Skip to main content

How to choose a signer

Cilock supports nine signer providers. This guide is the decision tree, pick the path that matches your environment, then jump to the relevant config detail.

TL;DRโ€‹

EnvironmentPick this
Hosted CI with OIDC (GitHub Actions, GitLab CI)fulcio (Sigstore keyless)
Workloads inside a SPIFFE/SPIRE service meshspiffe
AWS-native, want an HSM-backed identitykms/aws
GCP-nativekms/gcp
Azure-nativekms/azure
HashiCorp Vault user with the Transit enginevault-transit
HashiCorp Vault user, simple key-stored modevault
Local dev / smoke tests / CI without OIDCfile
Internal cilock developmentdebug-signer

The decision treeโ€‹

Is the workload running in CI with an OIDC token available?
โ”œโ”€โ”€ Yes โ†’ Is it inside a SPIFFE/SPIRE mesh?
โ”‚ โ”œโ”€โ”€ Yes โ†’ spiffe
โ”‚ โ””โ”€โ”€ No โ†’ fulcio (default for GitHub Actions, GitLab CI)
โ””โ”€โ”€ No โ†’ Is it inside a cloud you already have KMS in?
โ”œโ”€โ”€ AWS โ†’ kms/aws
โ”œโ”€โ”€ GCP โ†’ kms/gcp
โ”œโ”€โ”€ Azure โ†’ kms/azure
โ””โ”€โ”€ No โ†’ Are you using HashiCorp Vault?
โ”œโ”€โ”€ Yes, with Transit engine โ†’ vault-transit
โ”œโ”€โ”€ Yes, key-stored โ†’ vault
โ””โ”€โ”€ No โ†’ file (and consider whether you should be in CI at all)

Why prefer keyless?โ€‹

Both fulcio and spiffe issue short-lived certificates tied to runtime identity. There's no long-lived private key to leak, no rotation schedule to forget, no credential to exfiltrate that's useful from an attacker's laptop.

The two recent supply-chain attacks cilock catches at scale (Trivy March 2026, LiteLLM March 2026) both worked by exfiltrating exactly the kind of long-lived credentials that file/KMS signing requires you to store. Keyless signing eliminates that attack surface entirely for the signing keys themselves.

If you can do keyless, do keyless.

Fulcio specificsโ€‹

The Sigstore CA. Most teams using GitHub Actions or GitLab CI should pick this.

cilock run --step build \
--signer-fulcio-url "$FULCIO_URL" \
--signer-fulcio-oidc-issuer "https://token.actions.githubusercontent.com" \
--signer-fulcio-oidc-client-id sigstore \
--signer-fulcio-token "$OIDC_TOKEN" \
...

In GitHub Actions, the cilock-action defaults all of this for you when enable-sigstore: true (the action's default). You only need permissions: { id-token: write } on the workflow.

For self-hosted Fulcio, set --signer-fulcio-url to your instance. The client defaults to HTTP/REST (--signer-fulcio-use-http=true); set --signer-fulcio-use-http=false to switch to gRPC.

SPIFFE/SPIRE specificsโ€‹

For workloads that already have SVID identities from a SPIRE agent. The signer fetches an SVID from the agent's workload API socket and uses the cert as the signing identity.

cilock run --step build \
--signer-spiffe-socket-path "unix:///run/spire/sockets/agent.sock" \
...

The socket path must have a unix:// or tcp:// scheme; the bare filesystem path is not accepted.

Pair with policy certConstraint.uris containing your SPIFFE ID:

{
"type": "root",
"certConstraint": {
"commonname": "*",
"dnsnames": ["*"],
"emails": ["*"],
"organizations": ["*"],
"uris": ["spiffe://example.com/build"],
"roots": ["*"]
}
}

KMS specificsโ€‹

KMS signers use a URI-based reference scheme borrowed from Sigstore Cosign. The same URI works as the --signer-kms-ref flag and as the publickeyid in a policy:

ProviderURI form
AWSawskms:///arn:aws:kms:<region>:<account>:key/<key-id> (note the triple slash; the alternative awskms://[endpoint]/[ID/ALIAS/ARN] lets you target a non-default endpoint such as LocalStack)
GCPgcpkms://projects/<proj>/locations/<loc>/keyRings/<ring>/cryptoKeys/<key>/cryptoKeyVersions/<v> (/versions/<v> shorthand also accepted; the whole version suffix is optional and resolves to the key's primary version)
Azureazurekms://<vault-name>.vault.azure.net/<key>[/<key-version>]
cilock run --step build \
--signer-kms-ref "awskms:///arn:aws:kms:us-east-2:111122223333:key/1234abcd-..." \
...

Authentication uses each cloud provider's standard credential resolution, no extra cilock config needed. For full flag detail (custom endpoints, profile selection, etc.) see signing & identity.

For air-gapped verification where the KMS service isn't reachable from the verifier, embed the base64-encoded PEM public key directly in the policy alongside the publickeyid URI.

Vault specificsโ€‹

Two distinct Vault signers, two different flag namespaces, two different Vault engines. Pick by engine:

vault-transit uses Vault's Transit secrets engine for signing-as-a-service. The key stays in Vault; cilock asks Vault to sign the payload digest. Configure via the kms-hashivault provider:

cilock run --step build \
--signer-kms-ref "hashivault://my-signing-key" \
--signer-kms-hashivault-addr "https://vault.internal:8200" \
--signer-kms-hashivault-token-file /run/secrets/vault-token \
...

This calls PUT /v1/transit/sign/<key>/<hash> under the hood (verified by tracing a failed run against a nonexistent host).

vault uses Vault's PKI secrets engine. Each invocation asks Vault to issue a short-lived X.509 certificate for the role, similar in spirit to Fulcio:

cilock run --step build \
--signer-vault-url "https://vault.internal:8200" \
--signer-vault-token "$VAULT_TOKEN" \
--signer-vault-role build-signer \
...

This calls POST /v1/pki/issue/<role> under the hood. Pick this when you want Vault-managed identity but cert-based rather than long-lived-key-based signing.

Most teams pick vault-transit. Pick vault when you already have a Vault PKI engine standing up build identities.

File specificsโ€‹

A local PEM-format key file. Useful for local dev and CI environments without OIDC support, but not recommended for production releases: there's a long-lived private key to manage, rotate, and protect.

cilock run --step build \
--signer-file-key-path ./signing.key \
--signer-file-cert-path ./signing.crt \
...

If you find yourself using file in production CI, the question to ask is: why don't I have OIDC available here? GitHub Actions, GitLab CI, AWS, GCP, and Azure all expose OIDC tokens.

Mixing signers across pipelinesโ€‹

Different steps can use different signers, the verifier resolves each step's collection against that step's functionaries independently. A common pattern:

StepSignerWhy
buildfulcio (keyless)Runs in CI, has OIDC
policy-signkms/aws (long-lived)Policy is signed once at policy-publish time, away from CI
release-promotionkms/aws (long-lived)Promotion gate runs as a service account, not a CI workflow

Only one signer is supported per cilock run invocation (verified from cilock/internal/cmd/run.go). To use multiple signers, run cilock multiple times.

Parity with Witnessโ€‹

The cilock signer surface is a superset of the upstream Witness signer surface; everything Witness supports, cilock supports with the same flag namespaces. Cilock adds three:

ProviderWitnessCilockNotes
fileโœ“โœ“Cilock adds --signer-file-key-passphrase / --signer-file-key-passphrase-path for encrypted PEMs.
fulcioโœ“โœ“Cilock adds --signer-fulcio-use-http (HTTP/REST vs. gRPC; defaults to HTTP).
spiffeโœ“โœ“Identical.
vault (PKI engine)โœ“โœ“Identical.
kms/awsโœ“โœ“Identical.
kms/gcpโœ“โœ“Identical.
kms/azure(none)โœ“Cilock-only.
vault-transit (kms/hashivault)(none)โœ“Cilock-only; uses Vault's Transit engine instead of PKI.
debug-signer(none)โœ“Cilock-only; ephemeral keypair for development.

For the five shared providers, the Witness docs are an authoritative reference; cilock's wire format and flag names are identical.

See alsoโ€‹