OCI Signature Verification and Trust Policies¶
Experimental Feature
OCI artifact support is currently experimental. Please provide feedback and report any issues you encounter.
This page provides comprehensive documentation on configuring OCI artifact signature verification in doco-cd using trust policies.
Overview¶
OCI Signature Verification ensures that deployment artifacts are signed by trusted entities before deployment. This is a security best practice that prevents:
- Unauthorized deployments - Only signed artifacts can be deployed
- Tampering - Modified artifacts would have invalid signatures
- Compromised registries - Artifacts from compromised registries without valid signatures are rejected
Signature verification is disabled by default
Verification only runs when explicitly enabled via global configuration or per-deployment override.
Supported Signature Methods¶
Doco-cd supports two signature verification methods:
- Public Key Signatures - Traditional PKI with public/private key pairs
- Keyless Signatures - OIDC-based verification (e.g., GitHub Actions, Google Service Accounts)
Both can be used together in a single trust policy.
Trust Policy Schema¶
enabled: true # Enable/disable verification
public_keys: # List of trusted public keys (PEM format)
- |
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
keyless_identities: # Keyless verification using OIDC
- issuer: https://issuer.url # OIDC issuer URL
subject: https://github.com/org/repo/.github/workflows/build.yml@refs/heads/main # Exact certificate SAN match (optional)
subject_regexp: ^https://github.com/org/repo/.+@refs/heads/main$ # Regex SAN match (optional)
Available Options¶
Global configuration¶
| Key | Type | Description |
|---|---|---|
OCI_TRUST_POLICY |
string | YAML-formatted OCI signature trust policy for verifying artifact signatures. Supports public keys and keyless (OIDC) identities. Verification is disabled unless enabled: true is set. |
OCI_TRUST_POLICY_FILE |
string | Path to the file containing OCI trust policy YAML (Mutually exclusive with OCI_TRUST_POLICY). |
OCI_VERIFY_MAX_WORKERS |
number | Maximum number of workers used per OCI signature verification. Values below 1 are invalid. Values above 10 are clamped to 10. |
Verification Parallelism and Memory Usage
OCI_VERIFY_MAX_WORKERS applies per OCI verification call.
Combined with MAX_CONCURRENT_DEPLOYMENTS, total verification parallelism can grow roughly with both settings.
A higher number of workers can speed up verification for artifacts with many signatures or complex policies, but also increases memory usage.
For example:
MAX_CONCURRENT_DEPLOYMENTS=4OCI_VERIFY_MAX_WORKERS=2
can allow up to roughly 8 concurrent verification workers in the worst case.
Trust Policy Fields¶
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | false |
Enables OCI signature verification globally. When false, verification is skipped. |
public_keys |
array of strings | [] |
List of trusted public keys (PEM format) used by Cosign key verification. |
keyless_identities |
array of object | [] |
List of trusted keyless identities used for OIDC-based verification. |
keyless_identities object fields¶
| Key | Type | Description |
|---|---|---|
issuer |
string | OIDC issuer URL (for example https://token.actions.githubusercontent.com). |
subject |
string | Exact certificate identity (SAN) match (for example https://github.com/org/repo/.github/workflows/build.yml@refs/heads/main). Mutually exclusive with subject_regexp. |
subject_regexp |
string | Regular expression for certificate identity (SAN) matching. Useful when workflow file paths or refs may vary. Mutually exclusive with subject. |
Docker Compose escaping for subject_regexp
If you set OCI_TRUST_POLICY inside docker-compose.yml, Docker Compose treats$ characters as variable interpolation
and removes it before passing the value to doco-cd.
Use $$ (double-dollar sign) in regex patterns so a literal $ reaches doco-cd.
Example in docker-compose.yml:
Configuring Global Trust Policy
Provide the trust policy directly as a YAML string:
For complex policies or sensitive data, use OCI_TRUST_POLICY_FILE with a file:
Per-deployment override¶
Override the global trust policy for specific deployments:
| Key | Type | Default | Description |
|---|---|---|---|
verify |
boolean | unset | Overrides global enabled for this deployment only. true enforces verification, false disables it. |
public_keys |
array of strings | inherit | Replaces global public_keys when set and non-empty. |
keyless_identities |
array of objects | inherit | Replaces global keyless_identities when set and non-empty. |
Behavior
- If
verifyis unset, the deployment inherits the globalenabledvalue. Ifverify: trueis set, verification is enforced regardless of the global setting. - If both
public_keysandkeyless_identitiesare empty while verification is enabled, verification fails because no trust rules are defined. - Per-deployment
public_keysandkeyless_identitiesoverride global values only when the respective lists are non-empty.
Per-deployment Override Example
deployments:
- name: production
compose_file: docker-compose.yml
oci:
verify: true
public_keys:
- |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
keyless_identities:
- issuer: https://token.actions.githubusercontent.com
subject: https://github.com/myorg/config:ref:refs/heads/main
Enabling/Disabling Verification¶
Public Key Signatures¶
Use public keys for verifying artifacts signed with private keys.
Generating Key Pairs¶
Configuring Public Keys¶
OCI_TRUST_POLICY: |
enabled: true
public_keys:
- |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7bxKm8YvAjGmqKlWaIuQpQ...
-----END PUBLIC KEY-----
- |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAnPYz...
-----END PUBLIC KEY-----
- |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAXkL9F...
-----END PUBLIC KEY-----
Signing Artifacts with Cosign¶
Cosign is a popular tool for signing OCI artifacts. Use the private key to sign your artifact and the public key for verification.
# Sign with private key
cosign sign --key private.pem ghcr.io/myorg/config:latest
# Verify signature
cosign verify --key public.pem ghcr.io/myorg/config:latest
Keyless Identities (OIDC)¶
Use keyless verification for artifacts signed via OIDC providers like GitHub Actions or Google Service Accounts. This eliminates the need for managing public keys and allows dynamic trust based on identity claims.
OIDC Basics¶
Keyless identities verify that:
- An OIDC provider (issuer) issued the signature
- The subject (identity) matches expectations
GitHub Actions¶
Verify artifacts signed by GitHub Actions workflows:
OCI_TRUST_POLICY: |
enabled: true
keyless_identities:
- issuer: https://token.actions.githubusercontent.com
subject_regexp: ^https://github.com/myorg/config/.+@refs/heads/main$
Subject examples:
https://github.com/owner/repo/.github/workflows/build.yml@refs/heads/main- Exact workflow and branchhttps://github.com/owner/repo/.github/workflows/release.yml@refs/heads/main- Exact workflow and tag
Subject regexp examples:
^https://github.com/owner/repo/.+@refs/heads/main$- Any workflow file onmain^https://github.com/owner/repo/.+@refs/tags/v.*$- Any workflow file for version tags
Multiple OIDC Providers¶
OCI_TRUST_POLICY: |
enabled: true
keyless_identities:
# GitHub Actions CI/CD
- issuer: https://token.actions.githubusercontent.com
subject_regexp: ^https://github.com/myorg/config/.+@refs/heads/main$
# Google Service Account
- issuer: https://accounts.google.com
subject: config-signer@company.iam.gserviceaccount.com
Signing with Cosign (GitHub Actions)¶
name: Build and Sign
permissions:
contents: read
packages: write
id-token: write # needed for signing the images with GitHub OIDC Token
on:
push:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
- name: Install Cosign
uses: sigstore/cosign-installer@v4.1.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.11.1
- name: Login to GitHub Container Registry
uses: docker/login-action@v3.4.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: docker_meta
uses: docker/metadata-action@v5.7.0
with:
images: ghcr.io/my/app # (1)!
tags: type=sha,format=long
- name: Build and Push container images
uses: docker/build-push-action@v6.18.0
id: build-and-push
with:
platforms: linux/amd64
push: true
tags: ${{ steps.docker_meta.outputs.tags }}
- name: Sign OIDC artifact
env:
DIGEST: ${{ steps.build-and-push.outputs.digest }}
TAGS: ${{ steps.docker_meta.outputs.tags }}
run:
images="";
for tag in ${TAGS}; do
images+="${tag}@${DIGEST} ";
done;
cosign sign --yes ${images}
- Change to your image name.
Examples¶
GitHub Actions Keyless Signing¶
Production setup using GitHub Actions to sign artifacts:
services:
doco-cd:
environment:
OCI_TRUST_POLICY: |
enabled: true
keyless_identities:
- issuer: https://token.actions.githubusercontent.com
subject: repo:kimdre/doco-cd:ref:refs/heads/main
POLL_CONFIG: |
- source: oci
url: ghcr.io/kimdre/doco-cd-config:main
reference: main
interval: 300
deployments:
- name: production
compose_file: docker-compose.yml
Progressive Rollout¶
Different verification levels for different environments:
POLL_CONFIG: |
- source: oci
url: ghcr.io/myorg/config:main
reference: main
interval: 300
deployments:
# Development: No verification required
- name: development
compose_file: docker-compose.yml
oci:
verify: false
# Staging: Single signer required
- name: staging
compose_file: docker-compose.yml
oci:
verify: true
public_keys:
- |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE... (Staging Lead)
-----END PUBLIC KEY-----
# Production: Multiple signers or GitHub Actions
- name: production
compose_file: docker-compose.yml
oci:
verify: true
public_keys:
- |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE... (Head of DevOps)
-----END PUBLIC KEY-----
- |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE... (Compliance Officer)
-----END PUBLIC KEY-----
keyless_identities:
- issuer: https://token.actions.githubusercontent.com
subject: repo:myorg/config:ref:refs/tags/v*
Troubleshooting¶
Verification Failed: "Invalid Signature"¶
Cause: Artifact signature doesn't match public key or was corrupted
Solutions:
- Verify artifact was actually signed:
cosign verify --key public.pem artifact:tag - Check that you're using the correct public key in valid PEM format
- Ensure artifact hasn't been modified since signing
- Try disabling verification temporarily:
verify: false
Verification Failed: "No Matching Identity"¶
Cause: OIDC subject doesn't match expected pattern
Solutions:
- Check OIDC issuer URL is correct
- Verify subject pattern matches (wildcards, etc.)
- Check certificate subject matches pattern
- Print certificate details:
cosign verify ghcr.io/myorg/image:tag
Public Key Format Error¶
Cause: Public key is not in valid PEM format
Solutions:
- Verify key starts with
-----BEGIN PUBLIC KEY----- - Check key ends with
-----END PUBLIC KEY----- - Ensure proper indentation (no leading spaces)
- Validate with:
openssl pkey -in public.pem -text -noout
Artifact Passes Verification But Shouldn't¶
Cause: Trust policy is not enabled or is misconfigured
Solutions:
- Check
enabled: truein policy - Verify public keys or keyless identities are configured
- Check deployment-level overrides aren't disabling verification
- Review logs for detailed verification status
No Signatures Found on Artifact¶
Cause: Artifact was not signed before pushing
Solutions:
- Sign artifact before pushing:
cosign sign --key private.pem ghcr.io/myorg/image:tag - Update your CI/CD pipeline to sign artifacts
- Verify signature exists:
cosign verify --key public.pem ghcr.io/myorg/image:tag
OIDC Certificate Verification Failed¶
Cause: OIDC provider certificate is invalid or issuer URL is wrong
Solutions:
- Verify OIDC issuer URL is accessible and correct
- Check issuer certificate is valid
- Ensure your system time is correct (certificate validity window)
- For GitHub Actions, issuer must be exactly:
https://token.actions.githubusercontent.com
Best Practices¶
- Enable verification in production - Always verify signatures for production deployments
- Use keyless for CI/CD - Keyless identities reduce key management burden
- Separate keys by role - Use different keys for different teams/environments
- Rotate keys regularly - Plan for key rotation and maintain key history
- Document your policy - Maintain clear documentation of your trust setup
- Test before enforcing - Test verification with
verify: falsefirst - Monitor verification failures - Alert on signature verification failures
- Use version tags for releases - Sign releases with specific version tags