From bd1a145678166dbf9f81cc910e978bc03b58996c Mon Sep 17 00:00:00 2001 From: Jim Bugwadia Date: Sun, 13 Feb 2022 20:35:11 -0800 Subject: [PATCH] Fix keyless attest (#3219) * allow root cert for keyless attestations checks Signed-off-by: Jim Bugwadia * add logs and improve var names Signed-off-by: Jim Bugwadia * make fmt Signed-off-by: Jim Bugwadia * handle err in sig loading Signed-off-by: Jim Bugwadia Co-authored-by: Sambhav Kothari --- pkg/cosign/cosign.go | 41 ++++++++++++++++++-------------- pkg/engine/imageVerify.go | 10 ++++---- pkg/kyverno/test/test_command.go | 12 ++++++---- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/pkg/cosign/cosign.go b/pkg/cosign/cosign.go index 5044b9e9bd..68ecbc3f49 100644 --- a/pkg/cosign/cosign.go +++ b/pkg/cosign/cosign.go @@ -9,6 +9,8 @@ import ( "fmt" "strings" + v1 "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/sigstore/cosign/cmd/cosign/cli/rekor" "github.com/sigstore/cosign/cmd/cosign/cli/fulcio" @@ -145,23 +147,32 @@ func getFulcioRoots(roots []byte) (*x509.CertPool, error) { // FetchAttestations retrieves signed attestations and decodes them into in-toto statements // https://github.com/in-toto/attestation/blob/main/spec/README.md#statement -func FetchAttestations(imageRef string, key string, repository string, log logr.Logger) ([]map[string]interface{}, error) { +func FetchAttestations(imageRef string, imageVerify *v1.ImageVerification, log logr.Logger) ([]map[string]interface{}, error) { ctx := context.Background() - var pubKey signature.Verifier var err error - if strings.HasPrefix(key, "-----BEGIN PUBLIC KEY-----") { - pubKey, err = decodePEM([]byte(key)) - if err != nil { - return nil, errors.Wrap(err, "decode pem") + cosignOpts := &cosign.CheckOpts{ + ClaimVerifier: cosign.IntotoSubjectClaimVerifier, + } + + if imageVerify.Key != "" { + if strings.HasPrefix(imageVerify.Key, "-----BEGIN PUBLIC KEY-----") { + cosignOpts.SigVerifier, err = decodePEM([]byte(imageVerify.Key)) + } else { + cosignOpts.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, imageVerify.Key) } } else { - pubKey, err = sigs.PublicKeyFromKeyRef(ctx, key) - if err != nil { - return nil, errors.Wrap(err, "loading public key") + cosignOpts.CertEmail = "" + cosignOpts.RootCerts, err = getFulcioRoots([]byte(imageVerify.Roots)) + if err == nil { + cosignOpts.RekorClient, err = rekor.NewClient("https://rekor.sigstore.dev") } } + if err != nil { + return nil, errors.Wrap(err, "loading credentials") + } + var opts []remote.Option ro := options.RegistryOptions{} @@ -170,20 +181,14 @@ func FetchAttestations(imageRef string, key string, repository string, log logr. return nil, errors.Wrap(err, "constructing client options") } opts = append(opts, remote.WithRemoteOptions(gcrremote.WithAuthFromKeychain(registryclient.DefaultKeychain))) - if repository != "" { - signatureRepo, err := name.NewRepository(repository) + if imageVerify.Repository != "" { + signatureRepo, err := name.NewRepository(imageVerify.Repository) if err != nil { - return nil, errors.Wrapf(err, "failed to parse signature repository %s", repository) + return nil, errors.Wrapf(err, "failed to parse signature repository %s", imageVerify.Repository) } opts = append(opts, remote.WithTargetRepository(signatureRepo)) } - cosignOpts := &cosign.CheckOpts{ - ClaimVerifier: cosign.IntotoSubjectClaimVerifier, - SigVerifier: pubKey, - RegistryClientOpts: opts, - } - ref, err := name.ParseReference(imageRef) if err != nil { return nil, errors.Wrap(err, "failed to parse image") diff --git a/pkg/engine/imageVerify.go b/pkg/engine/imageVerify.go index 0d2d3cb858..5addea15a8 100644 --- a/pkg/engine/imageVerify.go +++ b/pkg/engine/imageVerify.go @@ -125,8 +125,6 @@ type imageVerifier struct { func (iv *imageVerifier) verify(imageVerify *v1.ImageVerification, images map[string]*context.ImageInfo) { imagePattern := imageVerify.Image - key := imageVerify.Key - repository := getSignatureRepository(imageVerify) for _, imageInfo := range images { image := imageInfo.String() @@ -150,7 +148,7 @@ func (iv *imageVerifier) verify(imageVerify *v1.ImageVerification, images map[st iv.patchDigest(imageInfo, digest, ruleResp) } } else { - ruleResp = iv.attestImage(repository, key, imageInfo, imageVerify.Attestations) + ruleResp = iv.attestImage(imageVerify, imageInfo) } iv.resp.PolicyResponse.Rules = append(iv.resp.PolicyResponse.Rules, *ruleResp) @@ -235,11 +233,11 @@ func makeAddDigestPatch(imageInfo *context.ImageInfo, digest string) ([]byte, er return json.Marshal(patch) } -func (iv *imageVerifier) attestImage(repository, key string, imageInfo *context.ImageInfo, attestationChecks []*v1.Attestation) *response.RuleResponse { +func (iv *imageVerifier) attestImage(imageVerify *v1.ImageVerification, imageInfo *context.ImageInfo) *response.RuleResponse { image := imageInfo.String() start := time.Now() - statements, err := cosign.FetchAttestations(image, key, repository, iv.logger) + statements, err := cosign.FetchAttestations(image, imageVerify, iv.logger) if err != nil { iv.logger.Info("failed to fetch attestations", "image", image, "error", err, "duration", time.Since(start).Seconds()) return ruleError(iv.rule, utils.ImageVerify, fmt.Sprintf("failed to fetch attestations for %s", image), err) @@ -248,7 +246,7 @@ func (iv *imageVerifier) attestImage(repository, key string, imageInfo *context. iv.logger.V(4).Info("received attestations", "statements", statements) statementsByPredicate := buildStatementMap(statements) - for _, ac := range attestationChecks { + for _, ac := range imageVerify.Attestations { statements := statementsByPredicate[ac.PredicateType] if statements == nil { msg := fmt.Sprintf("predicate type %s not found", ac.PredicateType) diff --git a/pkg/kyverno/test/test_command.go b/pkg/kyverno/test/test_command.go index 6afc4c2adc..94979db0b1 100644 --- a/pkg/kyverno/test/test_command.go +++ b/pkg/kyverno/test/test_command.go @@ -512,13 +512,13 @@ func buildPolicyResults(engineResponses []*response.EngineResponse, testResults } var result report.PolicyReportResult - var resultsKey []string + var resultsKeys []string var resultKey string - resultsKey = GetAllPossibleResultsKey("", info.PolicyName, rule.Name, infoResult.Resource.Namespace, infoResult.Resource.Kind, infoResult.Resource.Name) - for _, resultK := range resultsKey { - if val, ok := results[resultK]; ok { + resultsKeys = GetAllPossibleResultsKey("", info.PolicyName, rule.Name, infoResult.Resource.Namespace, infoResult.Resource.Kind, infoResult.Resource.Name) + for _, key := range resultsKeys { + if val, ok := results[key]; ok { result = val - resultKey = resultK + resultKey = key } else { continue } @@ -779,6 +779,7 @@ func printTestResult(resps map[string]report.PolicyReportResult, testResults []T if val, ok := resps[resultKey]; ok { testRes = val } else { + log.Log.V(2).Info("result not found", "key", resultKey) res.Result = boldYellow.Sprintf("Not found") rc.Fail++ table = append(table, res) @@ -799,6 +800,7 @@ func printTestResult(resps map[string]report.PolicyReportResult, testResults []T rc.Pass++ } } else { + log.Log.V(2).Info("result mismatch", "expected", testRes.Result, "received", v.Result, "key", resultKey) res.Result = boldRed.Sprintf("Fail") rc.Fail++ }