Skip to content

Package Signing

Zarf supports cryptographic signing and verification of packages using Cosign. Two signing modes are supported: key-based signing using a local private key or cloud KMS, and keyless signing using a Sigstore OIDC identity (no private key required). Package signing provides:

  • Authenticity: Verify that a package comes from a trusted source
  • Integrity: Ensure the package has not been modified or corrupted
  • Non-repudiation: Prove who signed the package and when

Zarf signs the zarf.yaml file within a package, which contains metadata and checksums for all package contents. This allows verification of the entire package through a single signature.

Packages are signed using the Sigstore bundle format, stored as zarf.bundle.sig within the package. This format provides:

  • Offline Verification: Bundles include all verification materials in a single file
  • Standardized Format: Based on the widely-adopted Sigstore specification
  • Better interoperability: Compatible with other Sigstore tooling and services

The bundle format follows the Sigstore Bundle Specification.

The most common approach uses local private/public key pairs:

Terminal window
# Generate a key pair (prompts for password)
zarf tools gen-key
# Creates:
# - cosign.key (private key - keep this secure!)
# - cosign.pub (public key - share for verification)

For production environments, cloud-based KMS provides enhanced security and key management:

Terminal window
zarf package sign zarf-package-example-amd64.tar.zst \
--signing-key awskms://alias/my-signing-key

Requires AWS credentials configured and appropriate IAM permissions. The KMS key must be an asymmetric signing key.

Terminal window
zarf package sign zarf-package-example-amd64.tar.zst \
--signing-key gcpkms://projects/PROJECT/locations/LOCATION/keyRings/RING/cryptoKeys/KEY

Requires gcloud authentication and appropriate IAM roles. The key must be an asymmetric signing key.

Terminal window
zarf package sign zarf-package-example-amd64.tar.zst \
--signing-key azurekms://VAULT_NAME.vault.azure.net/keys/KEY_NAME/KEY_VERSION

Requires Azure CLI authentication and appropriate access policies. The key must be an asymmetric signing key.

Terminal window
zarf package sign zarf-package-example-amd64.tar.zst \
--signing-key hashivault://KEY_NAME

Requires Vault token authentication and appropriate policies. The transit key must be an asymmetric signing key.

Keyless signing uses Sigstore’s Fulcio certificate authority to issue a short-lived signing certificate bound to your OIDC identity (GitHub Actions workflow, Google account, etc.).

When to use keyless signing:

  • CI/CD pipelines (GitHub Actions, GitLab CI) where managing a private key is impractical
  • Signing official release artifacts with an identity pinned to the specific workflow that ran
  • Organizations that want to eliminate private key management entirely

The command is generally the same whether running interactively or in GitHub Actions — zarf detects the OIDC environment automatically:

Terminal window
zarf package sign zarf-package-example-amd64.tar.zst --keyless --confirm

The --confirm flag skips the prompt before uploading to the Rekor transparency log. --tlog-upload is automatically enabled with --keyless. When run interactively, the command opens a browser for OIDC login. In GitHub Actions, zarf uses the ambient OIDC token — ensure the workflow job has id-token: write permission:

permissions:
id-token: write
contents: read

Other non-interactive environments where OIDC auto-detection does not apply, supply a pre-acquired token with --identity-token. The flag accepts either a raw token value or a path to a file containing one:

Terminal window
# Pass the token value directly (register it as a masked secret in your CI platform)
zarf package sign zarf-package-example-amd64.tar.zst \
--keyless \
--identity-token "$MY_OIDC_TOKEN" \
--confirm
# Or pass a path to a file containing the token (e.g., written by a prior step or secret manager)
zarf package sign zarf-package-example-amd64.tar.zst \
--keyless \
--identity-token /path/to/oidc-token \
--confirm

Ensure the token value is registered as a masked secret in your CI platform so it is redacted from log output.

Automatically sign a package during creation:

Terminal window
zarf package create . --signing-key cosign.key --signing-key-pass <password>

The signature is embedded in the package archive during the build process.

Sign a package after it has been created:

Terminal window
zarf package sign zarf-package-example-amd64.tar.zst --signing-key cosign.key

The signature is added to the package archive, and the zarf.yaml is updated to indicate it is signed.

Replace an existing signature (useful for key rotation):

Terminal window
zarf package sign zarf-package-example-amd64.tar.zst \
--signing-key new-cosign.key \
--overwrite

The --overwrite flag is required when a signature already exists.

Zarf supports signing packages stored in OCI registries:

Terminal window
# Sign a package from OCI and output to local directory
zarf package sign oci://ghcr.io/my-org/my-package:1.0.0 \
--signing-key cosign.key \
--output ./signed/
# Sign a package and publish directly to OCI registry
zarf package sign zarf-package-example-amd64.tar.zst \
--signing-key cosign.key \
--output oci://ghcr.io/my-org/signed-packages
# Sign a package from OCI and re-publish to OCI (in place)
zarf package sign oci://ghcr.io/my-org/my-package:1.0.0 \
--signing-key cosign.key

Key-based signing — verify using the public key:

Terminal window
zarf package verify zarf-package-example-amd64.tar.zst --key cosign.pub

Keyless signing — verify using the expected OIDC identity of the signer. Use --certificate-identity for an exact match or --certificate-identity-regexp to match a pattern (useful when the identity encodes a workflow path and ref):

Terminal window
# Exact identity match (e.g., a service account email)
zarf package verify zarf-package-example-amd64.tar.zst \
--certificate-identity "signer@example.com" \
--certificate-oidc-issuer "https://accounts.google.com"
# Regexp match — pins the identity to a specific workflow and ref type
zarf package verify zarf-package-example-amd64.tar.zst \
--certificate-identity-regexp "https://github.com/my-org/my-repo/.github/workflows/release.yml@refs/tags/" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"

Using --certificate-identity-regexp prevents accepting packages signed by an unrelated pipeline in the same repository. For the official Zarf init package (signed during the release workflow):

Terminal window
zarf package verify zarf-init-amd64-vX.Y.Z.tar.zst \
--certificate-identity-regexp "https://github.com/zarf-dev/zarf/.github/workflows/release.yml@refs/tags/" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"

Successful verification output:

2025-11-15 14:17:13 INF checksum verification status=PASSED
Verified OK
2025-11-15 14:17:16 INF signature verification status=PASSED
2025-11-15 14:17:16 INF verification complete status=SUCCESS

If verification fails, the command exits with a non-zero status code and displays an error message.

Zarf embeds Sigstore verification material (a TrustedRoot JSON) directly in the binary. This enables zarf package verify to verify keyless-signed packages offline without reaching Sigstore’s TUF infrastructure — no extra flags required.

If you need fresher trust material (e.g., after a Sigstore key rotation), retrieve a new TrustedRoot and pass it in at verify time:

Terminal window
# Retrieve a fresh TrustedRoot from Sigstore
zarf tools trusted-root create --with-default-services > trusted-root.json
# Use it during verification
zarf package verify zarf-package-example-amd64.tar.zst \
--trusted-root trusted-root.json \
--certificate-identity-regexp "https://github.com/my-org/my-repo/.github/workflows/release.yml@refs/tags/" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"

Users running private Sigstore infrastructure can supply their own TrustedRoot the same way via --trusted-root /path/to/custom.json.

Air-gap environments: when a keyless package was signed with --tlog-upload (the default for --keyless), the Rekor inclusion proof is embedded in the bundle. Combined with the embedded TrustedRoot (which includes Rekor’s public key), tlog verification works fully offline — no extra flags required.

If instead the package was signed without tlog (using --tsa-server-url --tlog-upload=false), the bundle contains a TSA timestamp instead of a tlog entry. When using keyless identity flags, Zarf auto-enables tlog verification — explicitly disable it and enable timestamp verification:

Terminal window
zarf package verify zarf-package-example-amd64.tar.zst \
--certificate-identity "signer@example.com" \
--certificate-oidc-issuer "https://accounts.google.com" \
--insecure-ignore-tlog \
--use-signed-timestamps

Verify package integrity without signature verification:

Terminal window
zarf package verify zarf-package-example-amd64.tar.zst

This confirms that package files match their expected checksums but does not verify authenticity or source.

Verify signatures during package deployment:

Terminal window
zarf package deploy zarf-package-example-amd64.tar.zst --key cosign.pub --verify

If signature verification fails and --verify is specified, Zarf aborts the deployment to prevent deploying potentially compromised packages.

  1. Protect private keys: Store private keys securely with restricted file system permissions
  2. Use strong passwords: Encrypt private keys with strong, unique passwords
  3. Consider KMS or keyless for production: Use cloud KMS for centralized key management, or use keyless signing to eliminate private key management entirely
  4. Document key locations: Maintain clear documentation of where keys are stored and who has access
  1. Verify before deployment: Always verify package signatures before deploying to critical environments
  2. Automate verification: Integrate signature verification into CI/CD pipelines
  3. Enforce verification: Use --verify flag during deployment to make verification mandatory
  4. Distribute public keys securely: Establish a trusted channel for distributing public keys
  5. Maintain key inventory: Keep records of which keys were used to sign which packages
  1. Prefer keyless in CI/CD: Keyless signing eliminates private key management risk — use it for automated pipelines
  2. Pin the workflow identity: Use --certificate-identity-regexp to restrict verification to packages signed by a specific workflow and ref type, not just any signer in the repository
  3. Use TSA timestamps when anonymity is required: Rekor is a public transparency log — if publishing your signing event is a concern, use --tsa-server-url --tlog-upload=false to embed an RFC3161 timestamp instead
  1. Never commit private keys: Exclude private keys from version control
  2. Audit signature operations: Log all signing and verification operations
  3. Validate key sources: Verify public keys come from trusted sources before using them
  4. Monitor for warnings: Pay attention to deprecation warnings about signature formats
  5. Plan for key compromise: Have a procedure for responding to compromised signing keys
✖ failed to verify signature: invalid signature when validating ASN.1 encoded signature

Solution: Verify you are using the correct public key for verification.

✖ package is signed but no key was provided

Solution: Provide the public key when deploying or inspecting a signed package:

Terminal window
zarf package deploy zarf-package-example-amd64.tar.zst --key cosign.pub
ERR failed to sign package: failed to sign package: reading key: decrypt: encrypted: decryption failed

Solution: Verify the password is correct. Use --signing-key-pass to provide the password:

Terminal window
zarf package sign zarf-package-example-amd64.tar.zst \
--signing-key cosign.key \
--signing-key-pass <correct-password>
✖ signature verification failed: invalid signature

Possible causes:

  1. Wrong public key: The public key doesn’t match the private key used to sign
  2. Package modified: The package contents have been altered after signing
  3. Corrupted package: The package file is corrupted or incomplete
  4. Format mismatch: Using wrong verification method for signature format

Solutions:

  • Verify you’re using the correct public key that corresponds to the signing key
  • Re-download the package if corruption is suspected
  • Check package checksums to verify integrity
  • Inspect the package to confirm which signature format it uses
✖ failed to sign package: kms authentication failed

Solutions:

  • Verify cloud provider credentials are configured correctly
  • Ensure the KMS key exists and is accessible
  • Verify IAM permissions allow signing operations
  • Check that the key is an asymmetric signing key (not encryption key)
  • Refer to your KMS provider’s documentation for authentication setup

Symptom: zarf package sign --keyless hangs or fails trying to open a browser in a CI environment.

Solution: In CI environments that don’t expose OIDC tokens automatically, supply the token directly:

Terminal window
zarf package sign zarf-package-example-amd64.tar.zst \
--keyless \
--identity-token "$MY_OIDC_TOKEN" \
--confirm

For GitHub Actions specifically, ensure id-token: write is set in the job’s permissions block — cosign will acquire the token automatically.

Symptom: Verification fails with an identity mismatch error even though the package was signed correctly.

Solution: The value passed to --certificate-identity or --certificate-identity-regexp must match what was encoded in the Fulcio certificate at signing time. For GitHub Actions, the identity takes the form https://github.com/<org>/<repo>/.github/workflows/<workflow>.yml@<ref>. Use --certificate-identity-regexp with a partial pattern if the exact ref is variable.

Symptom: Verification of a keyless-signed package fails after ~10 minutes with a certificate expiry error.

Cause: The package was signed without --tlog-upload, so there is no Rekor inclusion proof to extend verifiability past the Fulcio certificate’s short validity window.

Solution: Re-sign the package with tlog upload enabled (the default when --keyless is set) or with a TSA timestamp (--tsa-server-url).