vcf-services

Securing Supervisor Services: Image Signing and Verification

To ensure your Supervisor Service artifact has not been tampered with and to demonstrate that it originates from a trusted publisher, you should sign the Carvel imgpkg bundle that backs each service version.

Unsigned Supervisor Services trigger a third-party software disclaimer in the UI, and certain high-privilege capabilities are restricted to bundles the Supervisor can verify as trusted.

Signing model at a glance

The Supervisor verifies bundle signatures with cosign + traditional Public Key Infrastructure (PKI):

What gets signed

Sign the imgpkg bundle that the Carvel Package references in spec.template.spec.fetch[].imgpkgBundle.image. This is the only artifact the Supervisor verifies.

You do not need to individually sign the container images inside the bundle — those are integrity-protected by the bundle’s own digest manifest once the bundle itself is trusted.

Trust and certificate chains

The Supervisor only accepts signatures whose embedded leaf certificate chains to a root in its built-in trust pool. We plan to support custom CA in future.

Prerequisites

Tool Purpose
imgpkg Build, push, copy bundles
cosign Sign and inspect signatures
crane (optional) Inspect manifests directly
an OCI-compliant registry Host the imgpkg bundle and its signature

Step-by-step signing workflow

Step 1: Get your bundle into a registry

Cosign signs artifacts in the registry, so the bundle must already exist as an OCI image.

imgpkg copy --tar my-service-bundle.tar --to-repo <REGISTRY>/<REPO>

Step 2: Resolve the bundle to a digest

Always sign by digest, not by tag — if the tag moves, a tag-based signature no longer matches what customers pull.

imgpkg describe -b <REGISTRY>/<REPO>:<TAG>
Sample `imgpkg describe` output ```bash $ imgpkg describe -b projects.packages.broadcom.com/tkg/packages/standard/contour:v1.28.2_vmware.1-tkg.1 Bundle SHA: sha256:ac665d3d0256f70c5272a593421ff3ef4532e98409bca7a72275befdfd3861be <====== Images: - Image: projects.registry.vmware.com/tkg/contour@sha256:d9d7063e1d11f1a518378aedc719f234a808109fbce6fb52701064c9b800c050 Type: Image Origin: projects.registry.vmware.com/tkg/contour@sha256:d9d7063e1d11f1a518378aedc719f234a808109fbce6fb52701064c9b800c050 Layers: - Digest: sha256:b4b933e428f5fd980733cf04da04c3e87350bf52a94797e774683b3688e64c54 Annotations: kbld.carvel.dev/id: ghcr.io/projectcontour/contour:v1.28.2 kbld.carvel.dev/origins: - resolved: tag: v1.28.2_vmware.1 url: projects.registry.vmware.com/tkg/contour:v1.28.2_vmware.1 - Image: projects.registry.vmware.com/tkg/envoy@sha256:a941610f98381083870953ede2d14d90b52c16f9b9f72b3158ccbbdf8c0bd608 Type: Image Origin: projects.registry.vmware.com/tkg/envoy@sha256:a941610f98381083870953ede2d14d90b52c16f9b9f72b3158ccbbdf8c0bd608 Layers: - Digest: sha256:2369c33f073ae935e1a0a5c3512446e661b865029a2a07941ee6c3c03d99da75 - Digest: sha256:2b776ada03417eaa87102a617f964324df1de8967698fc4209dc1a1fbdfae8cd - Digest: sha256:2a977872b36c1a2309fac8bd22b0fa0b3ee6efd1a25af5016ee9409beca1b3cf - Digest: sha256:54e6e6b3b3915686a45868275ea2e3a1195571182f288ac23a31ee6f8fe75c87 - Digest: sha256:ab4403e442bffb2ce0a44f9463d0891337d0784e2d5d50f0677e903622591609 - Digest: sha256:86d03bd6344168c148233c4f8adaf4b89aa08c01a05d30269ed367f113794ca7 - Digest: sha256:52873439a13501a6a358c47eefe68330258edbf019125aec597b713deb259bb5 - Digest: sha256:68d6cfd04225ec378e86f390e84392094483e5e35386baea4ce1becf4d570709 - Digest: sha256:4aa0ea1413d37a58615488592a0b827ea4b2e48fa5a77cf707d0e35f025e613f - Digest: sha256:5dbac4f9d635b61bbe48d8ac04943a22c0ae53927f84997891e16a8cacfebe20 - Digest: sha256:5d12877714b4438a7621b98d097e0a80224a5e190ab425575f875ced845640b9 - Digest: sha256:ffb78650e67985c615ddc88e82487cf2feda34bdff5ba9fa295523af3b63f36f - Digest: sha256:8e7e39f179d898f2fee3ae36f4e5d98e198547974bfefdabb7b9b06a18ffa66d Annotations: kbld.carvel.dev/id: docker.io/envoyproxy/envoy:v1.29.2 kbld.carvel.dev/origins: - resolved: tag: v1.29.2_vmware.1 url: projects.registry.vmware.com/tkg/envoy:v1.29.2_vmware.1 Succeeded ``` In the output, find `Bundle SHA:`. Copy the full digest including the `sha256:` prefix; that value is `` for the next steps. </details> ### Step 3: Sign with attached certificate and chain Use a leaf signing certificate and chain that chain to a root the **Supervisor already trusts** (see [Trust and certificate chains](#trust-and-certificate-chains) below). File-based keys are the usual approach for local and CI signing: ```bash cosign sign \ --key private.key \ --certificate cert.pem \ --certificate-chain chain.pem \ --tlog-upload=false \ /@ ``` **Notes on the flags:** - **`--key`, `--certificate`, `--certificate-chain`** — the Supervisor expects cosign signatures that carry the leaf cert and chain so it can walk trust to a known root. - **`--tlog-upload=false`** — optional policy choice. The Supervisor does not rely on the public Rekor log for verification; setting `false` avoids publishing your bundle digest to Rekor. ### Step 4: Verify the signature is published Pick one of these checks. #### Using `imgpkg` ```bash imgpkg describe -b /@ ```
Sample `imgpkg describe` output with explanation The transcript below is from a real signing run against a [Contour](https://projectcontour.io/) supervisor-service bundle pushed to a local test registry on `localhost:5123`. Use it as a reference for the shape of the output. ```bash imgpkg describe -b localhost:5123/contour@sha256:ac665d3d0256f70c5272a593421ff3ef4532e98409bca7a72275befdfd3861be ``` ```text Bundle SHA: sha256:ac665d3d0256f70c5272a593421ff3ef4532e98409bca7a72275befdfd3861be Images: - Image: localhost:5123/contour@sha256:824f983f1bb5dcc3ca0cecc476beea3ee6f4fd1ed5378c9e8be5143ac09ed6d2 Type: Signature Annotations: tag: sha256-ac665d3d0256f70c5272a593421ff3ef4532e98409bca7a72275befdfd3861be.sig - Image: localhost:5123/contour@sha256:91e9617a69f2a01f645c28cc4b2def569179c513a7155aa29909fa7eb272053b Type: Internal - Image: localhost:5123/contour@sha256:d9d7063e1d11f1a518378aedc719f234a808109fbce6fb52701064c9b800c050 Type: Image Origin: projects.registry.vmware.com/tkg/contour@sha256:d9d7063e1d11f1a518378aedc719f234a808109fbce6fb52701064c9b800c050 Annotations: kbld.carvel.dev/id: ghcr.io/projectcontour/contour:v1.28.2 kbld.carvel.dev/origins: - resolved: tag: v1.28.2_vmware.1 url: projects.registry.vmware.com/tkg/contour:v1.28.2_vmware.1 - Image: localhost:5123/contour@sha256:a941610f98381083870953ede2d14d90b52c16f9b9f72b3158ccbbdf8c0bd608 Type: Image Origin: projects.registry.vmware.com/tkg/envoy@sha256:a941610f98381083870953ede2d14d90b52c16f9b9f72b3158ccbbdf8c0bd608 Annotations: kbld.carvel.dev/id: docker.io/envoyproxy/envoy:v1.29.2 kbld.carvel.dev/origins: - resolved: tag: v1.29.2_vmware.1 url: projects.registry.vmware.com/tkg/envoy:v1.29.2_vmware.1 Succeeded ``` - The bundle digest (`Bundle SHA: sha256:ac665d3d…3861be`) is the object you signed in Step 3. - The **`Type: Signature`** row (`sha256:824f983f…ed6d2`) is the cosign signature object co-located in the same repo. Its `tag:` annotation (`sha256-ac665d3d…3861be.sig`) is the conventional cosign signature tag derived from the bundle digest — that is the same tag pattern you pass to `crane manifest …:` in Step 4. - The **`Type: Internal`** row is the bundle's internal lockfile / manifest object; you don't sign it directly. - Remaining **`Type: Image`** rows are the workload images recorded in `.imgpkg/images.yml`. They show their `Origin` (where they were pulled from at build time) and `kbld.carvel.dev/origins` (the original tag your manifests referenced before kbld pinned the digest). </details> #### Using `crane` ```bash crane manifest /:sha256-.sig ``` #### Using `cosign` ```bash cosign download signature /@ ``` ## Step 5: Relocate the signed bundle When you copy the bundle to another registry, **copy the cosign signature objects too**. Without `--cosign-signatures`, the bundle arrives **unsigned** and the Supervisor treats it as untrusted. ```bash imgpkg copy \ -b /@ \ --to-repo / \ --cosign-signatures ``` Through a tarball (typical for moving across networks): ```bash imgpkg copy -b /@ --to-tar bundle-signed.tar --cosign-signatures imgpkg copy --tar bundle-signed.tar --to-repo / --cosign-signatures ``` Watch the progress lines for **`(cosign signature)`** — that confirms the signature layer was included in the copy. After relocation, repeat the verification step against the destination registry to confirm the signature is present at the new location: ```bash imgpkg describe -b /@ ``` You should see a `Type: Signature` row tagged `sha256-.sig` matching your bundle digest. ## Common pitfalls - **Signing by tag instead of digest.** Tags are mutable; if the tag is later moved to point at a different bundle, the signature will not match what customers pull. Always sign and publish using `@sha256:`. - **Forgetting `--cosign-signatures` during relocation.** `imgpkg copy` does not transfer cosign signature objects unless this flag is passed. The bundle arrives at the destination registry intact but unsigned, and the Supervisor will treat it as untrusted. - **Expired leaf certificate.** The Supervisor does not consult a trusted timestamp service; once the leaf certificate's `notAfter` has passed, verification fails even if the signature was valid when produced. Plan signing-cert rotation accordingly. - **Custom CA not in the Supervisor trust pool.** The Supervisor accepts only signatures that chain to a root it already trusts. See [Trust and certificate chains](#trust-and-certificate-chains).