6.8 KiB
6.8 KiB
Keyless Signing Guide
This guide explains how to configure and use keyless signing with Sigstore Fulcio for CI/CD pipelines.
Overview
Keyless signing eliminates the need to manage long-lived signing keys by using short-lived X.509 certificates (~10 minute TTL) issued by Fulcio based on OIDC identity tokens. This approach:
- Zero key management: No secrets to rotate or protect
- Identity-bound signatures: Signatures are cryptographically tied to the CI/CD identity
- Non-repudiation: Audit trail via Rekor transparency log
- Industry standard: Compatible with Sigstore ecosystem (cosign, gitsign, etc.)
How It Works
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ CI Runner │────▶│ OIDC Token │────▶│ Fulcio │────▶│ Ephemeral │
│ (GitHub/GL) │ │ Provider │ │ CA │ │ Cert │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Sign DSSE │
│ Envelope │
└─────────────┘
- CI runner provides OIDC token - GitHub Actions, GitLab CI, etc. provide ambient identity tokens
- Token exchanged for certificate - Fulcio validates the OIDC token and issues a short-lived certificate
- Ephemeral key generation - A new ECDSA P-256 or Ed25519 key is generated per signing operation
- DSSE signing - The payload is signed using the ephemeral key
- Certificate attached - The Fulcio certificate is included in the signed bundle for verification
Configuration
Basic Configuration
# etc/signer.yaml
signer:
signing:
mode: "keyless"
keyless:
enabled: true
fulcio:
url: "https://fulcio.sigstore.dev"
timeout: 30s
retries: 3
oidc:
useAmbientToken: true
Private Fulcio Instance
For air-gapped or private deployments:
signer:
signing:
keyless:
fulcio:
url: "https://fulcio.internal.example.com"
certificate:
rootBundlePath: "/etc/stellaops/fulcio-roots.pem"
additionalRoots:
- |
-----BEGIN CERTIFICATE-----
MIIBjzCCATSgAwIBAgIRANZl...
-----END CERTIFICATE-----
Identity Constraints
Restrict which identities are allowed to sign:
signer:
signing:
keyless:
identity:
expectedIssuers:
- "https://token.actions.githubusercontent.com"
- "https://gitlab.com"
expectedSubjectPatterns:
- "^https://github\.com/myorg/.*$"
- "^project_path:mygroup/myproject:.*$"
CI/CD Integration
GitHub Actions
name: Sign Artifacts
on: [push]
jobs:
sign:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC token
contents: read
steps:
- uses: actions/checkout@v4
- name: Install StellaOps CLI
run: |
curl -sSL https://get.stella-ops.io | bash
- name: Sign with keyless mode
run: |
stella sign --mode keyless \
--image ghcr.io/${{ github.repository }}:${{ github.sha }}
GitLab CI
sign:
image: registry.stella-ops.io/cli:latest
id_tokens:
SIGSTORE_ID_TOKEN:
aud: sigstore
script:
- stella sign --mode keyless --image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Algorithm Support
| Algorithm | Status | Use Case |
|---|---|---|
| ECDSA P-256 | Preferred | Default, widest compatibility |
| Ed25519 | Supported | Better performance, growing adoption |
Configure preferred algorithm:
signer:
signing:
keyless:
algorithms:
preferred: "ECDSA_P256"
allowed: ["ECDSA_P256", "Ed25519"]
Signed Bundle Format
The keyless signing produces a DSSE envelope with embedded certificate:
{
"payloadType": "application/vnd.in-toto+json",
"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEi...",
"signatures": [
{
"keyid": "",
"sig": "MEUCIQD..."
}
],
"certificateChain": [
"-----BEGIN CERTIFICATE-----\nMIIC...",
"-----BEGIN CERTIFICATE-----\nMIIB..."
],
"signingMode": "keyless",
"signingIdentity": {
"issuer": "https://token.actions.githubusercontent.com",
"subject": "https://github.com/org/repo/.github/workflows/ci.yml@refs/heads/main"
}
}
Verification
Bundles signed with keyless mode can be verified using:
# Verify a signed bundle
stella verify --bundle verdict.json \
--expected-issuer "https://token.actions.githubusercontent.com" \
--expected-subject "https://github.com/myorg/myrepo/*"
The verification process:
- Validates the certificate chain to Fulcio roots
- Verifies the signature using the certificate's public key
- Checks identity claims match expectations
- Optionally validates SCT (Signed Certificate Timestamp)
Troubleshooting
Common Issues
OIDC token not available
- Ensure id-token: write permission in GitHub Actions
- Ensure id_tokens is configured in GitLab CI
- Check ACTIONS_ID_TOKEN_REQUEST_URL environment variable
Fulcio returns 401
- OIDC token may have expired (default 5-10 min validity)
- Audience mismatch - ensure token is for sigstore
- Issuer not trusted by Fulcio instance
Certificate chain validation failed
- Root certificate bundle may be outdated
- Private Fulcio instance roots not configured
- Certificate expired (Fulcio certs are ~10 min TTL)
Debug Logging
Enable verbose logging:
STELLAOPS_LOG_LEVEL=debug stella sign --mode keyless ...
Security Considerations
- Ephemeral keys never persist - Keys exist only in memory during signing
- Short-lived certificates - ~10 minute validity limits exposure window
- Identity verification - Always configure expectedIssuers and expectedSubjectPatterns in production
- SCT validation - Enable requireSct: true for public Fulcio instances