diff --git a/Makefile b/Makefile index 1a58f64f23c..f43a7bceb36 100644 --- a/Makefile +++ b/Makefile @@ -185,6 +185,10 @@ ko-local: $(ARTIFACT_HUB_LABELS) \ github.com/sigstore/cosign/cmd/cosign/policy_webhook +.PHONY: ko-apply +ko-apply: + LDFLAGS="$(LDFLAGS)" GIT_HASH=$(GIT_HASH) GIT_VERSION=$(GIT_VERSION) ko apply -Bf config/ + ################## # help ################## diff --git a/pkg/apis/config/image_policies.go b/pkg/apis/config/image_policies.go index f8a5c8eb5f2..b63d392ac27 100644 --- a/pkg/apis/config/image_policies.go +++ b/pkg/apis/config/image_policies.go @@ -19,6 +19,7 @@ import ( "encoding/json" "errors" "fmt" + "regexp" "github.com/sigstore/cosign/pkg/apis/cosigned/v1alpha1" corev1 "k8s.io/api/core/v1" @@ -87,6 +88,7 @@ func (p *ImagePolicyConfig) GetAuthorities(image string) ([]v1alpha1.Authority, return nil, errors.New("config is nil") } + var lastError error ret := []v1alpha1.Authority{} // TODO(vaikas): this is very inefficient, we should have a better @@ -94,10 +96,18 @@ func (p *ImagePolicyConfig) GetAuthorities(image string) ([]v1alpha1.Authority, // workable so fine for now. for _, v := range p.Policies { for _, pattern := range v.Images { - if GlobMatch(image, pattern.Glob) { - ret = append(ret, v.Authorities...) + if pattern.Glob != "" { + if GlobMatch(image, pattern.Glob) { + ret = append(ret, v.Authorities...) + } + } else if pattern.Regex != "" { + if regex, err := regexp.Compile(pattern.Regex); err != nil { + lastError = err + } else if regex.MatchString(image) { + ret = append(ret, v.Authorities...) + } } } } - return ret, nil + return ret, lastError } diff --git a/pkg/apis/config/image_policies_test.go b/pkg/apis/config/image_policies_test.go index 28cc0bff326..d3562442c53 100644 --- a/pkg/apis/config/image_policies_test.go +++ b/pkg/apis/config/image_policies_test.go @@ -17,6 +17,7 @@ package config import ( "testing" + "github.com/sigstore/cosign/pkg/apis/cosigned/v1alpha1" . "knative.dev/pkg/configmap/testing" _ "knative.dev/pkg/system/testing" ) @@ -43,35 +44,20 @@ func TestGetAuthorities(t *testing.T) { t.Error("NewImagePoliciesConfigFromConfigMap(example) =", err) } c, err := defaults.GetAuthorities("rando") - if err != nil { - t.Error("GetMatches Failed =", err) - } - if len(c) == 0 { - t.Error("Wanted a config, got none.") - } + checkGetMatches(t, c, err) want := "inlinedata here" if got := c[0].Key.Data; got != want { - t.Errorf("Did not get what I wanted %q, got %+v", want, c[0].Key.Data) + t.Errorf("Did not get what I wanted %q, got %+v", want, got) } // Make sure glob matches 'randomstuff*' c, err = defaults.GetAuthorities("randomstuffhere") - if err != nil { - t.Error("GetMatches Failed =", err) - } - if len(c) == 0 { - t.Error("Wanted a config, got none.") - } + checkGetMatches(t, c, err) want = "otherinline here" if got := c[0].Key.Data; got != want { - t.Errorf("Did not get what I wanted %q, got %+v", want, c[0].Key.Data) + t.Errorf("Did not get what I wanted %q, got %+v", want, got) } c, err = defaults.GetAuthorities("rando3") - if err != nil { - t.Error("GetMatches Failed =", err) - } - if len(c) == 0 { - t.Error("Wanted a config, got none.") - } + checkGetMatches(t, c, err) want = "cacert chilling here" if got := c[0].Keyless.CACert.Data; got != want { t.Errorf("Did not get what I wanted %q, got %+v", want, c[0].Keyless.CACert.Data) @@ -84,28 +70,35 @@ func TestGetAuthorities(t *testing.T) { if got := c[0].Keyless.Identities[0].Subject; got != want { t.Errorf("Did not get what I wanted %q, got %+v", want, c[0].Keyless.Identities[0].Subject) } + // Make sure regex matches ".*regexstring.*" + c, err = defaults.GetAuthorities("randomregexstringstuff") + checkGetMatches(t, c, err) + want = inlineKeyData + if got := c[0].Key.Data; got != want { + t.Errorf("Did not get what I wanted %q, got %+v", want, got) + } + // Test multiline yaml cert c, err = defaults.GetAuthorities("inlinecert") - if err != nil { - t.Error("GetMatches Failed =", err) - } - if len(c) == 0 { - t.Error("Wanted a config, got none.") - } + checkGetMatches(t, c, err) want = inlineKeyData if got := c[0].Key.Data; got != want { - t.Errorf("Did not get what I wanted %q, got %+v", want, c[0].Key.Data) + t.Errorf("Did not get what I wanted %q, got %+v", want, got) } // Test multiline cert but json encoded c, err = defaults.GetAuthorities("ghcr.io/example/*") + checkGetMatches(t, c, err) + want = inlineKeyData + if got := c[0].Key.Data; got != want { + t.Errorf("Did not get what I wanted %q, got %+v", want, got) + } +} + +func checkGetMatches(t *testing.T, c []v1alpha1.Authority, err error) { if err != nil { t.Error("GetMatches Failed =", err) } if len(c) == 0 { t.Error("Wanted a config, got none.") } - want = inlineKeyData - if got := c[0].Key.Data; got != want { - t.Errorf("Did not get what I wanted %q, got %+v", want, c[0].Key.Data) - } } diff --git a/pkg/apis/config/testdata/config-image-policies.yaml b/pkg/apis/config/testdata/config-image-policies.yaml index 057b8f4a05d..fe6f26e84a6 100644 --- a/pkg/apis/config/testdata/config-image-policies.yaml +++ b/pkg/apis/config/testdata/config-image-policies.yaml @@ -62,5 +62,15 @@ data: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExB6+H6054/W1SJgs5JR6AJr6J35J RCTfQ5s1kD+hGMSE1rH7s46hmXEeyhnlRnaGF8eMU/SBJE/2NKPnxE7WzQ== -----END PUBLIC KEY----- + cluster-image-policy-4: | + images: + - regex: .*regexstring.* + authorities: + - key: + data: |- + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExB6+H6054/W1SJgs5JR6AJr6J35J + RCTfQ5s1kD+hGMSE1rH7s46hmXEeyhnlRnaGF8eMU/SBJE/2NKPnxE7WzQ== + -----END PUBLIC KEY----- cluster-image-policy-json: "{\"images\":[{\"glob\":\"ghcr.io/example/*\",\"regex\":\"\"}],\"authorities\":[{\"key\":{\"data\":\"-----BEGIN PUBLIC KEY-----\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExB6+H6054/W1SJgs5JR6AJr6J35J\\nRCTfQ5s1kD+hGMSE1rH7s46hmXEeyhnlRnaGF8eMU/SBJE/2NKPnxE7WzQ==\\n-----END PUBLIC KEY-----\"}}]}" diff --git a/pkg/apis/cosigned/v1alpha1/clusterimagepolicy_validation.go b/pkg/apis/cosigned/v1alpha1/clusterimagepolicy_validation.go index 3bf06ee0ba3..0c8300fbd13 100644 --- a/pkg/apis/cosigned/v1alpha1/clusterimagepolicy_validation.go +++ b/pkg/apis/cosigned/v1alpha1/clusterimagepolicy_validation.go @@ -16,6 +16,8 @@ package v1alpha1 import ( "context" + "fmt" + "regexp" "strings" "github.com/sigstore/cosign/pkg/apis/utils" @@ -59,7 +61,7 @@ func (image *ImagePattern) Validate(ctx context.Context) *apis.FieldError { } if image.Regex != "" { - errs = errs.Also(apis.ErrDisallowedFields("regex")) + errs = errs.Also(ValidateRegex(image.Regex).ViaField("regex")) } return errs @@ -155,3 +157,12 @@ func ValidateGlob(glob string) *apis.FieldError { } return nil } + +func ValidateRegex(regex string) *apis.FieldError { + _, err := regexp.Compile(regex) + if err != nil { + return apis.ErrInvalidValue(regex, apis.CurrentField, fmt.Sprintf("regex is invalid: %v", err)) + } + + return nil +} diff --git a/pkg/apis/cosigned/v1alpha1/clusterimagepolicy_validation_test.go b/pkg/apis/cosigned/v1alpha1/clusterimagepolicy_validation_test.go index 955da06d32b..ba7edfd8645 100644 --- a/pkg/apis/cosigned/v1alpha1/clusterimagepolicy_validation_test.go +++ b/pkg/apis/cosigned/v1alpha1/clusterimagepolicy_validation_test.go @@ -32,7 +32,7 @@ func TestImagePatternValidation(t *testing.T) { { name: "Should fail when both regex and glob are present", expectErr: true, - errorString: "expected exactly one, got both: spec.images[0].glob, spec.images[0].regex\ninvalid value: **: spec.images[0].glob\nglob match supports only a single * as a trailing character\nmissing field(s): spec.authorities\nmust not set the field(s): spec.images[0].regex", + errorString: "expected exactly one, got both: spec.images[0].glob, spec.images[0].regex\ninvalid value: **: spec.images[0].glob\nglob match supports only a single * as a trailing character\nmissing field(s): spec.authorities", policy: ClusterImagePolicy{ Spec: ClusterImagePolicySpec{ Images: []ImagePattern{ @@ -92,6 +92,40 @@ func TestImagePatternValidation(t *testing.T) { Spec: ClusterImagePolicySpec{}, }, }, + { + name: "Should fail when regex is invalid: %v", + expectErr: true, + errorString: "invalid value: *: spec.images[0].regex\nregex is invalid: error parsing regexp: missing argument to repetition operator: `*`\nmissing field(s): spec.authorities", + policy: ClusterImagePolicy{ + Spec: ClusterImagePolicySpec{ + Images: []ImagePattern{ + { + Regex: "*", + }, + }, + }, + }, + }, + { + name: "Should pass when regex is valid: %v", + expectErr: false, + policy: ClusterImagePolicy{ + Spec: ClusterImagePolicySpec{ + Images: []ImagePattern{ + { + Regex: ".*", + }, + }, + Authorities: []Authority{ + { + Key: &KeyRef{ + KMS: "kms://key/path", + }, + }, + }, + }, + }, + }, } for _, test := range tests { @@ -198,29 +232,8 @@ func TestKeyValidation(t *testing.T) { }, }, { - name: "Should fail when regex is given", - expectErr: true, - errorString: "must not set the field(s): spec.images[0].regex", - policy: ClusterImagePolicy{ - Spec: ClusterImagePolicySpec{ - Images: []ImagePattern{ - { - Regex: "myg**lob*", - }, - }, - Authorities: []Authority{ - { - Key: &KeyRef{ - KMS: "kms://key/path", - }, - }, - }, - }, - }, - }, - { - name: "Should pass when key has only one property", - expectErr: false, + name: "Should pass when key has only one property: %v", + errorString: "", policy: ClusterImagePolicy{ Spec: ClusterImagePolicySpec{ Images: []ImagePattern{ diff --git a/pkg/cosign/kubernetes/webhook/validation.go b/pkg/cosign/kubernetes/webhook/validation.go index f6bd397ccb9..ab1d9854a84 100644 --- a/pkg/cosign/kubernetes/webhook/validation.go +++ b/pkg/cosign/kubernetes/webhook/validation.go @@ -29,7 +29,6 @@ import ( "knative.dev/pkg/logging" "github.com/sigstore/cosign/cmd/cosign/cli/fulcio/fulcioroots" - "github.com/sigstore/cosign/pkg/apis/config" "github.com/sigstore/cosign/pkg/cosign" "github.com/sigstore/cosign/pkg/oci" ociremote "github.com/sigstore/cosign/pkg/oci/remote" @@ -37,20 +36,6 @@ import ( ) func valid(ctx context.Context, ref name.Reference, keys []*ecdsa.PublicKey, opts ...ociremote.Option) error { - // TODO(vaikas): No failures, just logging as to not interfere with the - // normal operation. Just starting to plumb things through here. - config := config.FromContext(ctx) - if config != nil { - authorities, err := config.ImagePolicyConfig.GetAuthorities(ref.Name()) - if err != nil { - logging.FromContext(ctx).Errorf("Failed to fetch authorities for %s : %v", ref.Name(), err) - } else { - for _, authority := range authorities { - logging.FromContext(ctx).Infof("TODO: Check authority for image: %s : Authority: %+v ", ref.Name(), authority) - } - } - } - if len(keys) == 0 { // If there are no keys, then verify against the fulcio root. sps, err := validSignatures(ctx, ref, nil /* verifier */, opts...) @@ -121,6 +106,27 @@ func getKeys(ctx context.Context, cfg map[string][]byte) ([]*ecdsa.PublicKey, *a return keys, nil } +func parseAuthorityKeys(ctx context.Context, pubKey string) ([]*ecdsa.PublicKey, *apis.FieldError) { + keys := []*ecdsa.PublicKey{} + errs := []error{} + + logging.FromContext(ctx).Debugf("Got public key: %v", pubKey) + + pems := parsePems([]byte(pubKey)) + for _, p := range pems { + key, err := x509.ParsePKIXPublicKey(p.Bytes) + if err != nil { + errs = append(errs, err) + } else { + keys = append(keys, key.(*ecdsa.PublicKey)) + } + } + if len(keys) == 0 { + return nil, apis.ErrGeneric(fmt.Sprintf("malformed authority key data: %v", errs), apis.CurrentField) + } + return keys, nil +} + func parsePems(b []byte) []*pem.Block { p, rest := pem.Decode(b) if p == nil { diff --git a/pkg/cosign/kubernetes/webhook/validator.go b/pkg/cosign/kubernetes/webhook/validator.go index d732c30ee9c..f2b5d7fc6ac 100644 --- a/pkg/cosign/kubernetes/webhook/validator.go +++ b/pkg/cosign/kubernetes/webhook/validator.go @@ -17,11 +17,13 @@ package webhook import ( "context" + "crypto/ecdsa" "fmt" "github.com/google/go-containerregistry/pkg/authn/k8schain" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/sigstore/cosign/pkg/apis/config" ociremote "github.com/sigstore/cosign/pkg/oci/remote" corev1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" @@ -138,7 +140,18 @@ func (v *Validator) validatePodSpec(ctx context.Context, ps *corev1.PodSpec, opt continue } - if err := valid(ctx, ref, keys, ociremote.WithRemoteOptions(remote.WithAuthFromKeychain(kc))); err != nil { + containerKeys := keys + config := config.FromContext(ctx) + if config != nil { + authorityKeys, fieldErrors := getAuthorityKeys(ctx, ref, config) + if fieldErrors != nil { + // TODO:(dennyhoang) Enforce currently non-breaking errors /~https://github.com/sigstore/cosign/issues/1642 + logging.FromContext(ctx).Warnf("Failed to fetch authorities for %s : %v", ref.Name(), fieldErrors) + } + containerKeys = append(containerKeys, authorityKeys...) + } + + if err := valid(ctx, ref, containerKeys, ociremote.WithRemoteOptions(remote.WithAuthFromKeychain(kc))); err != nil { errorField := apis.ErrGeneric(err.Error(), "image").ViaFieldIndex(field, i) errorField.Details = c.Image errs = errs.Also(errorField) @@ -153,6 +166,26 @@ func (v *Validator) validatePodSpec(ctx context.Context, ps *corev1.PodSpec, opt return errs } +func getAuthorityKeys(ctx context.Context, ref name.Reference, config *config.Config) (keys []*ecdsa.PublicKey, errs *apis.FieldError) { + authorities, err := config.ImagePolicyConfig.GetAuthorities(ref.Name()) + if err != nil { + return keys, apis.ErrGeneric(fmt.Sprintf("failed to fetch authorities for %s : %v", ref.Name(), err), apis.CurrentField) + } + + for _, authority := range authorities { + if authority.Key != nil { + // Get the key from authority data + if authorityKeys, fieldErr := parseAuthorityKeys(ctx, authority.Key.Data); fieldErr != nil { + errs = errs.Also(fieldErr) + } else { + keys = append(keys, authorityKeys...) + } + } + } + + return keys, errs +} + // ResolvePodSpecable implements duckv1.PodSpecValidator func (v *Validator) ResolvePodSpecable(ctx context.Context, wp *duckv1.WithPod) { if wp.DeletionTimestamp != nil { diff --git a/pkg/cosign/kubernetes/webhook/validator_test.go b/pkg/cosign/kubernetes/webhook/validator_test.go index fdbdbc62051..4d9b1473584 100644 --- a/pkg/cosign/kubernetes/webhook/validator_test.go +++ b/pkg/cosign/kubernetes/webhook/validator_test.go @@ -16,7 +16,11 @@ package webhook import ( + "bytes" "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/x509" "errors" "testing" "time" @@ -24,6 +28,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-containerregistry/pkg/authn/k8schain" "github.com/google/go-containerregistry/pkg/name" + "github.com/sigstore/cosign/pkg/apis/config" + "github.com/sigstore/cosign/pkg/apis/cosigned/v1alpha1" "github.com/sigstore/cosign/pkg/cosign" "github.com/sigstore/cosign/pkg/oci" "github.com/sigstore/cosign/pkg/oci/remote" @@ -50,6 +56,21 @@ func TestValidatePodSpec(t *testing.T) { secretName := "blah" + var authorityKeyCosignPub *ecdsa.PublicKey + // Random public key (cosign generate-key-pair) 2022-03-18 + authorityKeyCosignPubString := `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENAyijLvRu5QpCPp2uOj8C79ZW1VJ +SID/4H61ZiRzN4nqONzp+ZF22qQTk3MFO3D0/ZKmWHAosIf2pf2GHH7myA== +-----END PUBLIC KEY-----` + + pems := parsePems([]byte(authorityKeyCosignPubString)) + if len(pems) > 0 { + key, _ := x509.ParsePKIXPublicKey(pems[0].Bytes) + authorityKeyCosignPub = key.(*ecdsa.PublicKey) + } else { + t.Errorf("Error parsing authority key from string") + } + si.Informer().GetIndexer().Add(&corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: system.Namespace(), @@ -79,7 +100,7 @@ UoJou2P8sbDxpLiE/v3yLw1/jyOrCPWYHWFXnyyeGlkgSVefG54tNoK7Uw== cosignVerifySignatures = cvs }() // Let's just say that everything is verified. - pass := func(ctx context.Context, signedImgRef name.Reference, co *cosign.CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { + pass := func(_ context.Context, _ name.Reference, _ *cosign.CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { sig, err := static.NewSignature(nil, "") if err != nil { return nil, false, err @@ -87,15 +108,31 @@ UoJou2P8sbDxpLiE/v3yLw1/jyOrCPWYHWFXnyyeGlkgSVefG54tNoK7Uw== return []oci.Signature{sig}, true, nil } // Let's just say that everything is not verified. - fail := func(ctx context.Context, signedImgRef name.Reference, co *cosign.CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { + fail := func(_ context.Context, _ name.Reference, _ *cosign.CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { return nil, false, errors.New("bad signature") } + // Let's say it is verified if it is the expected Public Key + authorityPublicKeyCVS := func(ctx context.Context, signedImgRef name.Reference, co *cosign.CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { + actualPublicKey, _ := co.SigVerifier.PublicKey() + actualECDSAPubkey := actualPublicKey.(*ecdsa.PublicKey) + actualKeyData := elliptic.Marshal(actualECDSAPubkey, actualECDSAPubkey.X, actualECDSAPubkey.Y) + + expectedKeyData := elliptic.Marshal(authorityKeyCosignPub, authorityKeyCosignPub.X, authorityKeyCosignPub.Y) + + if bytes.Equal(actualKeyData, expectedKeyData) { + return pass(ctx, signedImgRef, co) + } + + return fail(ctx, signedImgRef, co) + } + tests := []struct { - name string - ps *corev1.PodSpec - want *apis.FieldError - cvs func(context.Context, name.Reference, *cosign.CheckOpts) ([]oci.Signature, bool, error) + name string + ps *corev1.PodSpec + want *apis.FieldError + cvs func(context.Context, name.Reference, *cosign.CheckOpts) ([]oci.Signature, bool, error) + customContext context.Context }{{ name: "simple, no error", ps: &corev1.PodSpec{ @@ -149,14 +186,52 @@ UoJou2P8sbDxpLiE/v3yLw1/jyOrCPWYHWFXnyyeGlkgSVefG54tNoK7Uw== Details: digest.String(), }, cvs: fail, + }, { + name: "simple, no error, authority key", + ps: &corev1.PodSpec{ + InitContainers: []corev1.Container{{ + Name: "setup-stuff", + Image: digest.String(), + }}, + Containers: []corev1.Container{{ + Name: "user-container", + Image: digest.String(), + }}, + }, + customContext: config.ToContext(context.Background(), + &config.Config{ + ImagePolicyConfig: &config.ImagePolicyConfig{ + Policies: map[string]v1alpha1.ClusterImagePolicySpec{ + "cluster-image-policy": { + Images: []v1alpha1.ImagePattern{{ + Regex: ".*", + }}, + Authorities: []v1alpha1.Authority{ + { + Key: &v1alpha1.KeyRef{ + Data: authorityKeyCosignPubString, + }, + }, + }, + }, + }, + }, + }, + ), + cvs: authorityPublicKeyCVS, }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { cosignVerifySignatures = test.cvs + testContext := context.Background() + + if test.customContext != nil { + testContext = test.customContext + } // Check the core mechanics - got := v.validatePodSpec(context.Background(), test.ps, k8schain.Options{}) + got := v.validatePodSpec(testContext, test.ps, k8schain.Options{}) if (got != nil) != (test.want != nil) { t.Errorf("validatePodSpec() = %v, wanted %v", got, test.want) } else if got != nil && got.Error() != test.want.Error() { @@ -167,7 +242,7 @@ UoJou2P8sbDxpLiE/v3yLw1/jyOrCPWYHWFXnyyeGlkgSVefG54tNoK7Uw== pod := &duckv1.Pod{ Spec: *test.ps, } - got = v.ValidatePod(context.Background(), pod) + got = v.ValidatePod(testContext, pod) want := test.want.ViaField("spec") if (got != nil) != (want != nil) { t.Errorf("ValidatePod() = %v, wanted %v", got, want) @@ -176,7 +251,7 @@ UoJou2P8sbDxpLiE/v3yLw1/jyOrCPWYHWFXnyyeGlkgSVefG54tNoK7Uw== } // Check that we don't block things being deleted. pod.DeletionTimestamp = &metav1.Time{Time: time.Now()} - if got := v.ValidatePod(context.Background(), pod); got != nil { + if got := v.ValidatePod(testContext, pod); got != nil { t.Errorf("ValidatePod() = %v, wanted nil", got) } @@ -188,7 +263,7 @@ UoJou2P8sbDxpLiE/v3yLw1/jyOrCPWYHWFXnyyeGlkgSVefG54tNoK7Uw== }, }, } - got = v.ValidatePodSpecable(context.Background(), withPod) + got = v.ValidatePodSpecable(testContext, withPod) want = test.want.ViaField("spec.template.spec") if (got != nil) != (want != nil) { t.Errorf("ValidatePodSpecable() = %v, wanted %v", got, want) @@ -197,7 +272,7 @@ UoJou2P8sbDxpLiE/v3yLw1/jyOrCPWYHWFXnyyeGlkgSVefG54tNoK7Uw== } // Check that we don't block things being deleted. withPod.DeletionTimestamp = &metav1.Time{Time: time.Now()} - if got := v.ValidatePodSpecable(context.Background(), withPod); got != nil { + if got := v.ValidatePodSpecable(testContext, withPod); got != nil { t.Errorf("ValidatePodSpecable() = %v, wanted nil", got) } }) @@ -865,3 +940,99 @@ UoJou2P8sbDxpLiE/v3yLw1/jyOrCPWYHWFXnyyeGlkgSVefG54tNoK7Uw== }) } } + +func TestGetAuthorityKeys(t *testing.T) { + refName := name.MustParseReference("gcr.io/distroless/static:nonroot") + // Random public key (cosign generate-key-pair) 2021-09-25 + validPublicKey := `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEapTW568kniCbL0OXBFIhuhOboeox +UoJou2P8sbDxpLiE/v3yLw1/jyOrCPWYHWFXnyyeGlkgSVefG54tNoK7Uw== +-----END PUBLIC KEY-----` + + validKeyData := v1alpha1.Authority{ + Key: &v1alpha1.KeyRef{ + Data: validPublicKey, + }, + } + + tests := []struct { + name string + imagePatterns []v1alpha1.ImagePattern + authorities []v1alpha1.Authority + wantKeyLength int + expectErr bool + }{ + { + name: "no authorities", + wantKeyLength: 0, + expectErr: false, + }, { + name: "invalid regex and one key data", + imagePatterns: []v1alpha1.ImagePattern{{ + Regex: "*", + }}, + authorities: []v1alpha1.Authority{ + validKeyData, + }, + wantKeyLength: 0, + expectErr: true, + }, { + name: "unmatching glob and one key data", + imagePatterns: []v1alpha1.ImagePattern{{ + Glob: "-", + }}, + authorities: []v1alpha1.Authority{ + validKeyData, + }, + wantKeyLength: 0, + expectErr: false, + }, { + name: "wildcard glob and one key data", + imagePatterns: []v1alpha1.ImagePattern{{ + Glob: "*", + }}, + authorities: []v1alpha1.Authority{ + validKeyData, + }, + wantKeyLength: 1, + expectErr: false, + }, { + name: "wildcard regex and one key data", + imagePatterns: []v1alpha1.ImagePattern{{ + Regex: ".*", + }}, + authorities: []v1alpha1.Authority{ + validKeyData, + }, + wantKeyLength: 1, + expectErr: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + config := config.Config{ + ImagePolicyConfig: &config.ImagePolicyConfig{ + Policies: map[string]v1alpha1.ClusterImagePolicySpec{ + "cluster-image-policy": { + Images: test.imagePatterns, + Authorities: test.authorities, + }, + }, + }, + } + + keys, err := getAuthorityKeys(context.Background(), refName, &config) + if test.expectErr && err == nil { + t.Error("Did not get wanted error") + } + if !test.expectErr && err != nil { + t.Error("Did get unwanted error") + } + + if got := len(keys); got != test.wantKeyLength { + t.Errorf("Did not get what I wanted %d, got %d", test.wantKeyLength, got) + } + }) + } +} diff --git a/test/e2e_test_policy_crd.sh b/test/e2e_test_policy_crd.sh index bcc12ff7321..247b0696561 100755 --- a/test/e2e_test_policy_crd.sh +++ b/test/e2e_test_policy_crd.sh @@ -37,6 +37,8 @@ do echo "${i} failed when it should not have" exit 1 fi + + kubectl delete -f ./test/testdata/cosigned/valid/$i --ignore-not-found=true done echo '::endgroup:: Valid policy test:' diff --git a/test/testdata/cosigned/invalid/invalid-regex.yaml b/test/testdata/cosigned/invalid/invalid-regex.yaml new file mode 100644 index 00000000000..1133acbb584 --- /dev/null +++ b/test/testdata/cosigned/invalid/invalid-regex.yaml @@ -0,0 +1,28 @@ +# Copyright 2022 The Sigstore Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +apiVersion: cosigned.sigstore.dev/v1alpha1 +kind: ClusterImagePolicy +metadata: + name: image-policy +spec: + images: + - regex: * + authorities: + - key: + data: | + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZxAfzrQG1EbWyCI8LiSB7YgSFXoI + FNGTyQGKHFc6/H8TQumT9VLS78pUwtv3w7EfKoyFZoP32KrO7nzUy2q6Cw== + -----END PUBLIC KEY----- diff --git a/test/testdata/cosigned/valid/valid-policy.yaml b/test/testdata/cosigned/valid/valid-policy-glob.yaml similarity index 100% rename from test/testdata/cosigned/valid/valid-policy.yaml rename to test/testdata/cosigned/valid/valid-policy-glob.yaml diff --git a/test/testdata/cosigned/valid/valid-policy-regex.yaml b/test/testdata/cosigned/valid/valid-policy-regex.yaml new file mode 100644 index 00000000000..a82ae479d84 --- /dev/null +++ b/test/testdata/cosigned/valid/valid-policy-regex.yaml @@ -0,0 +1,47 @@ +# Copyright 2022 The Sigstore Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +apiVersion: cosigned.sigstore.dev/v1alpha1 +kind: ClusterImagePolicy +metadata: + name: image-policy +spec: + images: + - regex: images\..* + - regex: image.* + authorities: + - keyless: + ca-cert: + secretRef: + name: ca-cert-secret + namespace: some-namespacemak + - keyless: + identities: + - issuer: "issue-details" + subject: "subject-details" + - keyless: + identities: + - issuer: "issue-details1" + - key: + data: | + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaEOVJCFtduYr3xqTxeRWSW32CY/s + TBNZj4oIUPl8JvhVPJ1TKDPlNcuT4YphSt6t3yOmMvkdQbCj8broX6vijw== + -----END PUBLIC KEY----- + - key: + kms: "kms://key/path" + - key: + secretRef: + name: secret-name + namespace: secret-namespce