Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Signing Packages

This guide explains how Cargoship signs and verifies packages. It covers key pair generation, the three points where you can sign, and verification.

A Cosign key pair is the only signing method. Keyless (Fulcio/OIDC) signing and verification, and cloud KMS key references, were removed: all of them need to reach a cloud endpoint, and the management node Cargoship runs from is air-gapped. If you need keyless back, say so on issue #848.

What Gets Signed

A Cargoship package is a .tar.zst archive. It contains a distro.yaml manifest, a checksums.txt file, and the image and file layers.

Cargoship signs distro.yaml only. The signing step sets build.signed: true in the manifest. It then signs the manifest bytes with Cosign and writes the signature beside the manifest as distro.bundle.sig, a Sigstore bundle.

One signature covers the whole package. distro.yaml records metadata.aggregateChecksum, the checksum of checksums.txt. checksums.txt lists a checksum for every layer in the package. Signing does not modify the checksums.

Signing is optional. Unsigned packages build, publish, pull, and apply normally. Verification then has nothing to check.

Generating a Cosign Key Pair

Cargoship does not generate keys. It uses keys in Cosign’s format. Use the cosign CLI:

cosign generate-key-pair

The command prompts for a password. It writes two files into the current directory:

  • cosign.key – the encrypted private key. Keep this file secret. Treat it like any other signing credential.
  • cosign.pub – the public key. Give this file to everyone who verifies your packages.

To skip the prompt in CI, set the password in the environment first:

COSIGN_PASSWORD='...' cosign generate-key-pair

Restrict the private key file to the signing user:

chmod 600 cosign.key

Keeping the Key Off Disk

--signing-key accepts a local file path, or env://NAME to read the PEM out of an environment variable. The environment form keeps the private key off the build host’s filesystem, which is what a CI pipeline wants.

export COSIGN_PRIVATE_KEY="$(cat cosign.key)"
export COSIGN_PASSWORD=...
cargoship sign ./my-package.tar.zst --signing-key env://COSIGN_PRIVATE_KEY

--key takes the same two forms when verifying, so a public key can come from env://COSIGN_PUBLIC_KEY as well as from a file.

Cloud KMS references – awskms://, gcpkms://, azurekms://, hashivault://, openbao:// – are not supported. Each resolves a key by reaching a cloud endpoint, which an air-gapped management node cannot do. Passing one fails naming the scheme rather than reporting a missing file.

Signing a Package

You can sign a package at three points. Each point accepts the same --signing-key and --signing-key-pass flags.

At Create Time

cargoship create ./distro-defs --signing-key ./cosign.key

Cargoship prompts for the key password when you omit --signing-key-pass. Use --confirm to skip the prompt in a non-interactive environment. The password must then come from --signing-key-pass, the config file, or the environment.

cargoship create ./distro-defs --signing-key ./cosign.key --signing-key-pass "$COSIGN_PASSWORD" --confirm

At Publish Time

publish signs an unsigned package as it pushes to a registry. It also re-signs a signed package with a different key.

cargoship publish ./build/cargoship-rancher-rke2-amd64-1.0.0.tar.zst oci://ghcr.io/my-org \
  --signing-key ./cosign.key --confirm

Use this form for a two-stage pipeline. One job builds the package. A second job holds the key material and signs.

As a Separate Step

cargoship sign signs an existing package without a rebuild. The source is a local tarball or an OCI reference.

cargoship sign cargoship-rancher-rke2-amd64-1.0.0.tar.zst --signing-key ./cosign.key

--output sets the destination of the signed package. Cargoship applies a default when you omit the flag. For a local source, the default is the source directory. For an OCI source, the default is the repository part of the source reference. The output can also be an OCI URL. Cargoship then signs and publishes in one step.

cargoship sign oci://ghcr.io/my-org/my-package:1.0.0 --signing-key ./cosign.key --output ./signed/

cargoship sign ./my-package.tar.zst --signing-key ./cosign.key --output oci://ghcr.io/my-org/signed-packages

Re-signing

Cargoship refuses to sign a package that already has a signature. It reports package is already signed, use --overwrite to re-sign. Use --overwrite to replace the existing signature.

cargoship sign ./my-package.tar.zst --signing-key ./new-cosign.key --overwrite

Add --verify=always --key ./old-cosign.pub to check the existing signature against the old key first. Use this form for key rotation. It proves the package is the one you expect before the new signature replaces the old one.

Verifying a Package

Every command that loads a package accepts --verify: sign, publish, pull, apply, prepare, and engine-config-sync. The flag takes one of three modes.

ModeBehavior
neverCargoship skips signature verification.
if-possibleDefault. Cargoship verifies when the package has a signature and you supply verification material. It tolerates a package with nothing to verify against. Every other failure is fatal, including a tampered signature or a wrong key.
alwaysCargoship requires a successful verification. It fails when the package is unsigned. It also fails when you supply no verification material.

Key-Based Verification

Pass the public key with --key or -k.

cargoship pull oci://ghcr.io/my-org/my-package:1.0.0 --verify=always --key ./cosign.pub

Verification on the Install Path

apply, prepare, and engine-config-sync verify the package before they touch any host. They accept the same flags as pull, and they read the same config keys.

cargoship apply ./cargoship-distro-amd64.tar.zst --config ./cluster.yaml --confirm \
  --verify=always --key /etc/cargoship/cosign.pub

Set verify: always and public_key in the config file to apply this policy to every install on the host. See Configuration File and Environment Variables.

Verification runs while Cargoship loads the package. A failure stops the command before it configures a node.

Configuration File and Environment Variables

Set signing and verification options in cargoship-config.yaml to avoid repeating them on every command. The distro.verify and distro.public_key keys apply to every command that loads a package, including apply.

distro:
  # verification defaults, applied to any command that loads a package
  verify: always
  public_key: /etc/cargoship/cosign.pub
  publish:
    signing_key: /etc/cargoship/cosign.key
    signing_key_password: hunter2

Every key also reads from the environment. Use the DISTRO_ prefix and the full config path, with each . replaced by _.

export DISTRO_DISTRO_PUBLISH_SIGNING_KEY=./cosign.key
export DISTRO_DISTRO_PUBLISH_SIGNING_KEY_PASSWORD="$COSIGN_PASSWORD"
export DISTRO_DISTRO_PUBLIC_KEY=./cosign.pub

Note the doubled prefix. DISTRO_ is the environment prefix. The second DISTRO is the distro section of the config file.

Command-line flags take precedence over the config file and the environment.

signing_key_password in the config file puts a plaintext secret on disk. Use the environment variable instead, and env:// for the key itself, so neither the key nor its password is written to the build host.

Use this pattern for a pipeline that builds and signs with a stored key.

env:
  COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
  COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}

# ...

- run: |
    cargoship create ./distro-defs -o ./build \
      --signing-key env://COSIGN_PRIVATE_KEY \
      --signing-key-pass "$COSIGN_PASSWORD" \
      --confirm

    cargoship publish ./build/cargoship-*.tar.zst oci://ghcr.io/my-org \
      --verify=always --key ./cosign.pub

--verify=always on the publish step matters. The step fails instead of pushing a package whose signature does not validate against the public key you distribute.