mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-30 19:35:06 +00:00
support attestations
Signed-off-by: Jim Bugwadia <jim@nirmata.com>
This commit is contained in:
parent
0dbe7ea675
commit
249c0f62f8
3 changed files with 103 additions and 66 deletions
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue