diff --git a/api/kyverno/v1/image_verification_types.go b/api/kyverno/v1/image_verification_types.go index 4f281acdee..b30d0dfe76 100644 --- a/api/kyverno/v1/image_verification_types.go +++ b/api/kyverno/v1/image_verification_types.go @@ -9,13 +9,13 @@ import ( ) // ImageVerificationType selects the type of verification algorithm -// +kubebuilder:validation:Enum=Cosign;NotaryV2 +// +kubebuilder:validation:Enum=Cosign;Notary // +kubebuilder:default=Cosign type ImageVerificationType string const ( - Cosign ImageVerificationType = "Cosign" - NotaryV2 ImageVerificationType = "NotaryV2" + Cosign ImageVerificationType = "Cosign" + Notary ImageVerificationType = "Notary" ) // ImageVerification validates that images that match the specified pattern @@ -23,7 +23,7 @@ const ( // mutated to include the SHA digest retrieved during the registration. type ImageVerification struct { // Type specifies the method of signature validation. The allowed options - // are Cosign and NotaryV2. By default Cosign is used if a type is not specified. + // are Cosign and Notary. By default Cosign is used if a type is not specified. // +kubebuilder:validation:Optional Type ImageVerificationType `json:"type,omitempty" yaml:"type,omitempty"` @@ -236,9 +236,14 @@ type CTLog struct { // OCI registry and decodes them into a list of Statements. type Attestation struct { // PredicateType defines the type of Predicate contained within the Statement. - // +kubebuilder:validation:Required + // Deprecated in favour of 'Type', to be removed soon + // +kubebuilder:validation:Optional PredicateType string `json:"predicateType" yaml:"predicateType"` + // Type defines the type of attestation contained within the Statement. + // +kubebuilder:validation:Optional + Type string `json:"type" yaml:"type"` + // Attestors specify the required attestors (i.e. authorities) // +kubebuilder:validation:Optional Attestors []AttestorSet `json:"attestors" yaml:"attestors"` @@ -281,6 +286,19 @@ func (iv *ImageVerification) Validate(isAuditFailureAction bool, path *field.Pat errs = append(errs, attestorErrors...) } + if iv.Type == Notary { + for _, attestorSet := range iv.Attestors { + for _, attestor := range attestorSet.Entries { + if attestor.Keyless != nil { + errs = append(errs, field.Invalid(attestorsPath, iv, "Keyless field is not allowed for type notary")) + } + if attestor.Keys != nil { + errs = append(errs, field.Invalid(attestorsPath, iv, "Keys field is not allowed for type notary")) + } + } + } + } + return errs } diff --git a/api/kyverno/v2beta1/image_verification_types.go b/api/kyverno/v2beta1/image_verification_types.go index 8485ccf15d..a2577ac234 100644 --- a/api/kyverno/v2beta1/image_verification_types.go +++ b/api/kyverno/v2beta1/image_verification_types.go @@ -6,13 +6,13 @@ import ( ) // ImageVerificationType selects the type of verification algorithm -// +kubebuilder:validation:Enum=Cosign;NotaryV2 +// +kubebuilder:validation:Enum=Cosign;Notary // +kubebuilder:default=Cosign type ImageVerificationType string const ( - Cosign ImageVerificationType = "Cosign" - NotaryV2 ImageVerificationType = "NotaryV2" + Cosign ImageVerificationType = "Cosign" + Notary ImageVerificationType = "Notary" ) // ImageVerification validates that images that match the specified pattern @@ -20,7 +20,7 @@ const ( // mutated to include the SHA digest retrieved during the registration. type ImageVerification struct { // Type specifies the method of signature validation. The allowed options - // are Cosign and NotaryV2. By default Cosign is used if a type is not specified. + // are Cosign and Notary. By default Cosign is used if a type is not specified. // +kubebuilder:validation:Optional Type ImageVerificationType `json:"type,omitempty" yaml:"type,omitempty"` @@ -86,5 +86,18 @@ func (iv *ImageVerification) Validate(isAuditFailureAction bool, path *field.Pat errs = append(errs, attestorErrors...) } + if iv.Type == Notary { + for _, attestorSet := range iv.Attestors { + for _, attestor := range attestorSet.Entries { + if attestor.Keyless != nil { + errs = append(errs, field.Invalid(attestorsPath, iv, "Keyless field is not allowed for type notary")) + } + if attestor.Keys != nil { + errs = append(errs, field.Invalid(attestorsPath, iv, "Keys field is not allowed for type notary")) + } + } + } + } + return errs } diff --git a/charts/kyverno/templates/crds/crds.yaml b/charts/kyverno/templates/crds/crds.yaml index 0ba7ecab21..05ca055a60 100644 --- a/charts/kyverno/templates/crds/crds.yaml +++ b/charts/kyverno/templates/crds/crds.yaml @@ -7248,10 +7248,13 @@ spec: type: array predicateType: description: PredicateType defines the type of Predicate + contained within the Statement. Deprecated in + favour of 'Type', to be removed soon + type: string + type: + description: Type defines the type of attestation contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -7499,11 +7502,11 @@ spec: type: string type: description: Type specifies the method of signature validation. - The allowed options are Cosign and NotaryV2. By default + The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -11153,9 +11156,13 @@ spec: predicateType: description: PredicateType defines the type of Predicate contained within the Statement. + Deprecated in favour of 'Type', to be removed + soon + type: string + type: + description: Type defines the type of attestation + contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -11412,11 +11419,11 @@ spec: type: string type: description: Type specifies the method of signature - validation. The allowed options are Cosign and NotaryV2. + validation. The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -14748,10 +14755,13 @@ spec: type: array predicateType: description: PredicateType defines the type of Predicate + contained within the Statement. Deprecated in + favour of 'Type', to be removed soon + type: string + type: + description: Type defines the type of attestation contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -14974,11 +14984,11 @@ spec: type: boolean type: description: Type specifies the method of signature validation. - The allowed options are Cosign and NotaryV2. By default + The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -18628,9 +18638,13 @@ spec: predicateType: description: PredicateType defines the type of Predicate contained within the Statement. + Deprecated in favour of 'Type', to be removed + soon + type: string + type: + description: Type defines the type of attestation + contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -18887,11 +18901,11 @@ spec: type: string type: description: Type specifies the method of signature - validation. The allowed options are Cosign and NotaryV2. + validation. The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -22509,10 +22523,13 @@ spec: type: array predicateType: description: PredicateType defines the type of Predicate + contained within the Statement. Deprecated in + favour of 'Type', to be removed soon + type: string + type: + description: Type defines the type of attestation contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -22760,11 +22777,11 @@ spec: type: string type: description: Type specifies the method of signature validation. - The allowed options are Cosign and NotaryV2. By default + The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -26415,9 +26432,13 @@ spec: predicateType: description: PredicateType defines the type of Predicate contained within the Statement. + Deprecated in favour of 'Type', to be removed + soon + type: string + type: + description: Type defines the type of attestation + contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -26674,11 +26695,11 @@ spec: type: string type: description: Type specifies the method of signature - validation. The allowed options are Cosign and NotaryV2. + validation. The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -30011,10 +30032,13 @@ spec: type: array predicateType: description: PredicateType defines the type of Predicate + contained within the Statement. Deprecated in + favour of 'Type', to be removed soon + type: string + type: + description: Type defines the type of attestation contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -30237,11 +30261,11 @@ spec: type: boolean type: description: Type specifies the method of signature validation. - The allowed options are Cosign and NotaryV2. By default + The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -33891,9 +33915,13 @@ spec: predicateType: description: PredicateType defines the type of Predicate contained within the Statement. + Deprecated in favour of 'Type', to be removed + soon + type: string + type: + description: Type defines the type of attestation + contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -34150,11 +34178,11 @@ spec: type: string type: description: Type specifies the method of signature - validation. The allowed options are Cosign and NotaryV2. + validation. The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true diff --git a/config/crds/kyverno.io_clusterpolicies.yaml b/config/crds/kyverno.io_clusterpolicies.yaml index 999640c451..b374e3b355 100644 --- a/config/crds/kyverno.io_clusterpolicies.yaml +++ b/config/crds/kyverno.io_clusterpolicies.yaml @@ -3494,10 +3494,13 @@ spec: type: array predicateType: description: PredicateType defines the type of Predicate + contained within the Statement. Deprecated in + favour of 'Type', to be removed soon + type: string + type: + description: Type defines the type of attestation contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -3745,11 +3748,11 @@ spec: type: string type: description: Type specifies the method of signature validation. - The allowed options are Cosign and NotaryV2. By default + The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -7399,9 +7402,13 @@ spec: predicateType: description: PredicateType defines the type of Predicate contained within the Statement. + Deprecated in favour of 'Type', to be removed + soon + type: string + type: + description: Type defines the type of attestation + contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -7658,11 +7665,11 @@ spec: type: string type: description: Type specifies the method of signature - validation. The allowed options are Cosign and NotaryV2. + validation. The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -10994,10 +11001,13 @@ spec: type: array predicateType: description: PredicateType defines the type of Predicate + contained within the Statement. Deprecated in + favour of 'Type', to be removed soon + type: string + type: + description: Type defines the type of attestation contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -11220,11 +11230,11 @@ spec: type: boolean type: description: Type specifies the method of signature validation. - The allowed options are Cosign and NotaryV2. By default + The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -14874,9 +14884,13 @@ spec: predicateType: description: PredicateType defines the type of Predicate contained within the Statement. + Deprecated in favour of 'Type', to be removed + soon + type: string + type: + description: Type defines the type of attestation + contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -15133,11 +15147,11 @@ spec: type: string type: description: Type specifies the method of signature - validation. The allowed options are Cosign and NotaryV2. + validation. The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true diff --git a/config/crds/kyverno.io_policies.yaml b/config/crds/kyverno.io_policies.yaml index 764c0e3bcf..4b0446f5d5 100644 --- a/config/crds/kyverno.io_policies.yaml +++ b/config/crds/kyverno.io_policies.yaml @@ -3495,10 +3495,13 @@ spec: type: array predicateType: description: PredicateType defines the type of Predicate + contained within the Statement. Deprecated in + favour of 'Type', to be removed soon + type: string + type: + description: Type defines the type of attestation contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -3746,11 +3749,11 @@ spec: type: string type: description: Type specifies the method of signature validation. - The allowed options are Cosign and NotaryV2. By default + The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -7401,9 +7404,13 @@ spec: predicateType: description: PredicateType defines the type of Predicate contained within the Statement. + Deprecated in favour of 'Type', to be removed + soon + type: string + type: + description: Type defines the type of attestation + contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -7660,11 +7667,11 @@ spec: type: string type: description: Type specifies the method of signature - validation. The allowed options are Cosign and NotaryV2. + validation. The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -10997,10 +11004,13 @@ spec: type: array predicateType: description: PredicateType defines the type of Predicate + contained within the Statement. Deprecated in + favour of 'Type', to be removed soon + type: string + type: + description: Type defines the type of attestation contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -11223,11 +11233,11 @@ spec: type: boolean type: description: Type specifies the method of signature validation. - The allowed options are Cosign and NotaryV2. By default + The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -14877,9 +14887,13 @@ spec: predicateType: description: PredicateType defines the type of Predicate contained within the Statement. + Deprecated in favour of 'Type', to be removed + soon + type: string + type: + description: Type defines the type of attestation + contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -15136,11 +15150,11 @@ spec: type: string type: description: Type specifies the method of signature - validation. The allowed options are Cosign and NotaryV2. + validation. The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true diff --git a/config/install-latest-testing.yaml b/config/install-latest-testing.yaml index 88b47ddc32..b95ed23734 100644 --- a/config/install-latest-testing.yaml +++ b/config/install-latest-testing.yaml @@ -7451,10 +7451,13 @@ spec: type: array predicateType: description: PredicateType defines the type of Predicate + contained within the Statement. Deprecated in + favour of 'Type', to be removed soon + type: string + type: + description: Type defines the type of attestation contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -7702,11 +7705,11 @@ spec: type: string type: description: Type specifies the method of signature validation. - The allowed options are Cosign and NotaryV2. By default + The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -11356,9 +11359,13 @@ spec: predicateType: description: PredicateType defines the type of Predicate contained within the Statement. + Deprecated in favour of 'Type', to be removed + soon + type: string + type: + description: Type defines the type of attestation + contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -11615,11 +11622,11 @@ spec: type: string type: description: Type specifies the method of signature - validation. The allowed options are Cosign and NotaryV2. + validation. The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -14951,10 +14958,13 @@ spec: type: array predicateType: description: PredicateType defines the type of Predicate + contained within the Statement. Deprecated in + favour of 'Type', to be removed soon + type: string + type: + description: Type defines the type of attestation contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -15177,11 +15187,11 @@ spec: type: boolean type: description: Type specifies the method of signature validation. - The allowed options are Cosign and NotaryV2. By default + The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -18831,9 +18841,13 @@ spec: predicateType: description: PredicateType defines the type of Predicate contained within the Statement. + Deprecated in favour of 'Type', to be removed + soon + type: string + type: + description: Type defines the type of attestation + contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -19090,11 +19104,11 @@ spec: type: string type: description: Type specifies the method of signature - validation. The allowed options are Cosign and NotaryV2. + validation. The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -22712,10 +22726,13 @@ spec: type: array predicateType: description: PredicateType defines the type of Predicate + contained within the Statement. Deprecated in + favour of 'Type', to be removed soon + type: string + type: + description: Type defines the type of attestation contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -22963,11 +22980,11 @@ spec: type: string type: description: Type specifies the method of signature validation. - The allowed options are Cosign and NotaryV2. By default + The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -26618,9 +26635,13 @@ spec: predicateType: description: PredicateType defines the type of Predicate contained within the Statement. + Deprecated in favour of 'Type', to be removed + soon + type: string + type: + description: Type defines the type of attestation + contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -26877,11 +26898,11 @@ spec: type: string type: description: Type specifies the method of signature - validation. The allowed options are Cosign and NotaryV2. + validation. The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -30214,10 +30235,13 @@ spec: type: array predicateType: description: PredicateType defines the type of Predicate + contained within the Statement. Deprecated in + favour of 'Type', to be removed soon + type: string + type: + description: Type defines the type of attestation contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -30440,11 +30464,11 @@ spec: type: boolean type: description: Type specifies the method of signature validation. - The allowed options are Cosign and NotaryV2. By default + The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true @@ -34094,9 +34118,13 @@ spec: predicateType: description: PredicateType defines the type of Predicate contained within the Statement. + Deprecated in favour of 'Type', to be removed + soon + type: string + type: + description: Type defines the type of attestation + contained within the Statement. type: string - required: - - predicateType type: object type: array attestors: @@ -34353,11 +34381,11 @@ spec: type: string type: description: Type specifies the method of signature - validation. The allowed options are Cosign and NotaryV2. + validation. The allowed options are Cosign and Notary. By default Cosign is used if a type is not specified. enum: - Cosign - - NotaryV2 + - Notary type: string verifyDigest: default: true diff --git a/docs/user/crd/index.html b/docs/user/crd/index.html index 220aee3cd8..5060ed34fc 100644 --- a/docs/user/crd/index.html +++ b/docs/user/crd/index.html @@ -715,7 +715,19 @@ string -

PredicateType defines the type of Predicate contained within the Statement.

+

PredicateType defines the type of Predicate contained within the Statement. +Deprecated in favour of ‘Type’, to be removed soon

+ + + + +type
+ +string + + + +

Type defines the type of attestation contained within the Statement.

@@ -1972,7 +1984,7 @@ ImageVerificationType

Type specifies the method of signature validation. The allowed options -are Cosign and NotaryV2. By default Cosign is used if a type is not specified.

+are Cosign and Notary. By default Cosign is used if a type is not specified.

@@ -6372,7 +6384,7 @@ ImageVerificationType

Type specifies the method of signature validation. The allowed options -are Cosign and NotaryV2. By default Cosign is used if a type is not specified.

+are Cosign and Notary. By default Cosign is used if a type is not specified.

diff --git a/go.mod b/go.mod index cfecfba775..3bf9a76f60 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/go-logr/logr v1.2.4 github.com/go-logr/zapr v1.2.4 github.com/google/gnostic v0.6.9 - github.com/google/go-containerregistry v0.14.0 + github.com/google/go-containerregistry v0.14.1-0.20230425172351-b7c6e9dc3944 github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20230403180904-b8d1c0a1df12 github.com/in-toto/in-toto-golang v0.6.0 github.com/jmespath/go-jmespath v0.4.0 @@ -33,6 +33,7 @@ require ( github.com/notaryproject/notation-go v1.0.0-rc.3 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.27.7 + github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0-rc2 github.com/orcaman/concurrent-map/v2 v2.0.1 github.com/pkg/errors v0.9.1 @@ -76,7 +77,6 @@ require ( k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f k8s.io/pod-security-admission v0.27.2 k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 - oras.land/oras-go/v2 v2.2.0 sigs.k8s.io/controller-runtime v0.15.0 sigs.k8s.io/kustomize/api v0.13.4 sigs.k8s.io/kustomize/kyaml v0.14.2 @@ -84,13 +84,13 @@ require ( ) require ( + cloud.google.com/go/compute v1.19.1 // indirect github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect github.com/google/cel-go v0.12.6 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect ) require ( - cloud.google.com/go/compute v1.19.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.0.0 // indirect cloud.google.com/go/kms v1.10.1 // indirect @@ -108,7 +108,7 @@ require ( github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect - github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect github.com/OneOfOne/xxhash v1.2.8 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect github.com/ThalesIgnite/crypto11 v1.2.5 // indirect @@ -156,9 +156,9 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/djherbis/times v1.5.0 // indirect - github.com/docker/cli v23.0.2+incompatible // indirect + github.com/docker/cli v23.0.4+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v23.0.3+incompatible // indirect + github.com/docker/docker v23.0.4+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.10.2 // indirect @@ -229,7 +229,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.16.3 // indirect + github.com/klauspost/compress v1.16.5 // indirect github.com/leodido/go-urn v1.2.2 // indirect github.com/letsencrypt/boulder v0.0.0-20230331213904-8c67769be400 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect @@ -254,7 +254,6 @@ require ( github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 // indirect github.com/open-policy-agent/gatekeeper v0.0.0-20210824170141-dd97b8a7e966 // indirect github.com/open-policy-agent/opa v0.51.0 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pelletier/go-toml/v2 v2.0.7 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect @@ -313,7 +312,7 @@ require ( go.uber.org/atomic v1.10.0 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.10.0 // indirect - golang.org/x/oauth2 v0.6.0 // indirect + golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/sync v0.2.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/term v0.8.0 // indirect @@ -330,6 +329,7 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect k8s.io/component-base v0.27.2 // indirect k8s.io/kubectl v0.26.3 // indirect + oras.land/oras-go/v2 v2.1.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/release-utils v0.7.3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect diff --git a/go.sum b/go.sum index c324ad23b7..f1c0fff693 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= -cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= @@ -109,8 +109,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= -github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.7/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= @@ -355,12 +355,12 @@ github.com/distribution/distribution v2.8.2+incompatible h1:k9+4DKdOG+quPFZXT/mU github.com/distribution/distribution v2.8.2+incompatible/go.mod h1:EgLm2NgWtdKgzF9NpMzUKgzmR7AMmb0VQi2B+ZzDRjc= github.com/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU= github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0= -github.com/docker/cli v23.0.2+incompatible h1:Yj4wkrNtyCNLCMobKDYzEUIsbtMbfAulkHMH75/ecik= -github.com/docker/cli v23.0.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v23.0.4+incompatible h1:xClB7PsiATttDHj8ce5qvJcikiApNy7teRR1XkoBZGs= +github.com/docker/cli v23.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v23.0.3+incompatible h1:9GhVsShNWz1hO//9BNg/dpMnZW25KydO4wtVxWAIbho= -github.com/docker/docker v23.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v23.0.4+incompatible h1:Kd3Bh9V/rO+XpTP/BLqM+gx8z7+Yb0AA2Ibj+nNo4ek= +github.com/docker/docker v23.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= @@ -669,8 +669,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.14.0 h1:z58vMqHxuwvAsVwvKEkmVBz2TlgBgH5k6koEXBtlYkw= -github.com/google/go-containerregistry v0.14.0/go.mod h1:aiJ2fp/SXvkWgmYHioXnbMdlgB8eXiiYOY55gfN91Wk= +github.com/google/go-containerregistry v0.14.1-0.20230425172351-b7c6e9dc3944 h1:7c5khUnWebZDFMUQ7rf2vynmmnKI1VvBACrTZKKpoD4= +github.com/google/go-containerregistry v0.14.1-0.20230425172351-b7c6e9dc3944/go.mod h1:0JopT7wiZeP5/ATNgx85oApuNAiNnfn4mr8+WOssYNQ= github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20230403180904-b8d1c0a1df12 h1:LLLVB/7zCZVKI27rqA7bbZHZJxH1lL2jbLxdomX1Eew= github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20230403180904-b8d1c0a1df12/go.mod h1:CSeefFZsOfyNrYGXDafpWNkf3tUz17nKReR5INPRaMI= github.com/google/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FCD+K3FI= @@ -880,8 +880,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= -github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -1672,8 +1672,8 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= -golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2176,8 +2176,8 @@ mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= mvdan.cc/unparam v0.0.0-20210104141923-aac4ce9116a7/go.mod h1:hBpJkZE8H/sb+VRFvw2+rBpHNsTBcvSpk61hr8mzXZE= -oras.land/oras-go/v2 v2.2.0 h1:E1fqITD56Eg5neZbxBtAdZVgDHD6wBabJo6xESTcQyo= -oras.land/oras-go/v2 v2.2.0/go.mod h1:pXjn0+KfarspMHHNR3A56j3tgvr+mxArHuI8qVn59v8= +oras.land/oras-go/v2 v2.1.0 h1:1nS8BIeEP6CBVQifwxrsth2bkuD+cYfjp7Hf7smUcS8= +oras.land/oras-go/v2 v2.1.0/go.mod h1:v5ZSAPIMEJYnZjZ6rTGPAyaonH+rCFmbE95IAzCTeGU= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/pkg/cosign/cosign.go b/pkg/cosign/cosign.go index 37c9b2af8c..20a745fd1c 100644 --- a/pkg/cosign/cosign.go +++ b/pkg/cosign/cosign.go @@ -79,7 +79,7 @@ func (v *cosignVerifier) VerifySignature(ctx context.Context, opts images.Option } var digest string - if opts.PredicateType == "" { + if opts.Type == "" { digest, err = extractDigest(opts.ImageRef, payload) if err != nil { return nil, err @@ -265,13 +265,13 @@ func (v *cosignVerifier) FetchAttestations(ctx context.Context, opts images.Opti } for _, signature := range signatures { - match, predicateType, err := matchPredicateType(signature, opts.PredicateType) + match, predicateType, err := matchType(signature, opts.Type) if err != nil { return nil, err } if !match { - logger.V(4).Info("predicateType doesn't match, continue", "expected", opts.PredicateType, "received", predicateType) + logger.V(4).Info("type doesn't match, continue", "expected", opts.Type, "received", predicateType) continue } @@ -294,15 +294,15 @@ func (v *cosignVerifier) FetchAttestations(ctx context.Context, opts images.Opti return &images.Response{Digest: digest, Statements: inTotoStatements}, nil } -func matchPredicateType(sig oci.Signature, expectedPredicateType string) (bool, string, error) { - if expectedPredicateType != "" { +func matchType(sig oci.Signature, expectedType string) (bool, string, error) { + if expectedType != "" { statement, _, err := decodeStatement(sig) if err != nil { - return false, "", fmt.Errorf("failed to decode predicateType: %w", err) + return false, "", fmt.Errorf("failed to decode type: %w", err) } - if pType, ok := statement["predicateType"]; ok { - if pType.(string) == expectedPredicateType { + if pType, ok := statement["type"]; ok { + if pType.(string) == expectedType { return true, pType.(string), nil } } @@ -360,6 +360,7 @@ func decodeStatement(sig oci.Signature) (map[string]interface{}, string, error) if err != nil { return nil, "", fmt.Errorf("failed to decode statement %s: %w", string(pld), err) } + decodedStatement["type"] = decodedStatement["predicateType"] return decodedStatement, digest, nil } @@ -376,7 +377,7 @@ func decodePayload(payloadBase64 string) (map[string]interface{}, error) { return nil, err } - if statement.PredicateType != attestation.CosignCustomProvenanceV01 { + if statement.Type != attestation.CosignCustomProvenanceV01 { // This assumes that the following statements are JSON objects: // - in_toto.PredicateSLSAProvenanceV01 // - in_toto.PredicateLinkV1 @@ -389,7 +390,7 @@ func decodePayload(payloadBase64 string) (map[string]interface{}, error) { } func decodeCosignCustomProvenanceV01(statement in_toto.Statement) (map[string]interface{}, error) { - if statement.PredicateType != attestation.CosignCustomProvenanceV01 { + if statement.Type != attestation.CosignCustomProvenanceV01 { return nil, fmt.Errorf("invalid statement type %s", attestation.CosignCustomProvenanceV01) } diff --git a/pkg/engine/api/context.go b/pkg/engine/api/context.go index d1b80b63d7..0091ad8c8c 100644 --- a/pkg/engine/api/context.go +++ b/pkg/engine/api/context.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/go-logr/logr" + "github.com/google/go-containerregistry/pkg/name" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/engine/apicall" @@ -141,6 +142,13 @@ func fetchImageData(ctx context.Context, jp jmespath.Interface, rclient registry // FetchImageDataMap fetches image information from the remote registry. func fetchImageDataMap(ctx context.Context, rclient registryclient.Client, ref string) (interface{}, error) { desc, err := rclient.FetchImageDescriptor(ctx, ref) + if err != nil { + return nil, fmt.Errorf("failed to fetch image descriptor: %s, error: %v", ref, err) + } + parsedRef, err := name.ParseReference(ref) + if err != nil { + return nil, fmt.Errorf("failed to parse image reference: %s, error: %v", ref, err) + } if err != nil { return nil, err } @@ -169,10 +177,10 @@ func fetchImageDataMap(ctx context.Context, rclient registryclient.Client, ref s data := map[string]interface{}{ "image": ref, - "resolvedImage": fmt.Sprintf("%s@%s", desc.Ref.Context().Name(), desc.Digest.String()), - "registry": desc.Ref.Context().RegistryStr(), - "repository": desc.Ref.Context().RepositoryStr(), - "identifier": desc.Ref.Identifier(), + "resolvedImage": fmt.Sprintf("%s@%s", parsedRef.Context().Name(), desc.Digest.String()), + "registry": parsedRef.Context().RegistryStr(), + "repository": parsedRef.Context().RepositoryStr(), + "identifier": parsedRef.Identifier(), "manifest": manifest, "configData": configData, } diff --git a/pkg/engine/handlers/mutation/mutate_image.go b/pkg/engine/handlers/mutation/mutate_image.go index 85b187ce22..c48ca38c21 100644 --- a/pkg/engine/handlers/mutation/mutate_image.go +++ b/pkg/engine/handlers/mutation/mutate_image.go @@ -112,7 +112,9 @@ func substituteVariables(rule kyvernov1.Rule, ctx enginecontext.EvalInterface, l // remove attestations as variables are not substituted in them ruleCopy := *rule.DeepCopy() for i := range ruleCopy.VerifyImages { - ruleCopy.VerifyImages[i].Attestations = nil + for j := range ruleCopy.VerifyImages[i].Attestations { + ruleCopy.VerifyImages[i].Attestations[j].Conditions = nil + } } var err error ruleCopy, err = variables.SubstituteAllInRule(logger, ctx, ruleCopy) @@ -120,8 +122,10 @@ func substituteVariables(rule kyvernov1.Rule, ctx enginecontext.EvalInterface, l return nil, err } // replace attestations - for i := range rule.VerifyImages { - ruleCopy.VerifyImages[i].Attestations = rule.VerifyImages[i].Attestations + for i := range ruleCopy.VerifyImages { + for j := range ruleCopy.VerifyImages[i].Attestations { + ruleCopy.VerifyImages[i].Attestations[j].Conditions = rule.VerifyImages[i].Attestations[j].Conditions + } } return &ruleCopy, nil } diff --git a/pkg/engine/internal/imageverifier.go b/pkg/engine/internal/imageverifier.go index f8a9bb21d8..ffd5601c6d 100644 --- a/pkg/engine/internal/imageverifier.go +++ b/pkg/engine/internal/imageverifier.go @@ -15,7 +15,7 @@ import ( enginecontext "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/variables" "github.com/kyverno/kyverno/pkg/images" - "github.com/kyverno/kyverno/pkg/notaryv2" + "github.com/kyverno/kyverno/pkg/notary" "github.com/kyverno/kyverno/pkg/registryclient" apiutils "github.com/kyverno/kyverno/pkg/utils/api" "github.com/kyverno/kyverno/pkg/utils/jsonpointer" @@ -138,7 +138,7 @@ func buildStatementMap(statements []map[string]interface{}) (map[string][]map[st results := map[string][]map[string]interface{}{} var predicateTypes []string for _, s := range statements { - predicateType := s["predicateType"].(string) + predicateType := s["type"].(string) if results[predicateType] != nil { results[predicateType] = append(results[predicateType], s) } else { @@ -249,6 +249,11 @@ func (iv *ImageVerifier) verifyImage( return nil, "" } image := imageInfo.String() + for _, att := range imageVerify.Attestations { + if att.Type == "" && att.PredicateType != "" { + att.Type = att.PredicateType + } + } iv.logger.V(2).Info("verifying image signatures", "image", image, "attestors", len(imageVerify.Attestors), "attestations", len(imageVerify.Attestations)) if err := iv.policyContext.JSONContext().AddImageInfo(imageInfo, cfg); err != nil { iv.logger.Error(err, "failed to add image to context") @@ -319,8 +324,13 @@ func (iv *ImageVerifier) verifyAttestations( var attestationError error path := fmt.Sprintf(".attestations[%d]", i) - if attestation.PredicateType == "" { - return engineapi.RuleFail(iv.rule.Name, engineapi.ImageVerify, path+": missing predicateType"), "" + iv.logger.V(2).Info(fmt.Sprintf("attestation %+v", attestation)) + if attestation.Type == "" && attestation.PredicateType == "" { + return engineapi.RuleFail(iv.rule.Name, engineapi.ImageVerify, path+": missing type"), "" + } + + if attestation.Type == "" && attestation.PredicateType != "" { + attestation.Type = attestation.PredicateType } if len(attestation.Attestors) == 0 { @@ -366,7 +376,7 @@ func (iv *ImageVerifier) verifyAttestations( } } - iv.logger.V(4).Info("attestation checks passed", "path", path, "image", imageInfo.String(), "predicateType", attestation.PredicateType) + iv.logger.V(4).Info("attestation checks passed", "path", path, "image", imageInfo.String(), "type", attestation.Type) } msg := fmt.Sprintf("verified image attestations for %s", image) @@ -432,8 +442,8 @@ func (iv *ImageVerifier) buildVerifier( attestation *kyvernov1.Attestation, ) (images.ImageVerifier, *images.Options, string) { switch imageVerify.Type { - case kyvernov1.NotaryV2: - return iv.buildNotaryV2Verifier(attestor, imageVerify, image) + case kyvernov1.Notary: + return iv.buildNotaryVerifier(attestor, imageVerify, image, attestation) default: return iv.buildCosignVerifier(attestor, imageVerify, image, attestation) } @@ -463,6 +473,11 @@ func (iv *ImageVerifier) buildCosignVerifier( if attestation != nil { opts.PredicateType = attestation.PredicateType + opts.Type = attestation.Type + if attestation.PredicateType != "" && attestation.Type == "" { + iv.logger.Info("predicate type has been deprecated, please use type instead") + opts.Type = attestation.PredicateType + } opts.FetchAttestations = true } @@ -509,10 +524,11 @@ func (iv *ImageVerifier) buildCosignVerifier( return cosign.NewVerifier(), opts, path } -func (iv *ImageVerifier) buildNotaryV2Verifier( +func (iv *ImageVerifier) buildNotaryVerifier( attestor kyvernov1.Attestor, imageVerify kyvernov1.ImageVerification, image string, + attestation *kyvernov1.Attestation, ) (images.ImageVerifier, *images.Options, string) { path := "" opts := &images.Options{ @@ -522,20 +538,38 @@ func (iv *ImageVerifier) buildNotaryV2Verifier( RegistryClient: iv.rclient, } - return notaryv2.NewVerifier(), opts, path + if attestation != nil { + opts.Type = attestation.Type + opts.PredicateType = attestation.PredicateType + if attestation.PredicateType != "" && attestation.Type == "" { + iv.logger.Info("predicate type has been deprecated, please use type instead") + opts.Type = attestation.PredicateType + } + opts.FetchAttestations = true + } + + if attestor.Repository != "" { + opts.Repository = attestor.Repository + } + + if attestor.Annotations != nil { + opts.Annotations = attestor.Annotations + } + + return notary.NewVerifier(), opts, path } func (iv *ImageVerifier) verifyAttestation(statements []map[string]interface{}, attestation kyvernov1.Attestation, imageInfo apiutils.ImageInfo) error { - if attestation.PredicateType == "" { - return fmt.Errorf("a predicateType is required") + if attestation.Type == "" && attestation.PredicateType == "" { + return fmt.Errorf("a type is required") } image := imageInfo.String() statementsByPredicate, types := buildStatementMap(statements) iv.logger.V(4).Info("checking attestations", "predicates", types, "image", image) - statements = statementsByPredicate[attestation.PredicateType] + statements = statementsByPredicate[attestation.Type] if statements == nil { - iv.logger.Info("no attestations found for predicate", "type", attestation.PredicateType, "predicates", types, "image", imageInfo.String()) - return fmt.Errorf("attestions not found for predicate type %s", attestation.PredicateType) + iv.logger.Info("no attestations found for predicate", "type", attestation.Type, "predicates", types, "image", imageInfo.String()) + return fmt.Errorf("attestions not found for predicate type %s", attestation.Type) } for _, s := range statements { iv.logger.Info("checking attestation", "predicates", types, "image", imageInfo.String()) @@ -544,7 +578,7 @@ func (iv *ImageVerifier) verifyAttestation(statements []map[string]interface{}, return fmt.Errorf("failed to check attestations: %w", err) } if !val { - return fmt.Errorf("attestation checks failed for %s and predicate %s: %s", imageInfo.String(), attestation.PredicateType, msg) + return fmt.Errorf("attestation checks failed for %s and predicate %s: %s", imageInfo.String(), attestation.Type, msg) } } return nil diff --git a/pkg/images/verifier.go b/pkg/images/verifier.go index def5d9ecc1..51874e1c36 100644 --- a/pkg/images/verifier.go +++ b/pkg/images/verifier.go @@ -30,6 +30,7 @@ type Options struct { RekorURL string SignatureAlgorithm string PredicateType string + Type string Identities string } diff --git a/pkg/notary/notary.go b/pkg/notary/notary.go new file mode 100644 index 0000000000..993434ef75 --- /dev/null +++ b/pkg/notary/notary.go @@ -0,0 +1,348 @@ +package notary + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + + "github.com/go-logr/logr" + "github.com/google/go-containerregistry/pkg/crane" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/kyverno/kyverno/pkg/images" + "github.com/kyverno/kyverno/pkg/logging" + _ "github.com/notaryproject/notation-core-go/signature/cose" + _ "github.com/notaryproject/notation-core-go/signature/jws" + "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation-go/verifier" + "github.com/notaryproject/notation-go/verifier/trustpolicy" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" + "github.com/sigstore/sigstore/pkg/cryptoutils" + "go.uber.org/multierr" +) + +func NewVerifier() images.ImageVerifier { + return ¬aryVerifier{ + log: logging.WithName("Notary"), + } +} + +type notaryVerifier struct { + log logr.Logger +} + +func (v *notaryVerifier) VerifySignature(ctx context.Context, opts images.Options) (*images.Response, error) { + v.log.V(2).Info("verifying image", "reference", opts.ImageRef) + + certsPEM := combineCerts(opts) + certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader([]byte(certsPEM))) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse certificates") + } + + trustStore := NewTrustStore("kyverno", certs) + policyDoc := v.buildPolicy() + notationVerifier, err := verifier.New(policyDoc, trustStore, nil) + if err != nil { + return nil, errors.Wrapf(err, "failed to created verifier") + } + + v.log.V(4).Info("creating notation repo", "reference", opts.ImageRef) + parsedRef, err := parseReferenceCrane(ctx, opts.ImageRef, opts.RegistryClient) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse image reference: %s", opts.ImageRef) + } + v.log.V(4).Info("created parsedRef", "reference", opts.ImageRef) + + ref := parsedRef.Ref.Name() + remoteVerifyOptions := notation.RemoteVerifyOptions{ + ArtifactReference: ref, + MaxSignatureAttempts: 10, + } + + targetDesc, outcomes, err := notation.Verify(context.TODO(), notationVerifier, parsedRef.Repo, remoteVerifyOptions) + if err != nil { + return nil, errors.Wrapf(err, "failed to verify %s", ref) + } + + if err := v.verifyOutcomes(outcomes); err != nil { + return nil, err + } + + v.log.V(2).Info("verified image", "type", targetDesc.MediaType, "digest", targetDesc.Digest, "size", targetDesc.Size) + + resp := &images.Response{ + Digest: targetDesc.Digest.String(), + Statements: nil, + } + + return resp, nil +} + +func combineCerts(opts images.Options) string { + certs := opts.Cert + if opts.CertChain != "" { + if certs != "" { + certs = certs + "\n" + } + + certs = certs + opts.CertChain + } + + return certs +} + +func (v *notaryVerifier) buildPolicy() *trustpolicy.Document { + return &trustpolicy.Document{ + Version: "1.0", + TrustPolicies: []trustpolicy.TrustPolicy{ + { + Name: "kyverno", + RegistryScopes: []string{"*"}, + SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: trustpolicy.LevelStrict.Name}, + TrustStores: []string{"ca:kyverno"}, + TrustedIdentities: []string{"*"}, + }, + }, + } +} + +func (v *notaryVerifier) verifyOutcomes(outcomes []*notation.VerificationOutcome) error { + var errs []error + for _, outcome := range outcomes { + if outcome.Error != nil { + errs = append(errs, outcome.Error) + continue + } + + content := outcome.EnvelopeContent.Payload.Content + contentType := outcome.EnvelopeContent.Payload.ContentType + + v.log.V(2).Info("content", "type", contentType, "data", content) + } + + return multierr.Combine(errs...) +} + +func (v *notaryVerifier) FetchAttestations(ctx context.Context, opts images.Options) (*images.Response, error) { + v.log.V(2).Info("fetching attestations", "reference", opts.ImageRef, "opts", opts) + + ref, err := name.ParseReference(opts.ImageRef) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse image reference: %s", opts.ImageRef) + } + authenticator, err := getAuthenticator(ctx, opts.ImageRef, opts.RegistryClient) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse authenticator: %s", opts.ImageRef) + } + craneOpts := crane.WithAuth(*authenticator) + + remoteOpts, err := getRemoteOpts(*authenticator) + if err != nil { + return nil, err + } + + v.log.V(4).Info("client setup done", "repo", ref) + + repoDesc, err := crane.Head(opts.ImageRef, craneOpts) + if err != nil { + return nil, err + } + v.log.V(4).Info("fetched repository", "repoDesc", repoDesc) + + referrers, err := remote.Referrers(ref.Context().Digest(repoDesc.Digest.String()), remoteOpts...) + if err != nil { + return nil, err + } + referrersDescs, err := referrers.IndexManifest() + if err != nil { + return nil, err + } + + v.log.V(4).Info("fetched referrers", "referrers", referrersDescs) + + var statements []map[string]interface{} + + for _, referrer := range referrersDescs.Manifests { + match, _, err := matchArtifactType(referrer, opts.Type) + if err != nil { + return nil, err + } + + if !match { + v.log.V(6).Info("type doesn't match, continue", "expected", opts.Type, "received", referrer.ArtifactType) + continue + } + + targetDesc, err := verifyAttestators(ctx, v, ref, opts, referrer) + if err != nil { + msg := err.Error() + v.log.V(4).Info(msg, "failed to verify referrer %s", targetDesc.Digest.String()) + return nil, err + } + + v.log.V(4).Info("extracting statements", "desc", referrer, "repo", ref) + statements, err = extractStatements(ctx, ref, referrer, craneOpts) + if err != nil { + msg := err.Error() + v.log.V(4).Info("failed to extract statements %s", "err", msg) + return nil, err + } + + v.log.V(4).Info("verified attestators", "digest", targetDesc.Digest.String()) + + if len(statements) == 0 { + return nil, fmt.Errorf("failed to fetch attestations") + } + v.log.V(6).Info("sending response") + return &images.Response{Digest: repoDesc.Digest.String(), Statements: statements}, nil + } + + return nil, fmt.Errorf("failed to fetch attestations %s", err) +} + +func verifyAttestators(ctx context.Context, v *notaryVerifier, ref name.Reference, opts images.Options, desc v1.Descriptor) (ocispec.Descriptor, error) { + v.log.V(2).Info("verifying attestations", "reference", opts.ImageRef, "opts", opts) + if opts.Cert == "" && opts.CertChain == "" { + // skips the checks when no attestor is provided + v1Desc := ocispec.Descriptor{ + MediaType: string(desc.MediaType), + Size: desc.Size, + Digest: digest.Digest(desc.Digest.String()), + URLs: desc.URLs, + Annotations: desc.Annotations, + Data: desc.Data, + } + return v1Desc, nil + } + certsPEM := combineCerts(opts) + certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader([]byte(certsPEM))) + if err != nil { + v.log.V(4).Info("failed to parse certificates", "err", err) + return ocispec.Descriptor{}, errors.Wrapf(err, "failed to parse certificates") + } + + v.log.V(4).Info("parsed certificates") + trustStore := NewTrustStore("kyverno", certs) + policyDoc := v.buildPolicy() + notationVerifier, err := verifier.New(policyDoc, trustStore, nil) + if err != nil { + v.log.V(4).Info("failed to created verifier", "err", err) + return ocispec.Descriptor{}, errors.Wrapf(err, "failed to created verifier") + } + + v.log.V(4).Info("created verifier") + reference := ref.Context().RegistryStr() + "/" + ref.Context().RepositoryStr() + "@" + desc.Digest.String() + parsedRef, err := parseReferenceCrane(ctx, reference, opts.RegistryClient) + if err != nil { + return ocispec.Descriptor{}, errors.Wrapf(err, "failed to parse image reference: %s", opts.ImageRef) + } + v.log.V(4).Info("created notation repo", "reference", opts.ImageRef) + + remoteVerifyOptions := notation.RemoteVerifyOptions{ + ArtifactReference: reference, + MaxSignatureAttempts: 10, + } + + v.log.V(4).Info("verification started") + targetDesc, outcomes, err := notation.Verify(context.TODO(), notationVerifier, parsedRef.Repo, remoteVerifyOptions) + if err != nil { + v.log.V(4).Info("failed to vefify attestator", "remoteVerifyOptions", remoteVerifyOptions, "repo", parsedRef.Repo) + return targetDesc, err + } + if err := v.verifyOutcomes(outcomes); err != nil { + return targetDesc, err + } + + if targetDesc.Digest.String() != desc.Digest.String() { + v.log.V(4).Info("digest mismatch", "expected", desc.Digest.String(), "found", targetDesc.Digest.String()) + return targetDesc, errors.Errorf("digest mismatch") + } + v.log.V(2).Info("attestator verified", "desc", targetDesc.Digest.String()) + + return targetDesc, nil +} + +func extractStatements(ctx context.Context, repoRef name.Reference, desc v1.Descriptor, craneOpts ...crane.Option) ([]map[string]interface{}, error) { + statements := make([]map[string]interface{}, 0) + data, err := extractStatement(ctx, repoRef, desc, craneOpts...) + if err != nil { + return nil, err + } + statements = append(statements, data) + + if len(statements) == 0 { + return nil, fmt.Errorf("no statements found") + } + return statements, nil +} + +func extractStatement(ctx context.Context, repoRef name.Reference, desc v1.Descriptor, craneOpts ...crane.Option) (map[string]interface{}, error) { + refStr := repoRef.Context().RegistryStr() + "/" + repoRef.Context().RepositoryStr() + "@" + desc.Digest.String() + ref, err := name.ParseReference(refStr) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse image reference: %s", refStr) + } + + manifestBytes, err := crane.Manifest(refStr, craneOpts...) + if err != nil { + return nil, fmt.Errorf("error in fetching statement: %w", err) + } + var manifest ocispec.Manifest + if err := json.Unmarshal(manifestBytes, &manifest); err != nil { + return nil, err + } + + if len(manifest.Layers) == 0 { + return nil, fmt.Errorf("no predicate found: %+v", manifest) + } + if len(manifest.Layers) > 1 { + return nil, fmt.Errorf("multiple layers in predicate not supported: %+v", manifest) + } + predicateDesc := manifest.Layers[0] + predicateRef := ref.Context().RegistryStr() + "/" + ref.Context().RepositoryStr() + "@" + predicateDesc.Digest.String() + + layer, err := crane.PullLayer(predicateRef, craneOpts...) + if err != nil { + return nil, err + } + ioPredicate, err := layer.Uncompressed() + if err != nil { + return nil, err + } + predicateBytes := new(bytes.Buffer) + _, err = predicateBytes.ReadFrom(ioPredicate) + if err != nil { + return nil, err + } + + predicate := make(map[string]interface{}) + if err := json.Unmarshal(predicateBytes.Bytes(), &predicate); err != nil { + return nil, err + } + data := make(map[string]interface{}) + if err := json.Unmarshal(manifestBytes, &data); err != nil { + return nil, err + } + + if data["type"] == nil { + data["type"] = desc.ArtifactType + } + if data["predicate"] == nil { + data["predicate"] = predicate + } + return data, nil +} + +func matchArtifactType(ref v1.Descriptor, expectedArtifactType string) (bool, string, error) { + if expectedArtifactType != "" { + if ref.ArtifactType == expectedArtifactType { + return true, ref.ArtifactType, nil + } + } + return false, "", nil +} diff --git a/pkg/notary/notary_test.go b/pkg/notary/notary_test.go new file mode 100644 index 0000000000..dc16eea92e --- /dev/null +++ b/pkg/notary/notary_test.go @@ -0,0 +1,33 @@ +package notary + +import ( + "context" + "testing" + + "github.com/google/go-containerregistry/pkg/crane" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" + "gotest.tools/assert" +) + +func TestExtractStatements(t *testing.T) { + imageRef := "jimnotarytest.azurecr.io/jim/net-monitor:v1" + ref, err := name.ParseReference(imageRef) + assert.NilError(t, err) + repoDesc, err := crane.Head(imageRef) + assert.NilError(t, err) + referrers, err := remote.Referrers(ref.Context().Digest(repoDesc.Digest.String())) + assert.NilError(t, err) + referrersDescs, err := referrers.IndexManifest() + assert.NilError(t, err) + + for _, referrer := range referrersDescs.Manifests { + if referrer.ArtifactType == "application/vnd.cncf.notary.signature" { + statements, err := extractStatements(context.Background(), ref, referrer) + assert.NilError(t, err) + assert.Assert(t, len(statements) == 1) + assert.Assert(t, statements[0]["type"] == referrer.ArtifactType) + assert.Assert(t, statements[0]["mediaType"] == string(referrer.MediaType)) + } + } +} diff --git a/pkg/notary/registry.go b/pkg/notary/registry.go new file mode 100644 index 0000000000..6c0227dc10 --- /dev/null +++ b/pkg/notary/registry.go @@ -0,0 +1,133 @@ +package notary + +import ( + "context" + "strings" + + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/crane" + "github.com/google/go-containerregistry/pkg/name" + gcrremote "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/kyverno/kyverno/pkg/registryclient" + notationregistry "github.com/notaryproject/notation-go/registry" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +type parsedReference struct { + Repo notationregistry.Repository + CraneOpts crane.Option + RemoteOpts []gcrremote.Option + Ref name.Reference + Desc ocispec.Descriptor +} + +func parseReferenceCrane(ctx context.Context, ref string, registryClient registryclient.Client) (*parsedReference, error) { + nameRef, err := name.ParseReference(ref) + if err != nil { + return nil, err + } + + authenticator, err := getAuthenticator(ctx, ref, registryClient) + if err != nil { + return nil, err + } + + craneOpts := crane.WithAuth(*authenticator) + remoteOpts, err := getRemoteOpts(*authenticator) + if err != nil { + return nil, err + } + + desc, err := crane.Head(ref, craneOpts) + if err != nil { + return nil, err + } + + if !isDigestReference(ref) { + nameRef, err = name.ParseReference(GetReferenceFromDescriptor(v1ToOciSpecDescriptor(*desc), nameRef)) + if err != nil { + return nil, err + } + } + + repository := NewRepository(craneOpts, remoteOpts, nameRef) + err = resolveDigestCrane(repository, craneOpts, remoteOpts, nameRef) + if err != nil { + return nil, errors.Wrapf(err, "failed to resolve digest") + } + + return &parsedReference{ + Repo: repository, + CraneOpts: craneOpts, + RemoteOpts: remoteOpts, + Ref: nameRef, + Desc: v1ToOciSpecDescriptor(*desc), + }, nil +} + +type imageResource struct { + ref name.Reference +} + +func (ir *imageResource) String() string { + return ir.ref.Name() +} + +func (ir *imageResource) RegistryStr() string { + return ir.ref.Context().RegistryStr() +} + +func getAuthenticator(ctx context.Context, ref string, registryClient registryclient.Client) (*authn.Authenticator, error) { + parsedRef, err := name.ParseReference(ref) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse registry reference %s", ref) + } + + if err := registryClient.RefreshKeychainPullSecrets(ctx); err != nil { + return nil, errors.Wrapf(err, "failed to refresh image pull secrets") + } + + authn, err := registryClient.Keychain().Resolve(&imageResource{parsedRef}) + if err != nil { + return nil, errors.Wrapf(err, "failed to resolve auth for %s", parsedRef.String()) + } + return &authn, nil +} + +func isDigestReference(reference string) bool { + parts := strings.SplitN(reference, "/", 2) + if len(parts) == 1 { + return false + } + + index := strings.Index(parts[1], "@") + return index != -1 +} + +func getRemoteOpts(authenticator authn.Authenticator) ([]gcrremote.Option, error) { + remoteOpts := []gcrremote.Option{} + remoteOpts = append(remoteOpts, gcrremote.WithAuth(authenticator)) + + pusher, err := gcrremote.NewPusher(remoteOpts...) + if err != nil { + return nil, err + } + remoteOpts = append(remoteOpts, gcrremote.Reuse(pusher)) + + puller, err := gcrremote.NewPuller(remoteOpts...) + if err != nil { + return nil, err + } + remoteOpts = append(remoteOpts, gcrremote.Reuse(puller)) + + return remoteOpts, nil +} + +func resolveDigestCrane(repo notationregistry.Repository, craneOpts crane.Option, remoteOpts []gcrremote.Option, ref name.Reference) error { + _, err := repo.Resolve(context.Background(), ref.Name()) + if err != nil { + return err + } + return nil +} diff --git a/pkg/notary/repository.go b/pkg/notary/repository.go new file mode 100644 index 0000000000..f8115f29d1 --- /dev/null +++ b/pkg/notary/repository.go @@ -0,0 +1,127 @@ +package notary + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + + "github.com/google/go-containerregistry/pkg/crane" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" + notationregistry "github.com/notaryproject/notation-go/registry" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +type repositoryClient struct { + ref name.Reference + craneOpts crane.Option + remoteOpts []remote.Option +} + +func NewRepository(craneOpts crane.Option, remoteOpts []remote.Option, ref name.Reference) notationregistry.Repository { + return &repositoryClient{ + craneOpts: craneOpts, + remoteOpts: remoteOpts, + ref: ref, + } +} + +func (c *repositoryClient) Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) { + head, err := crane.Head(reference) + if err != nil { + return ocispec.Descriptor{}, nil + } + descriptor := v1ToOciSpecDescriptor(*head) + return descriptor, nil +} + +func (c *repositoryClient) ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error { + referrers, err := remote.Referrers(c.ref.Context().Digest(desc.Digest.String()), c.remoteOpts...) + if err != nil { + return err + } + + referrersDescs, err := referrers.IndexManifest() + if err != nil { + return err + } + + descList := []ocispec.Descriptor{} + for _, d := range referrersDescs.Manifests { + if d.ArtifactType == notationregistry.ArtifactTypeNotation { + descList = append(descList, v1ToOciSpecDescriptor(d)) + } + } + + return fn(descList) +} + +func (c *repositoryClient) FetchSignatureBlob(ctx context.Context, desc ocispec.Descriptor) ([]byte, ocispec.Descriptor, error) { + manifestRef := c.getReferenceFromDescriptor(desc) + + manifestBytes, err := crane.Manifest(manifestRef) + if err != nil { + return nil, ocispec.Descriptor{}, err + } + + var manifest ocispec.Manifest + if err := json.Unmarshal(manifestBytes, &manifest); err != nil { + return nil, ocispec.Descriptor{}, err + } + manifestDesc := manifest.Layers[0] + + signatureBlobRef := c.getReferenceFromDescriptor(manifestDesc) + + signatureBlobLayer, err := crane.PullLayer(signatureBlobRef) + if err != nil { + panic(err) + } + + io, err := signatureBlobLayer.Uncompressed() + if err != nil { + panic(err) + } + SigBlobBuf := new(bytes.Buffer) + + _, err = SigBlobBuf.ReadFrom(io) + if err != nil { + panic(err) + } + return SigBlobBuf.Bytes(), manifestDesc, nil +} + +func (c *repositoryClient) PushSignature(ctx context.Context, mediaType string, blob []byte, subject ocispec.Descriptor, annotations map[string]string) (blobDesc, manifestDesc ocispec.Descriptor, err error) { + return ocispec.Descriptor{}, ocispec.Descriptor{}, fmt.Errorf("push signature is not implemented") +} + +func v1ToOciSpecDescriptor(v1desc v1.Descriptor) ocispec.Descriptor { + ociDesc := ocispec.Descriptor{ + MediaType: string(v1desc.MediaType), + Digest: digest.Digest(v1desc.Digest.String()), + Size: v1desc.Size, + URLs: v1desc.URLs, + Annotations: v1desc.Annotations, + Data: v1desc.Data, + + ArtifactType: v1desc.ArtifactType, + } + if v1desc.Platform != nil { + ociDesc.Platform = &ocispec.Platform{ + Architecture: v1desc.Platform.Architecture, + OS: v1desc.Platform.OS, + OSVersion: v1desc.Platform.OSVersion, + } + } + return ociDesc +} + +func (c *repositoryClient) getReferenceFromDescriptor(desc ocispec.Descriptor) string { + return GetReferenceFromDescriptor(desc, c.ref) +} + +func GetReferenceFromDescriptor(desc ocispec.Descriptor, ref name.Reference) string { + return ref.Context().RegistryStr() + "/" + ref.Context().RepositoryStr() + "@" + desc.Digest.String() +} diff --git a/pkg/notaryv2/truststore.go b/pkg/notary/truststore.go similarity index 97% rename from pkg/notaryv2/truststore.go rename to pkg/notary/truststore.go index 899d70574e..a5c561822a 100644 --- a/pkg/notaryv2/truststore.go +++ b/pkg/notary/truststore.go @@ -1,4 +1,4 @@ -package notaryv2 +package notary import ( "context" diff --git a/pkg/notaryv2/notaryv2.go b/pkg/notaryv2/notaryv2.go deleted file mode 100644 index 414efdd172..0000000000 --- a/pkg/notaryv2/notaryv2.go +++ /dev/null @@ -1,132 +0,0 @@ -package notaryv2 - -import ( - "bytes" - "context" - - "github.com/go-logr/logr" - "github.com/kyverno/kyverno/pkg/images" - "github.com/kyverno/kyverno/pkg/logging" - _ "github.com/notaryproject/notation-core-go/signature/cose" - _ "github.com/notaryproject/notation-core-go/signature/jws" - "github.com/notaryproject/notation-go" - "github.com/notaryproject/notation-go/verifier" - "github.com/notaryproject/notation-go/verifier/trustpolicy" - "github.com/pkg/errors" - "github.com/sigstore/sigstore/pkg/cryptoutils" - "go.uber.org/multierr" -) - -func NewVerifier() images.ImageVerifier { - return ¬aryV2Verifier{ - log: logging.WithName("NotaryV2"), - } -} - -type notaryV2Verifier struct { - log logr.Logger -} - -func (v *notaryV2Verifier) VerifySignature(ctx context.Context, opts images.Options) (*images.Response, error) { - v.log.V(2).Info("verifying image", "reference", opts.ImageRef) - - certsPEM := combineCerts(opts) - certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader([]byte(certsPEM))) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse certificates") - } - - trustStore := NewTrustStore("kyverno", certs) - policyDoc := v.buildPolicy() - notationVerifier, err := verifier.New(policyDoc, trustStore, nil) - if err != nil { - return nil, errors.Wrapf(err, "failed to created verifier") - } - - repo, parsedRef, err := parseReference(ctx, opts.ImageRef, opts.RegistryClient) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse image reference: %s", opts.ImageRef) - } - - digest, err := parsedRef.Digest() - if err != nil { - return nil, errors.Wrapf(err, "failed to fetch digest") - } - - ref := parsedRef.String() - remoteVerifyOptions := notation.RemoteVerifyOptions{ - ArtifactReference: ref, - MaxSignatureAttempts: 10, - } - - targetDesc, outcomes, err := notation.Verify(context.TODO(), notationVerifier, repo, remoteVerifyOptions) - if err != nil { - return nil, errors.Wrapf(err, "failed to verify %s", ref) - } - - if err := v.verifyOutcomes(outcomes); err != nil { - return nil, err - } - - if targetDesc.Digest != digest { - return nil, errors.Errorf("digest mismatch") - } - - v.log.V(2).Info("verified image", "type", targetDesc.MediaType, "digest", targetDesc.Digest, "size", targetDesc.Size) - - resp := &images.Response{ - Digest: targetDesc.Digest.String(), - Statements: nil, - } - - return resp, nil -} - -func combineCerts(opts images.Options) string { - certs := opts.Cert - if opts.CertChain != "" { - if certs != "" { - certs = certs + "\n" - } - - certs = certs + opts.CertChain - } - - return certs -} - -func (v *notaryV2Verifier) buildPolicy() *trustpolicy.Document { - return &trustpolicy.Document{ - Version: "1.0", - TrustPolicies: []trustpolicy.TrustPolicy{ - { - Name: "kyverno", - RegistryScopes: []string{"*"}, - SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: trustpolicy.LevelStrict.Name}, - TrustStores: []string{"ca:kyverno"}, - TrustedIdentities: []string{"*"}, - }, - }, - } -} - -func (v *notaryV2Verifier) verifyOutcomes(outcomes []*notation.VerificationOutcome) error { - var errs []error - for _, outcome := range outcomes { - if outcome.Error != nil { - errs = append(errs, outcome.Error) - continue - } - - content := outcome.EnvelopeContent.Payload.Content - contentType := outcome.EnvelopeContent.Payload.ContentType - - v.log.Info("content", "type", contentType, "data", content) - } - - return multierr.Combine(errs...) -} - -func (v *notaryV2Verifier) FetchAttestations(ctx context.Context, opts images.Options) (*images.Response, error) { - return nil, errors.Errorf("not implemented") -} diff --git a/pkg/notaryv2/registry.go b/pkg/notaryv2/registry.go deleted file mode 100644 index 71e63b94ac..0000000000 --- a/pkg/notaryv2/registry.go +++ /dev/null @@ -1,125 +0,0 @@ -package notaryv2 - -import ( - "context" - "strings" - - "github.com/kyverno/kyverno/pkg/registryclient" - notationregistry "github.com/notaryproject/notation-go/registry" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" - "oras.land/oras-go/v2/registry" - "oras.land/oras-go/v2/registry/remote" - "oras.land/oras-go/v2/registry/remote/auth" -) - -func parseReference(ctx context.Context, ref string, registryClient registryclient.Client) (notationregistry.Repository, registry.Reference, error) { - parsedRef, err := registry.ParseReference(ref) - if err != nil { - return nil, registry.Reference{}, errors.Wrapf(err, "failed to parse registry reference %s", ref) - } - - authClient, plainHTTP, err := getAuthClient(ctx, parsedRef, registryClient) - if err != nil { - return nil, registry.Reference{}, err - } - - repo, err := remote.NewRepository(ref) - if err != nil { - return nil, registry.Reference{}, errors.Wrapf(err, "failed to initialize repository") - } - - repo.PlainHTTP = plainHTTP - repo.Client = authClient - repository := notationregistry.NewRepository(repo) - - parsedRef, err = resolveDigest(repository, parsedRef) - if err != nil { - return nil, registry.Reference{}, errors.Wrapf(err, "failed to resolve digest") - } - - return repository, parsedRef, nil -} - -type imageResource struct { - ref registry.Reference -} - -func (ir *imageResource) String() string { - return ir.ref.String() -} - -func (ir *imageResource) RegistryStr() string { - return ir.ref.Registry -} - -func getAuthClient(ctx context.Context, ref registry.Reference, rc registryclient.Client) (*auth.Client, bool, error) { - if err := rc.RefreshKeychainPullSecrets(ctx); err != nil { - return nil, false, errors.Wrapf(err, "failed to refresh image pull secrets") - } - - authn, err := rc.Keychain().Resolve(&imageResource{ref}) - if err != nil { - return nil, false, errors.Wrapf(err, "failed to resolve auth for %s", ref.String()) - } - - authConfig, err := authn.Authorization() - if err != nil { - return nil, false, errors.Wrapf(err, "failed to get auth config for %s", ref.String()) - } - - credentials := auth.Credential{ - Username: authConfig.Username, - Password: authConfig.Password, - AccessToken: authConfig.IdentityToken, - RefreshToken: authConfig.RegistryToken, - } - - authClient := &auth.Client{ - Credential: func(ctx context.Context, registry string) (auth.Credential, error) { - switch registry { - default: - return credentials, nil - } - }, - Cache: auth.NewCache(), - ClientID: "notation", - } - - authClient.SetUserAgent("kyverno.io") - return authClient, false, nil -} - -func resolveDigest(repo notationregistry.Repository, ref registry.Reference) (registry.Reference, error) { - if isDigestReference(ref.String()) { - return ref, nil - } - - // Resolve tag reference to digest reference. - manifestDesc, err := getManifestDescriptorFromReference(repo, ref.String()) - if err != nil { - return registry.Reference{}, err - } - - ref.Reference = manifestDesc.Digest.String() - return ref, nil -} - -func isDigestReference(reference string) bool { - parts := strings.SplitN(reference, "/", 2) - if len(parts) == 1 { - return false - } - - index := strings.Index(parts[1], "@") - return index != -1 -} - -func getManifestDescriptorFromReference(repo notationregistry.Repository, reference string) (ocispec.Descriptor, error) { - ref, err := registry.ParseReference(reference) - if err != nil { - return ocispec.Descriptor{}, err - } - - return repo.Resolve(context.Background(), ref.ReferenceOrDefault()) -} diff --git a/pkg/validation/policy/validate.go b/pkg/validation/policy/validate.go index f7504198d7..0159c1290f 100644 --- a/pkg/validation/policy/validate.go +++ b/pkg/validation/policy/validate.go @@ -364,6 +364,10 @@ func Validate(policy, oldPolicy kyvernov1.PolicyInterface, client dclient.Interf checkForScaleSubresource(mutationJson, allKinds, &warnings) checkForStatusSubresource(mutationJson, allKinds, &warnings) } + + if rule.HasVerifyImages() { + checkForDeprecatedFieldsInVerifyImages(rule, &warnings) + } } if !mock && (spec.SchemaValidation == nil || *spec.SchemaValidation) { if err := openApiManager.ValidatePolicyMutation(policy); err != nil { @@ -1301,3 +1305,14 @@ func checkForStatusSubresource(ruleTypeJson []byte, allKinds []string, warnings *warnings = append(*warnings, msg) } } + +func checkForDeprecatedFieldsInVerifyImages(rule kyvernov1.Rule, warnings *[]string) { + for _, imageVerify := range rule.VerifyImages { + for _, attestation := range imageVerify.Attestations { + if attestation.PredicateType != "" { + msg := fmt.Sprintf("predicateType has been deprecated use 'type: %s' instead of 'prediacteType: %s'", attestation.PredicateType, attestation.PredicateType) + *warnings = append(*warnings, msg) + } + } + } +} diff --git a/test/conformance/kuttl/flags/standard/emit-events/admission-controller-assert.yaml b/test/conformance/kuttl/flags/standard/emit-events/admission-controller-assert.yaml index 4ed694ab26..c12a6220a3 100644 --- a/test/conformance/kuttl/flags/standard/emit-events/admission-controller-assert.yaml +++ b/test/conformance/kuttl/flags/standard/emit-events/admission-controller-assert.yaml @@ -1,9 +1,8 @@ -apiVersion: kyverno.io/v1 -kind: Policy +apiVersion: apps/v1 +kind: Deployment metadata: name: kyverno-admission-controller + namespace: kyverno status: - conditions: - - reason: Succeeded - status: "True" - type: Ready + readyReplicas: 1 + updatedReplicas: 1 \ No newline at end of file diff --git a/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/01-policy.yaml b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/01-policy.yaml new file mode 100644 index 0000000000..f3857739b0 --- /dev/null +++ b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/01-policy.yaml @@ -0,0 +1,6 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +apply: +- policy.yaml +assert: +- policy-ready.yaml \ No newline at end of file diff --git a/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/02-resource.yaml b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/02-resource.yaml new file mode 100644 index 0000000000..52ffd92005 --- /dev/null +++ b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/02-resource.yaml @@ -0,0 +1,6 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +apply: +- pod.yaml +assert: +- pod-assert.yaml \ No newline at end of file diff --git a/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/README.md b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/README.md new file mode 100644 index 0000000000..b246475eda --- /dev/null +++ b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/README.md @@ -0,0 +1,12 @@ +## Description + +This test verifies image attestations using notary signatures + +## Expected Behavior + +This test creates a cluster policy. +When a pod is created with the image reference and the signature on attestations matches, the pod creation is successful + +## Reference Issue(s) + +6142 diff --git a/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/pod-assert.yaml b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/pod-assert.yaml new file mode 100644 index 0000000000..d18a0a10e9 --- /dev/null +++ b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/pod-assert.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: Pod +metadata: + name: test + namespace: notary-verify-attestation \ No newline at end of file diff --git a/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/pod.yaml b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/pod.yaml new file mode 100644 index 0000000000..e16637872d --- /dev/null +++ b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: null + labels: + run: test + name: test + namespace: notary-verify-attestation +spec: + containers: + - image: ghcr.io/kyverno/test-verify-image:signed + name: test + resources: {} + dnsPolicy: ClusterFirst + restartPolicy: Always +status: {} \ No newline at end of file diff --git a/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/policy-ready.yaml b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/policy-ready.yaml new file mode 100644 index 0000000000..83c51e7057 --- /dev/null +++ b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/policy-ready.yaml @@ -0,0 +1,9 @@ +apiVersion: kyverno.io/v2beta1 +kind: ClusterPolicy +metadata: + name: check-image-attestation +status: + conditions: + - reason: Succeeded + status: "True" + type: Ready \ No newline at end of file diff --git a/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/policy.yaml b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/policy.yaml new file mode 100644 index 0000000000..25245849de --- /dev/null +++ b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-attestation-verification/policy.yaml @@ -0,0 +1,63 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: notary-verify-attestation +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: keys + namespace: notary-verify-attestation +data: + certificate: |- + -----BEGIN CERTIFICATE----- + MIIDTTCCAjWgAwIBAgIJAPI+zAzn4s0xMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV + BAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEPMA0GA1UECgwG + Tm90YXJ5MQ0wCwYDVQQDDAR0ZXN0MB4XDTIzMDUyMjIxMTUxOFoXDTMzMDUxOTIx + MTUxOFowTDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0 + dGxlMQ8wDQYDVQQKDAZOb3RhcnkxDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3 + DQEBAQUAA4IBDwAwggEKAoIBAQDNhTwv+QMk7jEHufFfIFlBjn2NiJaYPgL4eBS+ + b+o37ve5Zn9nzRppV6kGsa161r9s2KkLXmJrojNy6vo9a6g6RtZ3F6xKiWLUmbAL + hVTCfYw/2n7xNlVMjyyUpE+7e193PF8HfQrfDFxe2JnX5LHtGe+X9vdvo2l41R6m + Iia04DvpMdG4+da2tKPzXIuLUz/FDb6IODO3+qsqQLwEKmmUee+KX+3yw8I6G1y0 + Vp0mnHfsfutlHeG8gazCDlzEsuD4QJ9BKeRf2Vrb0ywqNLkGCbcCWF2H5Q80Iq/f + ETVO9z88R7WheVdEjUB8UrY7ZMLdADM14IPhY2Y+tLaSzEVZAgMBAAGjMjAwMAkG + A1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0G + CSqGSIb3DQEBCwUAA4IBAQBX7x4Ucre8AIUmXZ5PUK/zUBVOrZZzR1YE8w86J4X9 + kYeTtlijf9i2LTZMfGuG0dEVFN4ae3CCpBst+ilhIndnoxTyzP+sNy4RCRQ2Y/k8 + Zq235KIh7uucq96PL0qsF9s2RpTKXxyOGdtp9+HO0Ty5txJE2txtLDUIVPK5WNDF + ByCEQNhtHgN6V20b8KU2oLBZ9vyB8V010dQz0NRTDLhkcvJig00535/LUylECYAJ + 5/jn6XKt6UYCQJbVNzBg/YPGc1RF4xdsGVDBben/JXpeGEmkdmXPILTKd9tZ5TC0 + uOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz + -----END CERTIFICATE----- +--- +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: check-image-attestation +spec: + validationFailureAction: Enforce + webhookTimeoutSeconds: 30 + failurePolicy: Fail + rules: + - name: verify-attestation-notary + match: + any: + - resources: + kinds: + - Pod + context: + - name: keys + configMap: + name: keys + namespace: notary-verify-attestation + verifyImages: + - type: Notary + imageReferences: + - "ghcr.io/kyverno/test-verify-image*" + attestations: + - type: sbom/cyclone-dx + attestors: + - entries: + - certificates: + cert: "{{ keys.data.certificate }}" \ No newline at end of file diff --git a/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/01-policy.yaml b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/01-policy.yaml new file mode 100644 index 0000000000..f3857739b0 --- /dev/null +++ b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/01-policy.yaml @@ -0,0 +1,6 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +apply: +- policy.yaml +assert: +- policy-ready.yaml \ No newline at end of file diff --git a/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/02-resource.yaml b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/02-resource.yaml new file mode 100644 index 0000000000..52ffd92005 --- /dev/null +++ b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/02-resource.yaml @@ -0,0 +1,6 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +apply: +- pod.yaml +assert: +- pod-assert.yaml \ No newline at end of file diff --git a/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/README.md b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/README.md new file mode 100644 index 0000000000..a87ff91a4a --- /dev/null +++ b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/README.md @@ -0,0 +1,12 @@ +## Description + +This test verifies images using notary signatures + +## Expected Behavior + +This test creates a cluster policy. +When a pod is created with the image reference and the signature matches, the pod creation is successful + +## Reference Issue(s) + +6142 diff --git a/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/pod-assert.yaml b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/pod-assert.yaml new file mode 100644 index 0000000000..4bf9852a3c --- /dev/null +++ b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/pod-assert.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: Pod +metadata: + name: test + namespace: notary-verify-images \ No newline at end of file diff --git a/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/pod.yaml b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/pod.yaml new file mode 100644 index 0000000000..b5ab8f7959 --- /dev/null +++ b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: null + labels: + run: test + name: test + namespace: notary-verify-images +spec: + containers: + - image: ghcr.io/kyverno/test-verify-image:signed + name: test + resources: {} + dnsPolicy: ClusterFirst + restartPolicy: Always +status: {} \ No newline at end of file diff --git a/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/policy-ready.yaml b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/policy-ready.yaml new file mode 100644 index 0000000000..b3ad396d26 --- /dev/null +++ b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/policy-ready.yaml @@ -0,0 +1,9 @@ +apiVersion: kyverno.io/v2beta1 +kind: ClusterPolicy +metadata: + name: check-image-notary +status: + conditions: + - reason: Succeeded + status: "True" + type: Ready \ No newline at end of file diff --git a/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/policy.yaml b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/policy.yaml new file mode 100644 index 0000000000..05d6d6311c --- /dev/null +++ b/test/conformance/kuttl/verifyImages/clusterpolicy/standard/notary-image-verification/policy.yaml @@ -0,0 +1,62 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: notary-verify-images +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: keys + namespace: notary-verify-images +data: + certificate: |- + -----BEGIN CERTIFICATE----- + MIIDTTCCAjWgAwIBAgIJAPI+zAzn4s0xMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV + BAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEPMA0GA1UECgwG + Tm90YXJ5MQ0wCwYDVQQDDAR0ZXN0MB4XDTIzMDUyMjIxMTUxOFoXDTMzMDUxOTIx + MTUxOFowTDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0 + dGxlMQ8wDQYDVQQKDAZOb3RhcnkxDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3 + DQEBAQUAA4IBDwAwggEKAoIBAQDNhTwv+QMk7jEHufFfIFlBjn2NiJaYPgL4eBS+ + b+o37ve5Zn9nzRppV6kGsa161r9s2KkLXmJrojNy6vo9a6g6RtZ3F6xKiWLUmbAL + hVTCfYw/2n7xNlVMjyyUpE+7e193PF8HfQrfDFxe2JnX5LHtGe+X9vdvo2l41R6m + Iia04DvpMdG4+da2tKPzXIuLUz/FDb6IODO3+qsqQLwEKmmUee+KX+3yw8I6G1y0 + Vp0mnHfsfutlHeG8gazCDlzEsuD4QJ9BKeRf2Vrb0ywqNLkGCbcCWF2H5Q80Iq/f + ETVO9z88R7WheVdEjUB8UrY7ZMLdADM14IPhY2Y+tLaSzEVZAgMBAAGjMjAwMAkG + A1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0G + CSqGSIb3DQEBCwUAA4IBAQBX7x4Ucre8AIUmXZ5PUK/zUBVOrZZzR1YE8w86J4X9 + kYeTtlijf9i2LTZMfGuG0dEVFN4ae3CCpBst+ilhIndnoxTyzP+sNy4RCRQ2Y/k8 + Zq235KIh7uucq96PL0qsF9s2RpTKXxyOGdtp9+HO0Ty5txJE2txtLDUIVPK5WNDF + ByCEQNhtHgN6V20b8KU2oLBZ9vyB8V010dQz0NRTDLhkcvJig00535/LUylECYAJ + 5/jn6XKt6UYCQJbVNzBg/YPGc1RF4xdsGVDBben/JXpeGEmkdmXPILTKd9tZ5TC0 + uOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz + -----END CERTIFICATE----- +--- +apiVersion: kyverno.io/v2beta1 +kind: ClusterPolicy +metadata: + name: check-image-notary +spec: + validationFailureAction: Enforce + webhookTimeoutSeconds: 30 + failurePolicy: Fail + rules: + - name: verify-signature-notary + context: + - name: keys + configMap: + name: keys + namespace: notary-verify-images + match: + any: + - resources: + kinds: + - Pod + verifyImages: + - type: Notary + imageReferences: + - "ghcr.io/kyverno/test-verify-image*" + attestors: + - count: 1 + entries: + - certificates: + cert: "{{ keys.data.certificate }}" \ No newline at end of file