Notary
Notary is a CNCF project that provides a specification and tooling for securing software supply chains.
The Notation CLI can be used to sign images and attestations in a CI/CD pipeline. A quick start guide providing a complete example of signing and verifying a container image using Notation can be found here.
The Notation CLI can also be used to inspect details of the container image signature.
1notation inspect ghcr.io/kyverno/test-verify-image@sha256:b31bfb4d0213f254d361e0079deaaebefa4f82ba7aa76ef82e90b4935ad5b105
2Inspecting all signatures for signed artifact
3ghcr.io/kyverno/test-verify-image@sha256:b31bfb4d0213f254d361e0079deaaebefa4f82ba7aa76ef82e90b4935ad5b105
4└── application/vnd.cncf.notary.signature
5 └── sha256:7f870420d92765b42cec0f71ee8e25bf39b692f64d95d6f6607e9e6e54300265
6 ├── media type: application/jose+json
7 ├── signature algorithm: RSASSA-PSS-SHA-256
8 ├── signed attributes
9 │ ├── signingScheme: notary.x509
10 │ └── signingTime: Mon May 22 14:45:04 2023
11 ├── user defined attributes
12 │ └── (empty)
13 ├── unsigned attributes
14 │ └── signingAgent: Notation/1.0.0
15 ├── certificates
16 │ └── SHA256 fingerprint: da1f2d7d648dfacc7ebd59f98a9f35c753c331d80ca4280bb94060f4af4a5357
17 │ ├── issued to: CN=test,O=Notary,L=Seattle,ST=WA,C=US
18 │ ├── issued by: CN=test,O=Notary,L=Seattle,ST=WA,C=US
19 │ └── expiry: Thu May 19 21:15:18 2033
20 └── signed artifact
21 ├── media type: application/vnd.docker.distribution.manifest.v2+json
22 ├── digest: sha256:b31bfb4d0213f254d361e0079deaaebefa4f82ba7aa76ef82e90b4935ad5b105
23 └── size: 938
You can also use an OCI registry client to discover signatures and attestations for an image.
1oras discover -o tree ghcr.io/kyverno/test-verify-image:signed
2ghcr.io/kyverno/test-verify-image@sha256:b31bfb4d0213f254d361e0079deaaebefa4f82ba7aa76ef82e90b4935ad5b105
3├── application/vnd.cncf.notary.signature
4│ ├── sha256:7f870420d92765b42cec0f71ee8e25bf39b692f64d95d6f6607e9e6e54300265
5│ └── sha256:f7d941ed9e93a1ff1d5dee3b091144a87dae1d73481d5be93aa65258a110c689
6├── vulnerability-scan
7│ └── sha256:f89cb7a0748c63a674d157ca84d725ff3ac09cc2d4aee9d0ec4315e0fe92a5fd
8│ └── application/vnd.cncf.notary.signature
9│ └── sha256:ec45844601244aa08ac750f44def3fd48ddacb736d26b83dde9f5d8ac646c2f3
10├── sbom/cyclone-dx
11│ └── sha256:8cad9bd6de426683424a204697dd48b55abcd6bb6b4930ad9d8ade99ae165414
12│ └── application/vnd.cncf.notary.signature
13│ └── sha256:61f3e42f017b72f4277c78a7a42ff2ad8f872811324cd984830dfaeb4030c322
14├── application/vnd.cyclonedx+json
15│ └── sha256:aa886b475b431a37baa0e803765a9212f0accece0b82a131ebafd43ea78fa1f8
16│ └── application/vnd.cncf.notary.signature
17│ ├── sha256:00c5f96577878d79b545d424884886c37e270fac5996f17330d77a01a96801eb
18│ └── sha256:f3dc4687f5654ea8c2bc8da4e831d22a067298e8651fb59d55565dee58e94e2d
19├── cyclonedx/vex
20│ └── sha256:c058f08c9103bb676fcd0b98e41face2436e0a16f3d1c8255797b916ab5daa8a
21│ └── application/vnd.cncf.notary.signature
22│ └── sha256:79edc8936a4fb8758b9cb2b8603a1c7903f53261c425efb0cd85b09715eb6dfa
23└── trivy/scan
24 └── sha256:a75ac963617462fdfe6a3847d17e5519465dfb069f92870050cce5269e7cbd7b
25 └── application/vnd.cncf.notary.signature
26 └── sha256:d1e2b2ba837c164c282cf389594791a190df872cf7712b4d91aa10a3520a8460
Verifying Image Signatures
The following policy checks whether an image is signed with a valid X.509 key that matches the provided public certificate.
1apiVersion: kyverno.io/v2beta1
2kind: ClusterPolicy
3metadata:
4 name: check-image-notary
5spec:
6 validationFailureAction: Enforce
7 webhookTimeoutSeconds: 30
8 failurePolicy: Fail
9 rules:
10 - name: verify-signature-notary
11 match:
12 any:
13 - resources:
14 kinds:
15 - Pod
16 verifyImages:
17 - type: Notary
18 imageReferences:
19 - "ghcr.io/kyverno/test-verify-image*"
20 attestors:
21 - count: 1
22 entries:
23 - certificates:
24 cert: |-
25 -----BEGIN CERTIFICATE-----
26 MIIDTTCCAjWgAwIBAgIJAPI+zAzn4s0xMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV
27 BAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEPMA0GA1UECgwG
28 Tm90YXJ5MQ0wCwYDVQQDDAR0ZXN0MB4XDTIzMDUyMjIxMTUxOFoXDTMzMDUxOTIx
29 MTUxOFowTDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0
30 dGxlMQ8wDQYDVQQKDAZOb3RhcnkxDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3
31 DQEBAQUAA4IBDwAwggEKAoIBAQDNhTwv+QMk7jEHufFfIFlBjn2NiJaYPgL4eBS+
32 b+o37ve5Zn9nzRppV6kGsa161r9s2KkLXmJrojNy6vo9a6g6RtZ3F6xKiWLUmbAL
33 hVTCfYw/2n7xNlVMjyyUpE+7e193PF8HfQrfDFxe2JnX5LHtGe+X9vdvo2l41R6m
34 Iia04DvpMdG4+da2tKPzXIuLUz/FDb6IODO3+qsqQLwEKmmUee+KX+3yw8I6G1y0
35 Vp0mnHfsfutlHeG8gazCDlzEsuD4QJ9BKeRf2Vrb0ywqNLkGCbcCWF2H5Q80Iq/f
36 ETVO9z88R7WheVdEjUB8UrY7ZMLdADM14IPhY2Y+tLaSzEVZAgMBAAGjMjAwMAkG
37 A1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0G
38 CSqGSIb3DQEBCwUAA4IBAQBX7x4Ucre8AIUmXZ5PUK/zUBVOrZZzR1YE8w86J4X9
39 kYeTtlijf9i2LTZMfGuG0dEVFN4ae3CCpBst+ilhIndnoxTyzP+sNy4RCRQ2Y/k8
40 Zq235KIh7uucq96PL0qsF9s2RpTKXxyOGdtp9+HO0Ty5txJE2txtLDUIVPK5WNDF
41 ByCEQNhtHgN6V20b8KU2oLBZ9vyB8V010dQz0NRTDLhkcvJig00535/LUylECYAJ
42 5/jn6XKt6UYCQJbVNzBg/YPGc1RF4xdsGVDBben/JXpeGEmkdmXPILTKd9tZ5TC0
43 uOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz
44 -----END CERTIFICATE-----
With this policy configured, Kyverno will verify matching container image signatures and only allow the pod to be configured if the signatures are valid.
1kubectl run test --image=ghcr.io/kyverno/test-verify-image:signed --dry-run=server
2pod/test created (server dry run)
Kyverno will also mutate the pod to replace the image tag with its digest.
1kubectl run test --image=ghcr.io/kyverno/test-verify-image:signed --dry-run=server -o yaml | grep "image: "
2 - image: ghcr.io/kyverno/test-verify-image:signed@sha256:b31bfb4d0213f254d361e0079deaaebefa4f82ba7aa76ef82e90b4935ad5b105
Attempting to run a pod with an unsigned image will be blocked.
1kubectl run test --image=ghcr.io/kyverno/test-verify-image:unsigned --dry-run=server
2Error from server: admission webhook "mutate.kyverno.svc-fail" denied the request:
3
4resource Pod/default/test was blocked due to the following policies
5
6check-image-notary:
7 verify-signature-notary: 'failed to verify image ghcr.io/kyverno/test-verify-image:unsigned:
8 .attestors[0].entries[0]: failed to verify ghcr.io/kyverno/test-verify-image@sha256:74a98f0e4d750c9052f092a7f7a72de7b20f94f176a490088f7a744c76c53ea5:
9 no signature is associated with "ghcr.io/kyverno/test-verify-image@sha256:74a98f0e4d750c9052f092a7f7a72de7b20f94f176a490088f7a744c76c53ea5",
10 make sure the image was signed successfully'
Tip
You can manage public keys and certificates as external data in a ConfigMap. See Variables from ConfigMaps for details.Verifying Image Attestations
Consider the following image: ghcr.io/kyverno/test-verify-image:signed
ghcr.io/kyverno/test-verify-image:signed
├── application/vnd.cncf.notary.signature
│ ├── sha256:7f870420d92765b42cec0f71ee8e25bf39b692f64d95d6f6607e9e6e54300265
│ └── sha256:f7d941ed9e93a1ff1d5dee3b091144a87dae1d73481d5be93aa65258a110c689
├── vulnerability-scan
│ └── sha256:f89cb7a0748c63a674d157ca84d725ff3ac09cc2d4aee9d0ec4315e0fe92a5fd
│ └── application/vnd.cncf.notary.signature
│ └── sha256:ec45844601244aa08ac750f44def3fd48ddacb736d26b83dde9f5d8ac646c2f3
├── sbom/cyclone-dx
│ └── sha256:8cad9bd6de426683424a204697dd48b55abcd6bb6b4930ad9d8ade99ae165414
│ └── application/vnd.cncf.notary.signature
│ └── sha256:61f3e42f017b72f4277c78a7a42ff2ad8f872811324cd984830dfaeb4030c322
├── application/vnd.cyclonedx+json
│ └── sha256:aa886b475b431a37baa0e803765a9212f0accece0b82a131ebafd43ea78fa1f8
│ └── application/vnd.cncf.notary.signature
│ ├── sha256:00c5f96577878d79b545d424884886c37e270fac5996f17330d77a01a96801eb
│ └── sha256:f3dc4687f5654ea8c2bc8da4e831d22a067298e8651fb59d55565dee58e94e2d
├── cyclonedx/vex
│ └── sha256:c058f08c9103bb676fcd0b98e41face2436e0a16f3d1c8255797b916ab5daa8a
│ └── application/vnd.cncf.notary.signature
│ └── sha256:79edc8936a4fb8758b9cb2b8603a1c7903f53261c425efb0cd85b09715eb6dfa
└── trivy/scan
└── sha256:a75ac963617462fdfe6a3847d17e5519465dfb069f92870050cce5269e7cbd7b
└── application/vnd.cncf.notary.signature
└── sha256:d1e2b2ba837c164c282cf389594791a190df872cf7712b4d91aa10a3520a8460
This image has:
- A notary signature.
- A vulnerability scan report, signed using notary.
- A CycloneDX SBOM, signed using notary.
- A CycloneDX VEX report, signed using notary.
- A Trivy scan report, signed using notary.
This policy checks the signature in the repo ghcr.io/kyverno/test-verify-image
and ensures that it has been signed by verifying its signature against the provided certificates:
1apiVersion: kyverno.io/v1
2kind: ClusterPolicy
3metadata:
4 name: check-image-attestation
5spec:
6 validationFailureAction: Enforce
7 webhookTimeoutSeconds: 30
8 failurePolicy: Fail
9 rules:
10 - name: verify-attestation-notary
11 match:
12 any:
13 - resources:
14 kinds:
15 - Pod
16 context:
17 - name: keys
18 configMap:
19 name: keys
20 namespace: kyverno
21 verifyImages:
22 - type: Notary
23 imageReferences:
24 - "ghcr.io/kyverno/test-verify-image*"
25 attestations:
26 - type: sbom/cyclone-dx
27 attestors:
28 - entries:
29 - certificates:
30 cert: |-
31 -----BEGIN CERTIFICATE-----
32 MIIDTTCCAjWgAwIBAgIJAPI+zAzn4s0xMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV
33 BAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEPMA0GA1UECgwG
34 Tm90YXJ5MQ0wCwYDVQQDDAR0ZXN0MB4XDTIzMDUyMjIxMTUxOFoXDTMzMDUxOTIx
35 MTUxOFowTDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0
36 dGxlMQ8wDQYDVQQKDAZOb3RhcnkxDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3
37 DQEBAQUAA4IBDwAwggEKAoIBAQDNhTwv+QMk7jEHufFfIFlBjn2NiJaYPgL4eBS+
38 b+o37ve5Zn9nzRppV6kGsa161r9s2KkLXmJrojNy6vo9a6g6RtZ3F6xKiWLUmbAL
39 hVTCfYw/2n7xNlVMjyyUpE+7e193PF8HfQrfDFxe2JnX5LHtGe+X9vdvo2l41R6m
40 Iia04DvpMdG4+da2tKPzXIuLUz/FDb6IODO3+qsqQLwEKmmUee+KX+3yw8I6G1y0
41 Vp0mnHfsfutlHeG8gazCDlzEsuD4QJ9BKeRf2Vrb0ywqNLkGCbcCWF2H5Q80Iq/f
42 ETVO9z88R7WheVdEjUB8UrY7ZMLdADM14IPhY2Y+tLaSzEVZAgMBAAGjMjAwMAkG
43 A1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0G
44 CSqGSIb3DQEBCwUAA4IBAQBX7x4Ucre8AIUmXZ5PUK/zUBVOrZZzR1YE8w86J4X9
45 kYeTtlijf9i2LTZMfGuG0dEVFN4ae3CCpBst+ilhIndnoxTyzP+sNy4RCRQ2Y/k8
46 Zq235KIh7uucq96PL0qsF9s2RpTKXxyOGdtp9+HO0Ty5txJE2txtLDUIVPK5WNDF
47 ByCEQNhtHgN6V20b8KU2oLBZ9vyB8V010dQz0NRTDLhkcvJig00535/LUylECYAJ
48 5/jn6XKt6UYCQJbVNzBg/YPGc1RF4xdsGVDBben/JXpeGEmkdmXPILTKd9tZ5TC0
49 uOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz
50 -----END CERTIFICATE-----
51 conditions:
52 - all:
53 - key: "{{ components[].licenses[].expression }}"
54 operator: AllIn
55 value: ["GPL-3.0"]
56
After this policy is applied, Kyverno will verify the signature on the sbom/cyclone-dx attestation and check if the license version of all the components in the SBOM is GPL-3.0
.
1kubectl run test --image=ghcr.io/kyverno/test-verify-image:signed --dry-run=server
2pod/test created (server dry run)
Validation across multiple image attestations
Consider the image: ghcr.io/kyverno/test-verify-image:signed
which image has:
- A notary signature.
- A vulnerability scan report, signed using notary.
- A CycloneDX VEX report, signed using notary.
This policy checks:
- The signature in the repo
ghcr.io/kyverno/test-verify-image
- Ensures that it has a vulnerability scan report of type
trivy/vulnerability
, and a CycloneDX VEX report of typevex/cyclone-dx
, both are signed using the given certificate. - All the vulnerabilities found in the trivy scan report should be allowed in the vex report.
1apiVersion: kyverno.io/v1
2kind: ClusterPolicy
3metadata:
4 name: check-image-attestation
5spec:
6 validationFailureAction: Enforce
7 webhookTimeoutSeconds: 30
8 failurePolicy: Fail
9 rules:
10 - name: verify-attestation-notary
11 match:
12 any:
13 - resources:
14 kinds:
15 - Pod
16 context:
17 - name: keys
18 configMap:
19 name: keys
20 namespace: notary-verify-attestation
21 verifyImages:
22 - type: Notary
23 imageReferences:
24 - "ghcr.io/kyverno/test-verify-image*"
25 attestations:
26 - type: trivy/vulnerability
27 name: trivy
28 attestors:
29 - entries:
30 - certificates:
31 cert: |-
32 -----BEGIN CERTIFICATE-----
33 MIIDmDCCAoCgAwIBAgIUCntgF4FftePAhEa6nZTsu/NMT3cwDQYJKoZIhvcNAQEL
34 BQAwTDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0dGxl
35 MQ8wDQYDVQQKDAZOb3RhcnkxDTALBgNVBAMMBHRlc3QwHhcNMjQwNjEwMTYzMTQ2
36 WhcNMzQwNjA4MTYzMTQ2WjBMMQswCQYDVQQGEwJVUzELMAkGA1UECAwCV0ExEDAO
37 BgNVBAcMB1NlYXR0bGUxDzANBgNVBAoMBk5vdGFyeTENMAsGA1UEAwwEdGVzdDCC
38 ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJkEGqbILiWye6C1Jz+jwwDY
39 k/rovpXzxS+EQDvfj/YKvx37Kr4cjboJORu3wtzICWhPUtVWZ21ShfjerKgNq0iB
40 mrlF4cqz2KcOfuUT3XBglH/NwhEAqOrGPQrMsoQEFWgnilr0RTc+j4vDnkdkcTj2
41 K/qPhQHRAeb97TdvFCqcZfAGqiOVUqzDGxd2INz/fJd4/nYRX3LJBn9pUGxqRwZV
42 ElP5B/aCBjJDdh6tAElT5aDnLGAB+3+W2YwG342ELyAl2ILpbSRUpKLNAfKEd7Nj
43 1moIl4or5AIlTkgewZ/AK68HPFJEV3SwNbzkgAC+/mLVCD8tqu0o0ziyIUJtoQMC
44 AwEAAaNyMHAwHQYDVR0OBBYEFFTIzCppwv0vZnAVmETPm1CfMdcYMB8GA1UdIwQY
45 MBaAFFTIzCppwv0vZnAVmETPm1CfMdcYMAkGA1UdEwQCMAAwDgYDVR0PAQH/BAQD
46 AgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IBAQB8/vfP
47 /TQ3X80JEZDsttdvd9NLm08bTJ/T+nh0DIiV10aHymQT9/u+iahfm1+7mj+uv8LS
48 Y63LepQCX5p9SoFzt513pbNYXMBbRrOKpth3DD49IPL2Gce86AFGydfrakd86CL1
49 9MhFeWhtRf0KndyUX8J2s7jbpoN8HrN4/wZygiEqbQWZG8YtIZ9EewmoVMYirQqH
50 EvW93NcgmjiELuhjndcT/kHjhf8fUAgSuxiPIy6ern02fJjw40KzgiKNvxMoI9su
51 G2zu6gXmxkw+x0SMe9kX+Rg4hCIjTUM7dc66XL5LcTp4S5YEZNVC40/FgTIZoK0e
52 r1dC2/Y1SmmrIoA1
53 -----END CERTIFICATE-----
54 - type: vex/cyclone-dx
55 name: vex
56 attestors:
57 - entries:
58 - certificates:
59 cert: |-
60 -----BEGIN CERTIFICATE-----
61 MIIDmDCCAoCgAwIBAgIUCntgF4FftePAhEa6nZTsu/NMT3cwDQYJKoZIhvcNAQEL
62 BQAwTDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0dGxl
63 MQ8wDQYDVQQKDAZOb3RhcnkxDTALBgNVBAMMBHRlc3QwHhcNMjQwNjEwMTYzMTQ2
64 WhcNMzQwNjA4MTYzMTQ2WjBMMQswCQYDVQQGEwJVUzELMAkGA1UECAwCV0ExEDAO
65 BgNVBAcMB1NlYXR0bGUxDzANBgNVBAoMBk5vdGFyeTENMAsGA1UEAwwEdGVzdDCC
66 ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJkEGqbILiWye6C1Jz+jwwDY
67 k/rovpXzxS+EQDvfj/YKvx37Kr4cjboJORu3wtzICWhPUtVWZ21ShfjerKgNq0iB
68 mrlF4cqz2KcOfuUT3XBglH/NwhEAqOrGPQrMsoQEFWgnilr0RTc+j4vDnkdkcTj2
69 K/qPhQHRAeb97TdvFCqcZfAGqiOVUqzDGxd2INz/fJd4/nYRX3LJBn9pUGxqRwZV
70 ElP5B/aCBjJDdh6tAElT5aDnLGAB+3+W2YwG342ELyAl2ILpbSRUpKLNAfKEd7Nj
71 1moIl4or5AIlTkgewZ/AK68HPFJEV3SwNbzkgAC+/mLVCD8tqu0o0ziyIUJtoQMC
72 AwEAAaNyMHAwHQYDVR0OBBYEFFTIzCppwv0vZnAVmETPm1CfMdcYMB8GA1UdIwQY
73 MBaAFFTIzCppwv0vZnAVmETPm1CfMdcYMAkGA1UdEwQCMAAwDgYDVR0PAQH/BAQD
74 AgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IBAQB8/vfP
75 /TQ3X80JEZDsttdvd9NLm08bTJ/T+nh0DIiV10aHymQT9/u+iahfm1+7mj+uv8LS
76 Y63LepQCX5p9SoFzt513pbNYXMBbRrOKpth3DD49IPL2Gce86AFGydfrakd86CL1
77 9MhFeWhtRf0KndyUX8J2s7jbpoN8HrN4/wZygiEqbQWZG8YtIZ9EewmoVMYirQqH
78 EvW93NcgmjiELuhjndcT/kHjhf8fUAgSuxiPIy6ern02fJjw40KzgiKNvxMoI9su
79 G2zu6gXmxkw+x0SMe9kX+Rg4hCIjTUM7dc66XL5LcTp4S5YEZNVC40/FgTIZoK0e
80 r1dC2/Y1SmmrIoA1
81 -----END CERTIFICATE-----
82 validate:
83 deny:
84 conditions:
85 any:
86 - key: '{{ trivy.Vulnerabilities[*].VulnerabilityID }}'
87 operator: AnyNotIn
88 value: '{{ vex.vulnerabilities[*].id }}'
89 message: All vulnerabilities in trivy and vex should be same
After this policy is applied, Kyverno will verify the signatures in the image and the attestations and then evaluate the validate deny condition which checks all the vulneribilities in trivy report are there in vex report.
1kubectl run test --image=ghcr.io/kyverno/test-verify-image:signed --dry-run=server
2pod/test created (server dry run)
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.