github.com/containerd/nerdctl@v1.7.7/docs/cosign.md (about) 1 # Container Image Sign and Verify with cosign tool 2 3 | :zap: Requirement | nerdctl >= 0.15 | 4 |-------------------|-----------------| 5 6 [cosign](https://github.com/sigstore/cosign) is tool that allows you to sign and verify container images with the 7 public/private key pairs or without them by providing 8 a [Keyless support](https://github.com/sigstore/cosign/blob/main/KEYLESS.md). 9 10 Keyless uses ephemeral keys and certificates, which are signed automatically by 11 the [fulcio](https://github.com/sigstore/fulcio) root CA. Signatures are stored in 12 the [rekor](https://github.com/sigstore/rekor) transparency log, which automatically provides an attestation as to when 13 the signature was created. 14 15 Cosign would use prompt to confirm the statement below during `sign`. Nerdctl added `--yes` to Cosign command, which says yes and prevents this prompt. 16 Using Nerdctl push with signing by Cosign means that users agree the statement. 17 18 19 ``` 20 Note that there may be personally identifiable information associated with this signed artifact. 21 This may include the email address associated with the account with which you authenticate. 22 This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later. 23 24 By typing 'y', you attest that you grant (or have permission to grant) and agree to have this information stored permanently in transparency logs. 25 ``` 26 27 You can enable container signing and verifying features with `push` and `pull` commands of `nerdctl` by using `cosign` 28 under the hood with make use of flags `--sign` while pushing the container image, and `--verify` while pulling the 29 container image. 30 31 > * Ensure cosign executable in your `$PATH`. 32 > * You can install cosign by following this page: https://docs.sigstore.dev/cosign/installation 33 34 Prepare your environment: 35 36 ```shell 37 # Create a sample Dockerfile 38 $ cat <<EOF | tee Dockerfile.dummy 39 FROM alpine:latest 40 CMD [ "echo", "Hello World" ] 41 EOF 42 ``` 43 44 > Please do not forget, we won't be validating the base images, which is `alpine:latest` in this case, of the container image that was built on, 45 > we'll only verify the container image itself once we sign it. 46 47 ```shell 48 49 # Build the image 50 $ nerdctl build -t devopps/hello-world -f Dockerfile.dummy . 51 52 # Generate a key-pair: cosign.key and cosign.pub 53 $ cosign generate-key-pair 54 55 # Export your COSIGN_PASSWORD to prevent CLI prompting 56 $ export COSIGN_PASSWORD=$COSIGN_PASSWORD 57 ``` 58 59 Sign the container image while pushing: 60 61 ``` 62 # Sign the image with Keyless mode 63 $ nerdctl push --sign=cosign devopps/hello-world 64 65 # Sign the image and store the signature in the registry 66 $ nerdctl push --sign=cosign --cosign-key cosign.key devopps/hello-world 67 ``` 68 69 Verify the container image while pulling: 70 71 > REMINDER: Image won't be pulled if there are no matching signatures in case you passed `--verify` flag. 72 73 > REMINDER: For keyless flows to work, you need to set either --cosign-certificate-identity or --cosign-certificate-identity-regexp, and either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp. The OIDC issuer expected in a valid Fulcio certificate for --verify=cosign, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. 74 75 ```shell 76 # Verify the image with Keyless mode 77 $ nerdctl pull --verify=cosign --certificate-identity=name@example.com --certificate-oidc-issuer=https://accounts.example.com devopps/hello-world 78 INFO[0004] cosign: 79 INFO[0004] cosign: [{"critical":{"identity":...}] 80 docker.io/devopps/nginx-new:latest: resolved |++++++++++++++++++++++++++++++++++++++| 81 manifest-sha256:0910d404e58dd320c3c0c7ea31bf5fbfe7544b26905c5eccaf87c3af7bcf9b88: done |++++++++++++++++++++++++++++++++++++++| 82 config-sha256:1de1c4fb5122ac8650e349e018fba189c51300cf8800d619e92e595d6ddda40e: done |++++++++++++++++++++++++++++++++++++++| 83 elapsed: 1.4 s total: 1.3 Ki (928.0 B/s) 84 85 # You can not verify the image if it is not signed 86 $ nerdctl pull --verify=cosign --cosign-key cosign.pub devopps/hello-world-bad 87 INFO[0003] cosign: Error: no matching signatures: 88 INFO[0003] cosign: failed to verify signature 89 INFO[0003] cosign: main.go:46: error during command execution: no matching signatures: 90 INFO[0003] cosign: failed to verify signature 91 ``` 92 93 ## Cosign in Compose 94 95 > Cosign support in Compose is also experimental and implemented based on Compose's [extension](https://github.com/compose-spec/compose-spec/blob/master/spec.md#extension) capibility. 96 97 cosign is supported in `nerdctl compose up|run|push|pull`. You can use cosign in Compose by adding the following fields in your compose yaml. These fields are _per service_, and you can enable only `verify` or only `sign` (or both). 98 99 ```yaml 100 # only put cosign related fields under the service you want to sign/verify. 101 services: 102 svc0: 103 build: . 104 image: ${REGISTRY}/svc0_image # replace with your registry 105 # `x-nerdctl-verify` and `x-nerdctl-cosign-public-key` are for verify 106 # required for `nerdctl compose up|run|pull` 107 x-nerdctl-verify: cosign 108 x-nerdctl-cosign-public-key: /path/to/cosign.pub 109 # `x-nerdctl-sign` and `x-nerdctl-cosign-private-key` are for sign 110 # required for `nerdctl compose push` 111 x-nerdctl-sign: cosign 112 x-nerdctl-cosign-private-key: /path/to/cosign.key 113 ports: 114 - 8080:80 115 svc1: 116 build: . 117 image: ${REGISTRY}/svc1_image # replace with your registry 118 ports: 119 - 8081:80 120 ``` 121 122 Following the cosign tutorial above, first set up environment and prepare cosign key pair: 123 124 ```shell 125 # Generate a key-pair: cosign.key and cosign.pub 126 $ cosign generate-key-pair 127 128 # Export your COSIGN_PASSWORD to prevent CLI prompting 129 $ export COSIGN_PASSWORD=$COSIGN_PASSWORD 130 ``` 131 132 We'll use the following `Dockerfile` and `docker-compose.yaml`: 133 134 ```shell 135 $ cat Dockerfile 136 FROM nginx:1.19-alpine 137 RUN uname -m > /usr/share/nginx/html/index.html 138 139 $ cat docker-compose.yml 140 services: 141 svc0: 142 build: . 143 image: ${REGISTRY}/svc1_image # replace with your registry 144 x-nerdctl-verify: cosign 145 x-nerdctl-cosign-public-key: ./cosign.pub 146 x-nerdctl-sign: cosign 147 x-nerdctl-cosign-private-key: ./cosign.key 148 ports: 149 - 8080:80 150 svc1: 151 build: . 152 image: ${REGISTRY}/svc1_image # replace with your registry 153 ports: 154 - 8081:80 155 ``` 156 157 For keyless mode, the `docker-compose.yaml` will be: 158 ``` 159 $ cat docker-compose.yml 160 services: 161 svc0: 162 build: . 163 image: ${REGISTRY}/svc1_image # replace with your registry 164 x-nerdctl-verify: cosign 165 x-nerdctl-sign: cosign 166 x-nerdctl-cosign-certificate-identity: name@example.com # or x-nerdctl-cosign-certificate-identity-regexp 167 x-nerdctl-cosign-certificate-oidc-issuer: https://accounts.example.com # or x-nerdctl-cosign-certificate-oidc-issuer-regexp 168 ports: 169 - 8080:80 170 svc1: 171 build: . 172 image: ${REGISTRY}/svc1_image # replace with your registry 173 ports: 174 - 8081:80 175 ``` 176 177 > The `env "COSIGN_PASSWORD="$COSIGN_PASSWORD""` part in the below commands is a walkaround to use rootful nerdctl and make the env variable visible to root (in sudo). You don't need this part if (1) you're using rootless, or (2) your `COSIGN_PASSWORD` is visible in root. 178 179 First let's `build` and `push` the two services: 180 181 ```shell 182 $ sudo nerdctl compose build 183 INFO[0000] Building image xxxxx/svc0_image 184 ... 185 INFO[0000] Building image xxxxx/svc1_image 186 [+] Building 0.2s (6/6) FINISHED 187 188 $ sudo env "COSIGN_PASSWORD="$COSIGN_PASSWORD"" nerdctl compose --experimental=true push 189 INFO[0000] Pushing image xxxxx/svc1_image 190 ... 191 INFO[0000] Pushing image xxxxx/svc0_image 192 INFO[0000] pushing as a reduced-platform image (application/vnd.docker.distribution.manifest.v2+json, sha256:4329abc3143b1545835de17e1302c8313a9417798b836022f4c8c8dc8b10a3e9) 193 INFO[0000] cosign: WARNING: Image reference xxxxx/svc0_image uses a tag, not a digest, to identify the image to sign. 194 INFO[0000] cosign: 195 INFO[0000] cosign: This can lead you to sign a different image than the intended one. Please use a 196 INFO[0000] cosign: digest (example.com/ubuntu@sha256:abc123...) rather than tag 197 INFO[0000] cosign: (example.com/ubuntu:latest) for the input to cosign. The ability to refer to 198 INFO[0000] cosign: images by tag will be removed in a future release. 199 INFO[0000] cosign: Pushing signature to: xxxxx/svc0_image 200 ``` 201 202 Then we can `pull` and `up` services (`run` is similar to up): 203 204 ```shell 205 # ensure built images are removed and pull is performed. 206 $ sudo nerdctl compose down 207 $ sudo env "COSIGN_PASSWORD="$COSIGN_PASSWORD"" nerdctl compose --experimental=true pull 208 $ sudo env "COSIGN_PASSWORD="$COSIGN_PASSWORD"" nerdctl compose --experimental=true up 209 $ sudo env "COSIGN_PASSWORD="$COSIGN_PASSWORD"" nerdctl compose --experimental=true run svc0 -- echo "hello" 210 # clean up compose resources. 211 $ sudo nerdctl compose down 212 ``` 213 214 Check your logs to confirm that svc0 is verified by cosign (have cosign logs) and svc1 is not. You can also change the public key in `docker-compose.yaml` to a random value to see verify failure will stop the container being `pull|up|run`.