1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-05 07:26:55 +00:00

start attestation support

Signed-off-by: Jim Bugwadia <jim@nirmata.com>
This commit is contained in:
Jim Bugwadia 2021-10-01 11:10:36 -07:00
parent af944b9cd5
commit 0dbe7ea675
5 changed files with 441 additions and 109 deletions

20
go.mod
View file

@ -24,29 +24,27 @@ require (
github.com/lensesio/tableprinter v0.0.0-20201125135848-89e81fc956e7
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a
github.com/minio/pkg v1.1.3
github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.15.0
github.com/onsi/gomega v1.11.0
github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.15.0
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.11.0
github.com/robfig/cron/v3 v3.0.1
github.com/sigstore/cosign v1.0.0
github.com/sigstore/rekor v0.3.0 // indirect
github.com/sigstore/sigstore v0.0.0-20210726180807-7e34e36ecda1
github.com/sigstore/cosign v1.2.1
github.com/sigstore/sigstore v0.0.0-20210729211320-56a91f560f44
github.com/spf13/cobra v1.2.1
github.com/stretchr/testify v1.7.0
github.com/xanzy/ssh-agent v0.3.0 // indirect
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gotest.tools v2.2.0+incompatible
k8s.io/api v0.21.3
k8s.io/apiextensions-apiserver v0.21.1
k8s.io/apimachinery v0.21.3
k8s.io/api v0.21.4
k8s.io/apiextensions-apiserver v0.21.4
k8s.io/apimachinery v0.21.4
k8s.io/cli-runtime v0.21.1
k8s.io/client-go v0.21.3
k8s.io/klog/v2 v2.9.0
k8s.io/client-go v0.21.4
k8s.io/klog/v2 v2.10.0
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7
sigs.k8s.io/controller-runtime v0.8.1
sigs.k8s.io/kustomize/api v0.8.8

349
go.sum

File diff suppressed because it is too large Load diff

View file

@ -458,12 +458,16 @@ type ImageVerification struct {
// Wildcards ('*' and '?') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.
Image string `json:"image,omitempty" yaml:"image,omitempty"`
// Key is the PEM encoded public key that the image is signed with.
// 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.
// If specified Repository will override the default OCI image repository configured for the installation.
Repository string `json:"repository,omitempty" yaml:"repository,omitempty"`
// Attestations are optional statements used to verify the image.
Attestations []*AnyAllConditions `json:"attestations,omitempty" yaml:"attestations,omitempty"`
}
// Generation defines how new resources should be created and managed.

View file

@ -3,8 +3,11 @@ package cosign
import (
"context"
"crypto"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/sigstore/cosign/cmd/cosign/cli/fulcio"
"github.com/sigstore/sigstore/pkg/signature/dsse"
"strings"
"github.com/gardener/controller-manager-library/pkg/logger"
@ -20,7 +23,7 @@ import (
)
var (
// Alternate signature repository
// ImageSignatureRepository is an alternate signature repository
ImageSignatureRepository string
)
@ -42,13 +45,14 @@ func Initialize(client kubernetes.Interface, namespace, serviceAccount string, i
return nil
}
func Verify(imageRef string, key []byte, repository string, log logr.Logger) (digest string, err error) {
func VerifySignature(imageRef string, key []byte, repository string, log logr.Logger) (digest string, err error) {
pubKey, err := decodePEM(key)
if err != nil {
return "", errors.Wrapf(err, "failed to decode PEM %v", string(key))
}
cosignOpts := &cosign.CheckOpts{
RootCerts: fulcio.GetRoots(),
Annotations: map[string]interface{}{},
SigVerifier: pubKey,
RegistryClientOpts: []remote.Option{
@ -92,6 +96,82 @@ func Verify(imageRef string, key []byte, repository string, log logr.Logger) (di
return digest, nil
}
func FetchAttestations(imageRef string, key []byte, repository string) (map[string]interface{}, error) {
pubKey, err := decodePEM(key)
if err != nil {
return nil, errors.Wrapf(err, "failed to decode PEM %v", string(key))
}
ref, err := name.ParseReference(imageRef)
if err != nil {
return nil, errors.Wrap(err, "failed to parse image")
}
cosignOpts := &cosign.CheckOpts{
RootCerts: fulcio.GetRoots(),
ClaimVerifier: cosign.IntotoSubjectClaimVerifier,
SigTagSuffixOverride: cosign.AttestationTagSuffix,
SigVerifier: dsse.WrapVerifier(pubKey),
VerifyBundle: false,
}
if err := setSignatureRepo(cosignOpts, ref, repository); err != nil {
return nil, errors.Wrap(err, "failed to set signature repository")
}
verified, err := cosign.Verify(context.Background(), ref, cosignOpts)
if err != nil {
return nil, errors.Wrap(err, "failed to verify image attestations")
}
inTotoAttestation, err := decodeAttestation(verified)
if err != nil {
return nil, err
}
return inTotoAttestation, nil
}
func decodeAttestation(payloads []cosign.SignedPayload) (map[string]interface{}, error) {
if len(payloads) == 0 {
return nil, fmt.Errorf("empty payloads")
}
payload := payloads[0].Payload
data, err := base64.StdEncoding.DecodeString(string(payload))
if err != nil {
return nil, errors.Wrapf(err, "failed to base64 decode payload")
}
inTotoAttestation := make(map[string]interface{})
if err := json.Unmarshal(data, &inTotoAttestation); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal JSON payload")
}
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
}
func setSignatureRepo(cosignOpts *cosign.CheckOpts, ref name.Reference, repository string) error {
cosignOpts.SignatureRepo = ref.Context()
if repository != "" {
signatureRepo, err := name.NewRepository(repository)
if err != nil {
return errors.Wrapf(err, "failed to parse signature repository %s", repository)
}
cosignOpts.SignatureRepo = signatureRepo
}
return nil
}
func decodePEM(raw []byte) (signature.Verifier, error) {
// PEM encoded file.
ed, err := cosign.PemToECDSAKey(raw)

View file

@ -65,10 +65,7 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (resp *response.EngineRe
func verifyAndPatchImages(logger logr.Logger, policyContext *PolicyContext, rule *v1.Rule, imageVerify *v1.ImageVerification, images map[string]*context.ImageInfo, resp *response.EngineResponse) {
imagePattern := imageVerify.Image
key := imageVerify.Key
repository := cosign.ImageSignatureRepository
if imageVerify.Repository != "" {
repository = imageVerify.Repository
}
repository := getSignatureRepository(imageVerify)
for _, imageInfo := range images {
image := imageInfo.String()
@ -84,38 +81,76 @@ func verifyAndPatchImages(logger logr.Logger, policyContext *PolicyContext, rule
continue
}
logger.Info("verifying image", "image", image)
ruleResp := verifyImage(logger, rule.Name, repository, key, imageInfo, imageVerify.Attestations)
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
incrementAppliedCount(resp)
}
}
ruleResp := response.RuleResponse{
Name: rule.Name,
Type: utils.Validation.String(),
}
func getSignatureRepository(imageVerify *v1.ImageVerification) string {
repository := cosign.ImageSignatureRepository
if imageVerify.Repository != "" {
repository = imageVerify.Repository
}
start := time.Now()
digest, err := cosign.Verify(image, []byte(key), repository, logger)
return repository
}
func verifyImage(logger logr.Logger, ruleName, repository, key string, imageInfo *context.ImageInfo, attestations []*v1.AnyAllConditions) *response.RuleResponse {
image := imageInfo.String()
logger.Info("verifying image", "image", image)
ruleResp := &response.RuleResponse{
Name: ruleName,
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", "image", image, "key", key, "error", err, "duration", time.Since(start).Seconds())
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)
} else {
logger.V(3).Info("verified image", "image", image, "digest", digest, "duration", time.Since(start).Seconds())
ruleResp.Success = true
ruleResp.Message = fmt.Sprintf("image %s verified", image)
// add digest to image
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)
} else {
logger.V(4).Info("patching verified image with digest", "patch", string(patch))
ruleResp.Patches = [][]byte{patch}
}
}
return ruleResp
}
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, 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)
if err != nil {
logger.Info("failed to verify image attestations", "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
}
logger.Info("received attestation", "in-toto-attestation", inTotoAttestation)
// add to context
// process any / all conditions
return ruleResp
}
func addDigest(logger logr.Logger, 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)
} else {
logger.V(4).Info("patching verified image with digest", "patch", string(patch))
ruleResp.Patches = [][]byte{patch}
}
}
}