1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-28 02:18:15 +00:00
* fix make debug-deploy

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* improve log messages

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* initial update

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* initial update

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* update registry credentials handling order

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* comment out ACR helper - breaks anonymous image pull

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* merge main and refactor verifiers

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix opt init

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* remove local address

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* update to NotaryV2 RC

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix fmt

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* update deps

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* format imports

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* remove env and no-op statement

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix merge issues

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix linter issue

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* remove unused field

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* make fmt

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* renable ACR credential helper

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* Update .vscode/launch.json

Signed-off-by: shuting <shutting06@gmail.com>

---------

Signed-off-by: Jim Bugwadia <jim@nirmata.com>
Signed-off-by: shuting <shutting06@gmail.com>
Co-authored-by: shuting <shuting@nirmata.com>
Co-authored-by: shuting <shutting06@gmail.com>
This commit is contained in:
Jim Bugwadia 2023-02-20 08:26:10 -08:00 committed by GitHub
parent b76a73e7b6
commit 29997fe446
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 755 additions and 147 deletions

5
.vscode/launch.json vendored
View file

@ -9,7 +9,8 @@
"program": "${workspaceFolder}/cmd/kyverno",
"args": [
"--kubeconfig=${userHome}/.kube/config",
"--serverIP=<local ip>:9443",
"--serverIP=<SERVER-IP>:9443",
"-v=2",
],
},
{
@ -24,4 +25,4 @@
],
}
]
}
}

View file

@ -8,10 +8,25 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
)
// ImageVerificationType selects the type of verification algorithm
// +kubebuilder:validation:Enum=Cosign;NotaryV2
// +kubebuilder:default=Cosign
type ImageVerificationType string
const (
Cosign ImageVerificationType = "Cosign"
NotaryV2 ImageVerificationType = "NotaryV2"
)
// 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.
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.
// +kubebuilder:validation:Optional
Type ImageVerificationType `json:"type,omitempty" yaml:"type,omitempty"`
// Image is the image name consisting of the registry address, repository, image, and tag.
// Wildcards ('*' and '?') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.
// Deprecated. Use ImageReferences instead.
@ -234,6 +249,14 @@ type Attestation struct {
Conditions []AnyAllConditions `json:"conditions,omitempty" yaml:"conditions,omitempty"`
}
func (iv *ImageVerification) GetType() ImageVerificationType {
if iv.Type != "" {
return iv.Type
}
return Cosign
}
// Validate implements programmatic validation
func (iv *ImageVerification) Validate(path *field.Path) (errs field.ErrorList) {
copy := iv.Convert()

View file

@ -5,10 +5,25 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
)
// ImageVerificationType selects the type of verification algorithm
// +kubebuilder:validation:Enum=Cosign;NotaryV2
// +kubebuilder:default=Cosign
type ImageVerificationType string
const (
Cosign ImageVerificationType = "Cosign"
NotaryV2 ImageVerificationType = "NotaryV2"
)
// 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.
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.
// +kubebuilder:validation:Optional
Type ImageVerificationType `json:"type,omitempty" yaml:"type,omitempty"`
// ImageReferences is a list of matching image reference patterns. At least one pattern in the
// list must match the image for the rule to apply. Each image reference consists of a registry
// address (defaults to docker.io), repository, image, and tag (defaults to latest).

View file

@ -6632,6 +6632,14 @@ spec:
signing, for example an email address Deprecated. Use
KeylessAttestor instead.
type: string
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have a
@ -10001,6 +10009,14 @@ spec:
signing, for example an email address Deprecated.
Use KeylessAttestor instead.
type: string
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have
@ -13058,6 +13074,14 @@ spec:
i.e. have matched passed a signature or attestation
check.
type: boolean
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have a
@ -16427,6 +16451,14 @@ spec:
signing, for example an email address Deprecated.
Use KeylessAttestor instead.
type: string
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have
@ -19779,6 +19811,14 @@ spec:
signing, for example an email address Deprecated. Use
KeylessAttestor instead.
type: string
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have a
@ -23149,6 +23189,14 @@ spec:
signing, for example an email address Deprecated.
Use KeylessAttestor instead.
type: string
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have
@ -26207,6 +26255,14 @@ spec:
i.e. have matched passed a signature or attestation
check.
type: boolean
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have a
@ -29576,6 +29632,14 @@ spec:
signing, for example an email address Deprecated.
Use KeylessAttestor instead.
type: string
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have

View file

@ -3230,6 +3230,14 @@ spec:
signing, for example an email address Deprecated. Use
KeylessAttestor instead.
type: string
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have a
@ -6599,6 +6607,14 @@ spec:
signing, for example an email address Deprecated.
Use KeylessAttestor instead.
type: string
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have
@ -9656,6 +9672,14 @@ spec:
i.e. have matched passed a signature or attestation
check.
type: boolean
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have a
@ -13025,6 +13049,14 @@ spec:
signing, for example an email address Deprecated.
Use KeylessAttestor instead.
type: string
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have

View file

@ -3231,6 +3231,14 @@ spec:
signing, for example an email address Deprecated. Use
KeylessAttestor instead.
type: string
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have a
@ -6601,6 +6609,14 @@ spec:
signing, for example an email address Deprecated.
Use KeylessAttestor instead.
type: string
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have
@ -9659,6 +9675,14 @@ spec:
i.e. have matched passed a signature or attestation
check.
type: boolean
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have a
@ -13028,6 +13052,14 @@ spec:
signing, for example an email address Deprecated.
Use KeylessAttestor instead.
type: string
type:
description: 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.
enum:
- Cosign
- NotaryV2
type: string
verifyDigest:
default: true
description: VerifyDigest validates that images have

View file

@ -1784,6 +1784,20 @@ mutated to include the SHA digest retrieved during the registration.</p>
<tbody>
<tr>
<td>
<code>type</code><br/>
<em>
<a href="#kyverno.io/v1.ImageVerificationType">
ImageVerificationType
</a>
</em>
</td>
<td>
<p>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.</p>
</td>
</tr>
<tr>
<td>
<code>image</code><br/>
<em>
string
@ -1961,6 +1975,15 @@ bool
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.ImageVerificationType">ImageVerificationType
(<code>string</code> alias)</p></h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.ImageVerification">ImageVerification</a>)
</p>
<p>
<p>ImageVerificationType selects the type of verification algorithm</p>
</p>
<h3 id="kyverno.io/v1.KeylessAttestor">KeylessAttestor
</h3>
<p>
@ -6029,6 +6052,20 @@ mutated to include the SHA digest retrieved during the registration.</p>
<tbody>
<tr>
<td>
<code>type</code><br/>
<em>
<a href="#kyverno.io/v2beta1.ImageVerificationType">
ImageVerificationType
</a>
</em>
</td>
<td>
<p>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.</p>
</td>
</tr>
<tr>
<td>
<code>imageReferences</code><br/>
<em>
[]string
@ -6119,6 +6156,15 @@ bool
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v2beta1.ImageVerificationType">ImageVerificationType
(<code>string</code> alias)</p></h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v2beta1.ImageVerification">ImageVerification</a>)
</p>
<p>
<p>ImageVerificationType selects the type of verification algorithm</p>
</p>
<h3 id="kyverno.io/v2beta1.MatchResources">MatchResources
</h3>
<p>

14
go.mod
View file

@ -30,9 +30,13 @@ require (
github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23
github.com/lensesio/tableprinter v0.0.0-20201125135848-89e81fc956e7
github.com/mattbaird/jsonpatch v0.0.0-20200820163806-098863c1fc24
github.com/notaryproject/notation-core-go v1.0.0-rc.1
github.com/notaryproject/notation-go v1.0.0-rc.1
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.27.1
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
github.com/prometheus/client_golang v1.14.0
github.com/robfig/cron v1.2.0
github.com/sigstore/cosign v1.13.1
@ -71,6 +75,7 @@ require (
k8s.io/kube-openapi v0.0.0-20230118215034-64b6bb138190
k8s.io/pod-security-admission v0.26.1
k8s.io/utils v0.0.0-20230115233650-391b47cb4029
oras.land/oras-go/v2 v2.0.0
sigs.k8s.io/controller-runtime v0.14.4
sigs.k8s.io/kustomize/api v0.12.1
sigs.k8s.io/kustomize/kyaml v0.13.9
@ -95,6 +100,7 @@ require (
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
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-20220621081337-cb9428e4ac1e // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230117203413-a47887b8f098 // indirect
@ -155,10 +161,13 @@ require (
github.com/emirpasic/gods v1.18.1 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
github.com/go-chi/chi v4.1.2+incompatible // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-ldap/ldap/v3 v3.4.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/errors v0.20.3 // indirect
@ -253,13 +262,11 @@ require (
github.com/open-policy-agent/gatekeeper v0.0.0-20210824170141-dd97b8a7e966 // indirect
github.com/open-policy-agent/opa v0.48.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pjbgf/sha1cd v0.2.3 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.39.0 // indirect
@ -296,6 +303,8 @@ require (
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/transparency-dev/merkle v0.0.1 // indirect
github.com/vbatts/tar-split v0.11.2 // indirect
github.com/veraison/go-cose v1.0.0-rc.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/go-gitlab v0.78.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
@ -341,5 +350,4 @@ replace (
github.com/docker/cli => github.com/docker/cli v20.10.9+incompatible
github.com/evanphx/json-patch/v5 => github.com/kyverno/json-patch/v5 v5.5.1-0.20210915204938-7578f4ee9c77
github.com/jmespath/go-jmespath => github.com/kyverno/go-jmespath v0.4.1-0.20230204162932-3ee946b9433d
github.com/opencontainers/image-spec => github.com/opencontainers/image-spec v1.0.2
)

22
go.sum
View file

@ -93,6 +93,8 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU=
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
@ -426,6 +428,8 @@ github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmV
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM=
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -434,6 +438,8 @@ github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ER
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-critic/go-critic v0.5.6/go.mod h1:cVjj0DfqewQVIlIAGexPCaGaZDAqGE29PYDDADIVNEo=
@ -458,6 +464,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-ldap/ldap/v3 v3.4.4 h1:qPjipEpt+qDa6SI/h1fzuGWoRUY+qqQ9sOZq67/PYUs=
github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXgXtJC+aI=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
@ -1072,6 +1080,10 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/nishanths/exhaustive v0.1.0/go.mod h1:S1j9110vxV1ECdCudXRkeMnFQ/DQk9ajLT0Uf2MYZQQ=
github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ=
github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB9sbB1usJ+xjQE=
github.com/notaryproject/notation-core-go v1.0.0-rc.1 h1:ACi0gr6mD1bzp9+gu3P0meJ/N6iWHlyM9zgtdnooNAA=
github.com/notaryproject/notation-core-go v1.0.0-rc.1/go.mod h1:n8Gbvl9sKa00KptkKEL5XKUyMTIALe74QipKauE2rj4=
github.com/notaryproject/notation-go v1.0.0-rc.1 h1:WobIGCUPcPUDCD2qGMCccTfLm2J5y1bsh4SFVsyMIaA=
github.com/notaryproject/notation-go v1.0.0-rc.1/go.mod h1:xk4q0GXqGfEiy7WmyHi3Om3OM2dgHk0OHL6NIiJv5vA=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
@ -1122,8 +1134,8 @@ github.com/open-policy-agent/opa v0.48.0 h1:s2K823yohAUu/HB4MOPWDhBh88JMKQv7uTr6
github.com/open-policy-agent/opa v0.48.0/go.mod h1:CsQcksP+qGBxO9oEBj1NnZqKcjgjmTJbRNTzjZB/DXQ=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034=
github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@ -1427,9 +1439,13 @@ github.com/valyala/quicktemplate v1.6.3/go.mod h1:fwPzK2fHuYEODzJ9pkw0ipCPNHZ2tD
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
github.com/veraison/go-cose v1.0.0-rc.2 h1:zH3QmP4N5kwpdGauceIT3aJm8iUyV9OqpUOb+7CF7rQ=
github.com/veraison/go-cose v1.0.0-rc.2/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4=
github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xanzy/go-gitlab v0.78.0 h1:8jUHfQVAprG04Av5g0PxVd3CNsZ5hCbojIax7Hba1mE=
github.com/xanzy/go-gitlab v0.78.0/go.mod h1:DlByVTSXhPsJMYL6+cm8e8fTJjeBmhrXdC/yvkKKt6M=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
@ -2209,6 +2225,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.0.0 h1:+LRAz92WF7AvYQsQjPEAIw3Xb2zPPhuydjpi4pIHmc0=
oras.land/oras-go/v2 v2.0.0/go.mod h1:iVExH1NxrccIxjsiq17L91WCZ4KIw6jVQyCLsZsu1gc=
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=

View file

@ -12,7 +12,7 @@ import (
"github.com/google/go-containerregistry/pkg/name"
"github.com/in-toto/in-toto-golang/in_toto"
"github.com/kyverno/kyverno/pkg/registryclient"
"github.com/kyverno/kyverno/pkg/images"
"github.com/kyverno/kyverno/pkg/tracing"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
wildcard "github.com/kyverno/kyverno/pkg/utils/wildcard"
@ -34,32 +34,13 @@ import (
// ImageSignatureRepository is an alternate signature repository
var ImageSignatureRepository string
type Options struct {
ImageRef string
FetchAttestations bool
Key string
Cert string
CertChain string
Roots string
Subject string
Issuer string
AdditionalExtensions map[string]string
Annotations map[string]string
Repository string
RekorURL string
SignatureAlgorithm string
PredicateType string
func NewVerifier() images.ImageVerifier {
return &cosignVerifier{}
}
type Response struct {
Digest string
Statements []map[string]interface{}
}
type cosignVerifier struct{}
type CosignError struct{}
// VerifySignature verifies that the image has the expected signatures
func VerifySignature(ctx context.Context, rclient registryclient.Client, opts Options) (*Response, error) {
func (v *cosignVerifier) VerifySignature(ctx context.Context, opts images.Options) (*images.Response, error) {
ref, err := name.ParseReference(opts.ImageRef)
if err != nil {
return nil, fmt.Errorf("failed to parse image %s", opts.ImageRef)
@ -70,7 +51,7 @@ func VerifySignature(ctx context.Context, rclient registryclient.Client, opts Op
"",
"VERIFY IMG SIGS",
func(ctx context.Context, span trace.Span) ([]oci.Signature, bool, error) {
cosignOpts, err := buildCosignOptions(ctx, rclient, opts)
cosignOpts, err := buildCosignOptions(ctx, opts)
if err != nil {
return nil, false, err
}
@ -105,10 +86,10 @@ func VerifySignature(ctx context.Context, rclient registryclient.Client, opts Op
}
}
return &Response{Digest: digest}, nil
return &images.Response{Digest: digest}, nil
}
func buildCosignOptions(ctx context.Context, rclient registryclient.Client, opts Options) (*cosign.CheckOpts, error) {
func buildCosignOptions(ctx context.Context, opts images.Options) (*cosign.CheckOpts, error) {
var remoteOpts []remote.Option
var err error
signatureAlgorithmMap := map[string]crypto.Hash{
@ -121,7 +102,7 @@ func buildCosignOptions(ctx context.Context, rclient registryclient.Client, opts
if err != nil {
return nil, fmt.Errorf("constructing client options: %w", err)
}
remoteOpts = append(remoteOpts, rclient.BuildRemoteOption(ctx))
remoteOpts = append(remoteOpts, opts.RegistryClient.BuildRemoteOption(ctx))
cosignOpts := &cosign.CheckOpts{
Annotations: map[string]interface{}{},
RegistryClientOpts: remoteOpts,
@ -250,10 +231,8 @@ func loadCertChain(pem []byte) ([]*x509.Certificate, error) {
return cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(pem))
}
// FetchAttestations retrieves signed attestations and decodes them into in-toto statements
// https://github.com/in-toto/attestation/blob/main/spec/README.md#statement
func FetchAttestations(ctx context.Context, rclient registryclient.Client, opts Options) (*Response, error) {
cosignOpts, err := buildCosignOptions(ctx, rclient, opts)
func (v *cosignVerifier) FetchAttestations(ctx context.Context, opts images.Options) (*images.Response, error) {
cosignOpts, err := buildCosignOptions(ctx, opts)
if err != nil {
return nil, err
}
@ -312,7 +291,7 @@ func FetchAttestations(ctx context.Context, rclient registryclient.Client, opts
return nil, err
}
return &Response{Digest: digest, Statements: inTotoStatements}, nil
return &images.Response{Digest: digest, Statements: inTotoStatements}, nil
}
func matchPredicateType(sig oci.Signature, expectedPredicateType string) (bool, string, error) {

View file

@ -9,6 +9,7 @@ import (
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/kyverno/kyverno/pkg/images"
"github.com/kyverno/kyverno/pkg/registryclient"
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/cosign/pkg/cosign/bundle"
@ -72,24 +73,26 @@ func TestCosignPayload(t *testing.T) {
}
func TestCosignKeyless(t *testing.T) {
opts := Options{
opts := images.Options{
ImageRef: "ghcr.io/jimbugwadia/pause2",
Issuer: "https://github.com/",
Subject: "jim",
}
client, err := registryclient.New()
rc, err := registryclient.New()
assert.NilError(t, err)
opts.RegistryClient = rc
_, err = VerifySignature(context.TODO(), client, opts)
verifier := &cosignVerifier{}
_, err = verifier.VerifySignature(context.TODO(), opts)
assert.ErrorContains(t, err, "subject mismatch: expected jim, received jim@nirmata.com")
opts.Subject = "jim@nirmata.com"
_, err = VerifySignature(context.TODO(), client, opts)
_, err = verifier.VerifySignature(context.TODO(), opts)
assert.ErrorContains(t, err, "issuer mismatch: expected https://github.com/, received https://github.com/login/oauth")
opts.Issuer = "https://github.com/login/oauth"
_, err = VerifySignature(context.TODO(), client, opts)
_, err = verifier.VerifySignature(context.TODO(), opts)
assert.NilError(t, err)
}

View file

@ -49,78 +49,7 @@ func (e *engine) verifyAndPatchImages(
"pkg/engine",
fmt.Sprintf("RULE %s", rule.Name),
func(ctx context.Context, span trace.Span) {
if len(rule.VerifyImages) == 0 {
return
}
startTime := time.Now()
logger := internal.LoggerWithRule(logger, rules[i])
kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...)
subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(e.client, kindsInPolicy, policyContext)
if !matches(logger, rule, policyContext, subresourceGVKToAPIResource, e.configuration) {
return
}
// check if there is a corresponding policy exception
ruleResp := hasPolicyExceptions(logger, engineapi.ImageVerify, e.exceptionSelector, policyContext, rule, subresourceGVKToAPIResource, e.configuration)
if ruleResp != nil {
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
return
}
logger.V(3).Info("processing image verification rule")
ruleImages, imageRefs, err := e.extractMatchingImages(policyContext, rule)
if err != nil {
internal.AddRuleResponse(
&resp.PolicyResponse,
internal.RuleError(rule, engineapi.ImageVerify, "failed to extract images", err),
startTime,
)
return
}
if len(ruleImages) == 0 {
internal.AddRuleResponse(
&resp.PolicyResponse,
internal.RuleSkip(
rule,
engineapi.ImageVerify,
fmt.Sprintf("skip run verification as image in resource not found in imageRefs '%s'", imageRefs),
),
startTime,
)
return
}
policyContext.JSONContext().Restore()
if err := internal.LoadContext(ctx, e, policyContext, *rule); err != nil {
internal.AddRuleResponse(
&resp.PolicyResponse,
internal.RuleError(rule, engineapi.ImageVerify, "failed to load context", err),
startTime,
)
return
}
ruleCopy, err := substituteVariables(rule, policyContext.JSONContext(), logger)
if err != nil {
internal.AddRuleResponse(
&resp.PolicyResponse,
internal.RuleError(rule, engineapi.ImageVerify, "failed to substitute variables", err),
startTime,
)
return
}
iv := internal.NewImageVerifier(
logger,
e.rclient,
policyContext,
ruleCopy,
ivm,
)
for _, imageVerify := range ruleCopy.VerifyImages {
for _, r := range iv.Verify(ctx, imageVerify, ruleImages, e.configuration) {
internal.AddRuleResponse(&resp.PolicyResponse, r, startTime)
}
}
e.doVerifyAndPatch(ctx, logger, policyContext, rule, resp, ivm)
},
)
@ -132,6 +61,88 @@ func (e *engine) verifyAndPatchImages(
return resp, ivm
}
func (e *engine) doVerifyAndPatch(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,
rule *kyvernov1.Rule,
resp *engineapi.EngineResponse,
ivm *engineapi.ImageVerificationMetadata,
) {
if len(rule.VerifyImages) == 0 {
return
}
startTime := time.Now()
logger = internal.LoggerWithRule(logger, *rule)
kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...)
subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(e.client, kindsInPolicy, policyContext)
if !matches(logger, rule, policyContext, subresourceGVKToAPIResource, e.configuration) {
return
}
// check if there is a corresponding policy exception
ruleResp := hasPolicyExceptions(logger, engineapi.ImageVerify, e.exceptionSelector, policyContext, rule, subresourceGVKToAPIResource, e.configuration)
if ruleResp != nil {
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
return
}
logger.V(3).Info("processing image verification rule")
ruleImages, imageRefs, err := e.extractMatchingImages(policyContext, rule)
if err != nil {
internal.AddRuleResponse(
&resp.PolicyResponse,
internal.RuleError(rule, engineapi.ImageVerify, "failed to extract images", err),
startTime,
)
return
}
if len(ruleImages) == 0 {
internal.AddRuleResponse(
&resp.PolicyResponse,
internal.RuleSkip(
rule,
engineapi.ImageVerify,
fmt.Sprintf("skip run verification as image in resource not found in imageRefs '%s'", imageRefs),
),
startTime,
)
return
}
policyContext.JSONContext().Restore()
if err := internal.LoadContext(ctx, e, policyContext, *rule); err != nil {
internal.AddRuleResponse(
&resp.PolicyResponse,
internal.RuleError(rule, engineapi.ImageVerify, "failed to load context", err),
startTime,
)
return
}
ruleCopy, err := substituteVariables(rule, policyContext.JSONContext(), logger)
if err != nil {
internal.AddRuleResponse(
&resp.PolicyResponse,
internal.RuleError(rule, engineapi.ImageVerify, "failed to substitute variables", err),
startTime,
)
return
}
iv := internal.NewImageVerifier(
logger,
e.rclient,
policyContext,
ruleCopy,
ivm,
)
for _, imageVerify := range ruleCopy.VerifyImages {
for _, r := range iv.Verify(ctx, imageVerify, ruleImages, e.configuration) {
internal.AddRuleResponse(&resp.PolicyResponse, r, startTime)
}
}
}
func getMatchingImages(images map[string]map[string]apiutils.ImageInfo, rule *kyvernov1.Rule) ([]apiutils.ImageInfo, string) {
imageInfos := []apiutils.ImageInfo{}
imageRefs := []string{}

View file

@ -16,6 +16,8 @@ import (
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
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/registryclient"
apiutils "github.com/kyverno/kyverno/pkg/utils/api"
"github.com/kyverno/kyverno/pkg/utils/jsonpointer"
@ -285,8 +287,8 @@ func (iv *ImageVerifier) verifyAttestors(
imageVerify kyvernov1.ImageVerification,
imageInfo apiutils.ImageInfo,
predicateType string,
) (*engineapi.RuleResponse, *cosign.Response) {
var cosignResponse *cosign.Response
) (*engineapi.RuleResponse, *images.Response) {
var cosignResponse *images.Response
image := imageInfo.String()
for i, attestorSet := range attestors {
var err error
@ -341,8 +343,8 @@ func (iv *ImageVerifier) verifyAttestations(
for _, a := range attestor.Entries {
entryPath := fmt.Sprintf("%s.entries[%d]", attestorPath, i)
opts, subPath := iv.buildOptionsAndPath(a, imageVerify, image, &imageVerify.Attestations[i])
cosignResp, err := cosign.FetchAttestations(ctx, iv.rclient, *opts)
v, opts, subPath := iv.buildVerifier(a, imageVerify, image, &imageVerify.Attestations[i])
cosignResp, err := v.FetchAttestations(ctx, *opts)
if err != nil {
iv.logger.Error(err, "failed to fetch attestations")
return iv.handleRegistryErrors(image, err), ""
@ -386,7 +388,7 @@ func (iv *ImageVerifier) verifyAttestorSet(
imageVerify kyvernov1.ImageVerification,
imageInfo apiutils.ImageInfo,
path string,
) (*cosign.Response, error) {
) (*images.Response, error) {
var errorList []error
verifiedCount := 0
attestorSet = ExpandStaticKeys(attestorSet)
@ -395,7 +397,7 @@ func (iv *ImageVerifier) verifyAttestorSet(
for i, a := range attestorSet.Entries {
var entryError error
var cosignResp *cosign.Response
var cosignResp *images.Response
attestorPath := fmt.Sprintf("%s.entries[%d]", path, i)
iv.logger.V(4).Info("verifying attestorSet", "path", attestorPath)
@ -408,8 +410,8 @@ func (iv *ImageVerifier) verifyAttestorSet(
cosignResp, entryError = iv.verifyAttestorSet(ctx, *nestedAttestorSet, imageVerify, imageInfo, attestorPath)
}
} else {
opts, subPath := iv.buildOptionsAndPath(a, imageVerify, image, nil)
cosignResp, entryError = cosign.VerifySignature(ctx, iv.rclient, *opts)
v, opts, subPath := iv.buildVerifier(a, imageVerify, image, nil)
cosignResp, entryError = v.VerifySignature(ctx, *opts)
if entryError != nil {
entryError = fmt.Errorf("%s: %w", attestorPath+subPath, entryError)
}
@ -431,27 +433,49 @@ func (iv *ImageVerifier) verifyAttestorSet(
return nil, err
}
func (iv *ImageVerifier) buildOptionsAndPath(attestor kyvernov1.Attestor, imageVerify kyvernov1.ImageVerification, image string, attestation *kyvernov1.Attestation) (*cosign.Options, string) {
path := ""
opts := &cosign.Options{
ImageRef: image,
Repository: imageVerify.Repository,
Annotations: imageVerify.Annotations,
func (iv *ImageVerifier) buildVerifier(
attestor kyvernov1.Attestor,
imageVerify kyvernov1.ImageVerification,
image string,
attestation *kyvernov1.Attestation,
) (images.ImageVerifier, *images.Options, string) {
switch imageVerify.Type {
case kyvernov1.NotaryV2:
return iv.buildNotaryV2Verifier(attestor, imageVerify, image)
default:
return iv.buildCosignVerifier(attestor, imageVerify, image, attestation)
}
}
func (iv *ImageVerifier) buildCosignVerifier(
attestor kyvernov1.Attestor,
imageVerify kyvernov1.ImageVerification,
image string,
attestation *kyvernov1.Attestation,
) (images.ImageVerifier, *images.Options, string) {
path := ""
opts := &images.Options{
ImageRef: image,
Repository: imageVerify.Repository,
Annotations: imageVerify.Annotations,
RegistryClient: iv.rclient,
}
if imageVerify.Roots != "" {
opts.Roots = imageVerify.Roots
}
if attestation != nil {
opts.PredicateType = attestation.PredicateType
opts.FetchAttestations = true
}
if attestor.Keys != nil {
path = path + ".keys"
if attestor.Keys.PublicKeys != "" {
opts.Key = attestor.Keys.PublicKeys
} else if attestor.Keys.Secret != nil {
opts.Key = fmt.Sprintf("k8s://%s/%s", attestor.Keys.Secret.Namespace,
attestor.Keys.Secret.Name)
opts.Key = fmt.Sprintf("k8s://%s/%s", attestor.Keys.Secret.Namespace, attestor.Keys.Secret.Name)
} else if attestor.Keys.KMS != "" {
opts.Key = attestor.Keys.KMS
}
@ -471,18 +495,38 @@ func (iv *ImageVerifier) buildOptionsAndPath(attestor kyvernov1.Attestor, imageV
if attestor.Keyless.Rekor != nil {
opts.RekorURL = attestor.Keyless.Rekor.URL
}
opts.Roots = attestor.Keyless.Roots
opts.Issuer = attestor.Keyless.Issuer
opts.Subject = attestor.Keyless.Subject
opts.AdditionalExtensions = attestor.Keyless.AdditionalExtensions
}
if attestor.Repository != "" {
opts.Repository = attestor.Repository
}
if attestor.Annotations != nil {
opts.Annotations = attestor.Annotations
}
return opts, path
return cosign.NewVerifier(), opts, path
}
func (iv *ImageVerifier) buildNotaryV2Verifier(
attestor kyvernov1.Attestor,
imageVerify kyvernov1.ImageVerification,
image string,
) (images.ImageVerifier, *images.Options, string) {
path := ""
opts := &images.Options{
ImageRef: image,
Cert: attestor.Certificates.Certificate,
CertChain: attestor.Certificates.CertificateChain,
RegistryClient: iv.rclient,
}
return notaryv2.NewVerifier(), opts, path
}
func (iv *ImageVerifier) verifyAttestation(statements []map[string]interface{}, attestation kyvernov1.Attestation, imageInfo apiutils.ImageInfo) error {

View file

@ -8,7 +8,6 @@ import (
"testing"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
client "github.com/kyverno/kyverno/pkg/clients/dclient"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
@ -23,7 +22,7 @@ import (
func testMutate(
ctx context.Context,
client dclient.Interface,
client client.Interface,
rclient registryclient.Client,
pContext *PolicyContext,
contextLoader engineapi.ContextLoaderFactory,

View file

@ -157,7 +157,7 @@ func MatchesResourceDescription(subresourceGVKToAPIResource map[string]*metav1.A
var reasonsForFailure []error
if policyNamespace != "" && policyNamespace != resourceRef.GetNamespace() {
return fmt.Errorf(" The policy and resource namespace are different. Therefore, policy skip this resource.")
return fmt.Errorf("policy and resource namespaces mismatch")
}
if len(rule.MatchResources.Any) > 0 {

39
pkg/images/verifier.go Normal file
View file

@ -0,0 +1,39 @@
package images
import (
"context"
"github.com/kyverno/kyverno/pkg/registryclient"
)
type ImageVerifier interface {
// VerifySignature verifies that the image has the expected signatures
VerifySignature(ctx context.Context, opts Options) (*Response, error)
// FetchAttestations retrieves signed attestations and decodes them into in-toto statements
// https://github.com/in-toto/attestation/blob/main/spec/README.md#statement
FetchAttestations(ctx context.Context, opts Options) (*Response, error)
}
type Options struct {
ImageRef string
RegistryClient registryclient.Client
FetchAttestations bool
Key string
Cert string
CertChain string
Roots string
Subject string
Issuer string
AdditionalExtensions map[string]string
Annotations map[string]string
Repository string
RekorURL string
SignatureAlgorithm string
PredicateType string
Identities string
}
type Response struct {
Digest string
Statements []map[string]interface{}
}

132
pkg/notaryv2/notaryv2.go Normal file
View file

@ -0,0 +1,132 @@
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 &notaryV2Verifier{
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")
}

125
pkg/notaryv2/registry.go Normal file
View file

@ -0,0 +1,125 @@
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())
}

View file

@ -0,0 +1,35 @@
package notaryv2
import (
"context"
"crypto/x509"
"github.com/notaryproject/notation-go/verifier/truststore"
"github.com/pkg/errors"
)
type simpleTrustStore struct {
name string
storeType truststore.Type
certs []*x509.Certificate
}
func NewTrustStore(name string, certs []*x509.Certificate) truststore.X509TrustStore {
return &simpleTrustStore{
name: name,
storeType: truststore.TypeCA,
certs: certs,
}
}
func (ts *simpleTrustStore) GetCertificates(ctx context.Context, storeType truststore.Type, name string) ([]*x509.Certificate, error) {
if storeType != ts.storeType {
return nil, errors.Errorf("invalid truststore type")
}
if name != ts.name {
return nil, errors.Errorf("invalid truststore name")
}
return ts.certs, nil
}

View file

@ -9,7 +9,7 @@ import (
"net/http"
"time"
ecr "github.com/awslabs/amazon-ecr-credential-helper/ecr-login"
"github.com/awslabs/amazon-ecr-credential-helper/ecr-login"
"github.com/chrismellard/docker-credential-acr-env/pkg/credhelper"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/authn/github"
@ -49,8 +49,8 @@ var (
// Client provides registry related objects.
type Client interface {
// getKeychain provides keychain object.
getKeychain() authn.Keychain
// Keychain provides the configured credentials
Keychain() authn.Keychain
// getTransport provides transport object.
getTransport() http.RoundTripper
@ -61,6 +61,9 @@ type Client interface {
// BuildRemoteOption builds remote.Option based on client.
BuildRemoteOption(context.Context) remote.Option
// RefreshKeychainPullSecrets loads fresh data from pull secrets (if non-empty) and updates Keychain.
RefreshKeychainPullSecrets(ctx context.Context) error
}
type client struct {
@ -165,7 +168,7 @@ func (c *client) BuildRemoteOption(ctx context.Context) remote.Option {
// FetchImageDescriptor fetches Descriptor from registry with given imageRef
// and provides access to metadata about remote artifact.
func (c *client) FetchImageDescriptor(ctx context.Context, imageRef string) (*gcrremote.Descriptor, error) {
if err := c.refreshKeychainPullSecrets(ctx); err != nil {
if err := c.RefreshKeychainPullSecrets(ctx); err != nil {
return nil, fmt.Errorf("failed to refresh image pull secrets, error: %v", err)
}
parsedRef, err := name.ParseReference(imageRef)
@ -179,16 +182,15 @@ func (c *client) FetchImageDescriptor(ctx context.Context, imageRef string) (*gc
return desc, nil
}
// refreshKeychainPullSecrets loads fresh data from pull secrets and updates Keychain.
// If pull secrets are empty - returns.
func (c *client) refreshKeychainPullSecrets(ctx context.Context) error {
// refreshKeychainPullSecrets loads fresh data from pull secrets (if non-empty) and updates Keychain.
func (c *client) RefreshKeychainPullSecrets(ctx context.Context) error {
if c.pullSecretRefresher == nil {
return nil
}
return c.pullSecretRefresher(ctx, c)
}
func (c *client) getKeychain() authn.Keychain {
func (c *client) Keychain() authn.Keychain {
return c.keychain
}

View file

@ -15,7 +15,7 @@ func TestInitClientWithEmptyOptions(t *testing.T) {
c, err := New()
assert.NilError(t, err)
assert.Assert(t, defaultTransport == c.getTransport())
assert.Assert(t, c.getKeychain() != nil)
assert.Assert(t, c.Keychain() != nil)
}
func TestInitClientWithInsecureRegistryOption(t *testing.T) {
@ -27,5 +27,5 @@ func TestInitClientWithInsecureRegistryOption(t *testing.T) {
gotInsecureSkipVerify := c.getTransport().(*http.Transport).TLSClientConfig.InsecureSkipVerify
assert.NilError(t, err)
assert.Assert(t, expInsecureSkipVerify == gotInsecureSkipVerify)
assert.Assert(t, c.getKeychain() != nil)
assert.Assert(t, c.Keychain() != nil)
}