diff --git a/api/kyverno/v1/image_verification_types.go b/api/kyverno/v1/image_verification_types.go index bfb8c5f243..20cfdfc68e 100644 --- a/api/kyverno/v1/image_verification_types.go +++ b/api/kyverno/v1/image_verification_types.go @@ -23,6 +23,9 @@ type ImageVerification struct { // Issuer is the certificate issuer used for keyless signing. Issuer string `json:"issuer,omitempty" yaml:"issuer,omitempty"` + // AdditionalExtensions are certificate-extensions used for keyless signing. + AdditionalExtensions map[string]string `json:"additionalExtensions,omitempty" yaml:"additionalExtensions,omitempty"` + // Annotations are used for image verification. // Every specified key-value pair must exist and match in the verified payload. // The payload may contain other key-value pairs. diff --git a/api/kyverno/v1/zz_generated.deepcopy.go b/api/kyverno/v1/zz_generated.deepcopy.go index e92331b3f5..ad9aa8f946 100755 --- a/api/kyverno/v1/zz_generated.deepcopy.go +++ b/api/kyverno/v1/zz_generated.deepcopy.go @@ -568,6 +568,13 @@ func (in *ImageRegistry) DeepCopy() *ImageRegistry { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ImageVerification) DeepCopyInto(out *ImageVerification) { *out = *in + if in.AdditionalExtensions != nil { + in, out := &in.AdditionalExtensions, &out.AdditionalExtensions + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } if in.Annotations != nil { in, out := &in.Annotations, &out.Annotations *out = make(map[string]string, len(*in)) diff --git a/charts/kyverno/templates/crds.yaml b/charts/kyverno/templates/crds.yaml index a85b570304..bb6600c4a7 100644 --- a/charts/kyverno/templates/crds.yaml +++ b/charts/kyverno/templates/crds.yaml @@ -1215,6 +1215,11 @@ spec: items: description: ImageVerification validates that images that match the specified pattern are signed with the supplied public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object annotations: additionalProperties: type: string @@ -2530,6 +2535,11 @@ spec: items: description: ImageVerification validates that images that match the specified pattern are signed with the supplied public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object annotations: additionalProperties: type: string @@ -5041,6 +5051,11 @@ spec: items: description: ImageVerification validates that images that match the specified pattern are signed with the supplied public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object annotations: additionalProperties: type: string @@ -6356,6 +6371,11 @@ spec: items: description: ImageVerification validates that images that match the specified pattern are signed with the supplied public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object annotations: additionalProperties: type: string diff --git a/config/crds/kyverno.io_clusterpolicies.yaml b/config/crds/kyverno.io_clusterpolicies.yaml index 2f8ed6884a..4764822859 100644 --- a/config/crds/kyverno.io_clusterpolicies.yaml +++ b/config/crds/kyverno.io_clusterpolicies.yaml @@ -1943,6 +1943,12 @@ spec: public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions + used for keyless signing. + type: object annotations: additionalProperties: type: string @@ -4057,6 +4063,12 @@ spec: public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions + used for keyless signing. + type: object annotations: additionalProperties: type: string diff --git a/config/crds/kyverno.io_policies.yaml b/config/crds/kyverno.io_policies.yaml index cb878ec262..7d607567d7 100644 --- a/config/crds/kyverno.io_policies.yaml +++ b/config/crds/kyverno.io_policies.yaml @@ -1944,6 +1944,12 @@ spec: public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions + used for keyless signing. + type: object annotations: additionalProperties: type: string @@ -4059,6 +4065,12 @@ spec: public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions + used for keyless signing. + type: object annotations: additionalProperties: type: string diff --git a/config/install.yaml b/config/install.yaml index 02d68ab110..7f47c16654 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -1959,6 +1959,12 @@ spec: public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions + used for keyless signing. + type: object annotations: additionalProperties: type: string @@ -4073,6 +4079,12 @@ spec: public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions + used for keyless signing. + type: object annotations: additionalProperties: type: string @@ -7771,6 +7783,12 @@ spec: public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions + used for keyless signing. + type: object annotations: additionalProperties: type: string @@ -9886,6 +9904,12 @@ spec: public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions + used for keyless signing. + type: object annotations: additionalProperties: type: string diff --git a/config/install_debug.yaml b/config/install_debug.yaml index 10ec37a655..00b9086b5e 100755 --- a/config/install_debug.yaml +++ b/config/install_debug.yaml @@ -1948,6 +1948,12 @@ spec: public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions + used for keyless signing. + type: object annotations: additionalProperties: type: string @@ -4062,6 +4068,12 @@ spec: public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions + used for keyless signing. + type: object annotations: additionalProperties: type: string @@ -7736,6 +7748,12 @@ spec: public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions + used for keyless signing. + type: object annotations: additionalProperties: type: string @@ -9851,6 +9869,12 @@ spec: public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions + used for keyless signing. + type: object annotations: additionalProperties: type: string diff --git a/docs/crd/v1/index.html b/docs/crd/v1/index.html index cad25877c6..691f6eecbc 100644 --- a/docs/crd/v1/index.html +++ b/docs/crd/v1/index.html @@ -1456,6 +1456,17 @@ string +additionalExtensions
+ +map[string]string + + + +

AdditionalExtensions are certificate-extensions used for keyless signing.

+ + + + annotations
map[string]string diff --git a/pkg/cosign/cosign.go b/pkg/cosign/cosign.go index 87d40b90d3..a204d3300e 100644 --- a/pkg/cosign/cosign.go +++ b/pkg/cosign/cosign.go @@ -38,14 +38,15 @@ import ( var ImageSignatureRepository string type Options struct { - ImageRef string - Key string - Roots []byte - Subject string - Issuer string - Annotations map[string]string - Repository string - Log logr.Logger + ImageRef string + Key string + Roots []byte + Subject string + Issuer string + AdditionalExtensions map[string]string + Annotations map[string]string + Repository string + Log logr.Logger } // VerifySignature verifies that the image has the expected key @@ -120,6 +121,10 @@ func VerifySignature(opts Options) (digest string, err error) { return "", err } + if err := matchExtensions(signatures, opts.AdditionalExtensions, log); err != nil { + return "", errors.Wrap(err, "extensions mismatch") + } + err = checkAnnotations(pld, opts.Annotations) if err != nil { return "", errors.Wrap(err, "annotation mismatch") @@ -378,6 +383,48 @@ func matchSubjectAndIssuer(signatures []oci.Signature, subject, issuer string) e return fmt.Errorf("subject mismatch: expected %s, got %s", s, subject) } +func matchExtensions(signatures []oci.Signature, requiredExtensions map[string]string, log logr.Logger) error { + if len(requiredExtensions) == 0 { + return nil + } + + for _, sig := range signatures { + cert, err := sig.Cert() + if err != nil { + return errors.Wrap(err, "failed to read certificate") + } + + if cert == nil { + return errors.Wrap(err, "certificate not found") + } + + // This will return a map which consists of readable extension-names as keys + // or the raw extensionIDs as fallback and its values. + certExtensions := sigs.CertExtensions(cert) + for requiredKey, requiredValue := range requiredExtensions { + certValue, ok := certExtensions[requiredKey] + if !ok { + // "requiredKey" seems to be an extensionID, try to resolve its human readable name + readableName, ok := sigs.CertExtensionMap[requiredKey] + if !ok { + return fmt.Errorf("key %s not present", requiredKey) + } + + certValue, ok = certExtensions[readableName] + if !ok { + return fmt.Errorf("key %s (%s) not present", requiredKey, readableName) + } + } + + if requiredValue != "" && !wildcard.Match(requiredValue, certValue) { + return fmt.Errorf("extension mismatch: expected %s for key %s, got %s", requiredValue, requiredKey, certValue) + } + } + } + + return nil +} + func checkAnnotations(payload []payload.SimpleContainerImage, annotations map[string]string) error { for _, p := range payload { for key, val := range annotations { diff --git a/pkg/engine/imageVerify.go b/pkg/engine/imageVerify.go index 73193ca53d..7834652048 100644 --- a/pkg/engine/imageVerify.go +++ b/pkg/engine/imageVerify.go @@ -195,6 +195,10 @@ func (iv *imageVerifier) verifySignature(imageVerify *v1.ImageVerification, imag opts.Subject = imageVerify.Subject } + if imageVerify.AdditionalExtensions != nil { + opts.AdditionalExtensions = imageVerify.AdditionalExtensions + } + if imageVerify.Annotations != nil { opts.Annotations = imageVerify.Annotations }