diff --git a/pkg/api/kyverno/v1/policy_types.go b/pkg/api/kyverno/v1/policy_types.go index fc23a7a916..83df6bb554 100755 --- a/pkg/api/kyverno/v1/policy_types.go +++ b/pkg/api/kyverno/v1/policy_types.go @@ -459,7 +459,6 @@ type ImageVerification struct { Image string `json:"image,omitempty" yaml:"image,omitempty"` // Key is the PEM encoded public key that the image or attestation is signed with. - // Deprecated. Use Keys instead. Key string `json:"key,omitempty" yaml:"key,omitempty"` // Repository is an optional alternate OCI repository to use for image signatures that match this rule. diff --git a/pkg/cosign/cosign.go b/pkg/cosign/cosign.go index f624ecd676..ffa41dd30a 100644 --- a/pkg/cosign/cosign.go +++ b/pkg/cosign/cosign.go @@ -124,39 +124,48 @@ func FetchAttestations(imageRef string, key []byte, repository string) (map[stri return nil, errors.Wrap(err, "failed to verify image attestations") } - inTotoAttestation, err := decodeAttestation(verified) + inTotoAttestations, err := decodeAttestations(verified) if err != nil { return nil, err } - return inTotoAttestation, nil + return inTotoAttestations, nil } -func decodeAttestation(payloads []cosign.SignedPayload) (map[string]interface{}, error) { - if len(payloads) == 0 { - return nil, fmt.Errorf("empty payloads") +func decodeAttestations(attestations []cosign.SignedPayload) (map[string]interface{}, error) { + if len(attestations) == 0 { + return map[string]interface{}{}, nil } - payload := payloads[0].Payload + decodedAttestations := make([]map[string]interface{}, len(attestations)) - data, err := base64.StdEncoding.DecodeString(string(payload)) - if err != nil { - return nil, errors.Wrapf(err, "failed to base64 decode payload") + for _, a := range attestations { + payload := a.Payload + data, err := base64.StdEncoding.DecodeString(string(payload)) + if err != nil { + return nil, errors.Wrapf(err, "failed to base64 decode payload for attestation: %v", a) + } + + inTotoAttestation := make(map[string]interface{}) + if err := json.Unmarshal(data, &inTotoAttestation); err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal JSON payload for attestation: %v", a) + } + + attestationPayloadBase64 := inTotoAttestation["payload"].(string) + statement, err := base64.StdEncoding.DecodeString(attestationPayloadBase64) + if err != nil { + return nil, errors.Wrapf(err, "failed to base64 decode payload for ") + } + + inTotoAttestation["payload"] = statement + decodedAttestations = append(decodedAttestations, inTotoAttestation) } - inTotoAttestation := make(map[string]interface{}) - if err := json.Unmarshal(data, &inTotoAttestation); err != nil { - return nil, errors.Wrapf(err, "failed to unmarshal JSON payload") + results := map[string]interface{}{ + "attestations": decodedAttestations, } - attestationPayloadBase64 := inTotoAttestation["payload"].(string) - statement, err := base64.StdEncoding.DecodeString(attestationPayloadBase64) - if err != nil { - return nil, errors.Wrapf(err, "failed to base64 decode payload") - } - - inTotoAttestation["payload"] = statement - return inTotoAttestation, nil + return results, nil } func setSignatureRepo(cosignOpts *cosign.CheckOpts, ref name.Reference, repository string) error { diff --git a/pkg/engine/imageVerify.go b/pkg/engine/imageVerify.go index 3c448eeb90..6fa7839bf7 100644 --- a/pkg/engine/imageVerify.go +++ b/pkg/engine/imageVerify.go @@ -53,16 +53,31 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (resp *response.EngineRe } policyContext.JSONContext.Restore() + + iv := &imageVerifier { + logger: logger, + policyContext: policyContext, + rule: &rule, + resp: resp, + } + for _, imageVerify := range rule.VerifyImages { - verifyAndPatchImages(logger, policyContext, &rule, imageVerify, images.Containers, resp) - verifyAndPatchImages(logger, policyContext, &rule, imageVerify, images.InitContainers, resp) + iv.verify(imageVerify, images.Containers) + iv.verify(imageVerify, images.InitContainers) } } return } -func verifyAndPatchImages(logger logr.Logger, policyContext *PolicyContext, rule *v1.Rule, imageVerify *v1.ImageVerification, images map[string]*context.ImageInfo, resp *response.EngineResponse) { +type imageVerifier struct { + logger logr.Logger + policyContext *PolicyContext + rule *v1.Rule + resp *response.EngineResponse +} + +func (iv *imageVerifier) verify(imageVerify *v1.ImageVerification, images map[string]*context.ImageInfo) { imagePattern := imageVerify.Image key := imageVerify.Key repository := getSignatureRepository(imageVerify) @@ -70,20 +85,30 @@ func verifyAndPatchImages(logger logr.Logger, policyContext *PolicyContext, rule for _, imageInfo := range images { image := imageInfo.String() jmespath := utils.JsonPointerToJMESPath(imageInfo.JSONPointer) - changed, err := policyContext.JSONContext.HasChanged(jmespath) + changed, err := iv.policyContext.JSONContext.HasChanged(jmespath) if err == nil && !changed { - logger.V(4).Info("no change in image, skipping check", "image", image) + iv.logger.V(4).Info("no change in image, skipping check", "image", image) continue } if !wildcard.Match(imagePattern, image) { - logger.V(4).Info("image does not match pattern", "image", image, "pattern", imagePattern) + iv.logger.V(4).Info("image does not match pattern", "image", image, "pattern", imagePattern) continue } - ruleResp := verifyImage(logger, rule.Name, repository, key, imageInfo, imageVerify.Attestations) - resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp) - incrementAppliedCount(resp) + var ruleResp *response.RuleResponse + if len(imageVerify.Attestations) == 0 { + var digest string + ruleResp, digest = iv.verifySignature(repository, key, imageInfo) + if ruleResp.Success { + iv.patchDigest(imageInfo, digest, ruleResp) + } + } else { + ruleResp = iv.attestImage(repository, key, imageInfo) + } + + iv.resp.PolicyResponse.Rules = append(iv.resp.PolicyResponse.Rules, *ruleResp) + incrementAppliedCount(iv.resp) } } @@ -96,59 +121,37 @@ func getSignatureRepository(imageVerify *v1.ImageVerification) string { return repository } -func verifyImage(logger logr.Logger, ruleName, repository, key string, imageInfo *context.ImageInfo, attestations []*v1.AnyAllConditions) *response.RuleResponse { +func (iv *imageVerifier) verifySignature(repository, key string, imageInfo *context.ImageInfo) (*response.RuleResponse, string) { image := imageInfo.String() - logger.Info("verifying image", "image", image) + iv.logger.Info("verifying image", "image", image) ruleResp := &response.RuleResponse{ - Name: ruleName, + Name: iv.rule.Name, Type: utils.Validation.String(), } start := time.Now() - if len(attestations) == 0 { - digest, err := cosign.VerifySignature(image, []byte(key), repository, logger) - if err != nil { - logger.Info("failed to verify image signature", "image", image, "error", err, "duration", time.Since(start).Seconds()) - ruleResp.Success = false - ruleResp.Message = fmt.Sprintf("image verification failed for %s: %v", image, err) - return ruleResp - } - - ruleResp.Success = true - ruleResp.Message = fmt.Sprintf("image %s verified", image) - logger.V(3).Info("verified image", "image", image, "digest", digest, "duration", time.Since(start).Seconds()) - - addDigest(logger, imageInfo, digest, *ruleResp) - return ruleResp - } - - inTotoAttestation, err := cosign.FetchAttestations(image, []byte(key), repository) + digest, err := cosign.VerifySignature(image, []byte(key), repository, iv.logger) if err != nil { - logger.Info("failed to verify image attestations", "image", image, "error", err, "duration", time.Since(start).Seconds()) + iv.logger.Info("failed to verify image signature", "image", image, "error", err, "duration", time.Since(start).Seconds()) ruleResp.Success = false - ruleResp.Message = fmt.Sprintf("image verification failed for %s: %v", image, err) - return ruleResp + ruleResp.Message = fmt.Sprintf("image signature verification failed for %s: %v", image, err) + return ruleResp, "" } - logger.Info("received attestation", "in-toto-attestation", inTotoAttestation) - - - // add to context - - // process any / all conditions - - - return ruleResp + ruleResp.Success = true + ruleResp.Message = fmt.Sprintf("image %s verified", image) + iv.logger.V(3).Info("verified image", "image", image, "digest", digest, "duration", time.Since(start).Seconds()) + return ruleResp, digest } -func addDigest(logger logr.Logger, imageInfo *context.ImageInfo, digest string, ruleResp response.RuleResponse) { +func (iv *imageVerifier) patchDigest(imageInfo *context.ImageInfo, digest string, ruleResp *response.RuleResponse) { if imageInfo.Digest == "" { patch, err := makeAddDigestPatch(imageInfo, digest) if err != nil { - logger.Error(err, "failed to patch image with digest", "image", imageInfo.String(), "jsonPath", imageInfo.JSONPointer) + iv.logger.Error(err, "failed to patch image with digest", "image", imageInfo.String(), "jsonPath", imageInfo.JSONPointer) } else { - logger.V(4).Info("patching verified image with digest", "patch", string(patch)) + iv.logger.V(4).Info("patching verified image with digest", "patch", string(patch)) ruleResp.Patches = [][]byte{patch} } } @@ -161,3 +164,29 @@ func makeAddDigestPatch(imageInfo *context.ImageInfo, digest string) ([]byte, er patch["value"] = imageInfo.String() + "@" + digest return json.Marshal(patch) } + +func (iv *imageVerifier) attestImage(repository, key string, imageInfo *context.ImageInfo) *response.RuleResponse { + image := imageInfo.String() + + ruleResp := &response.RuleResponse{ + Name: iv.rule.Name, + Type: utils.Validation.String(), + } + + start := time.Now() + inTotoAttestation, err := cosign.FetchAttestations(image, []byte(key), repository) + if err != nil { + iv.logger.Info("failed to verify image attestations", "image", image, "error", err, "duration", time.Since(start).Seconds()) + ruleResp.Success = false + ruleResp.Message = fmt.Sprintf("image attestation failed for %s: %v", image, err) + return ruleResp + } + + iv.logger.Info("received attestation", "in-toto-attestation", inTotoAttestation) + + + // add to context + + // process any / all conditions + return ruleResp +}