1
0
Fork 0
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:
Jim Bugwadia 2021-10-02 01:19:47 -07:00
parent 0dbe7ea675
commit 249c0f62f8
3 changed files with 103 additions and 66 deletions

View file

@ -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.

View file

@ -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 {

View file

@ -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
}