# 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 │ └─────────────┘ ``` 1. **CI runner provides OIDC token** - GitHub Actions, GitLab CI, etc. provide ambient identity tokens 2. **Token exchanged for certificate** - Fulcio validates the OIDC token and issues a short-lived certificate 3. **Ephemeral key generation** - A new ECDSA P-256 or Ed25519 key is generated per signing operation 4. **DSSE signing** - The payload is signed using the ephemeral key 5. **Certificate attached** - The Fulcio certificate is included in the signed bundle for verification ## Configuration ### Basic Configuration ```yaml # 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: ```yaml 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: ```yaml 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 ```yaml 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 ```yaml 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: ```yaml signer: signing: keyless: algorithms: preferred: "ECDSA_P256" allowed: ["ECDSA_P256", "Ed25519"] ``` ## Signed Bundle Format The keyless signing produces a DSSE envelope with embedded certificate: ```json { "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: ```bash # 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: 1. Validates the certificate chain to Fulcio roots 2. Verifies the signature using the certificate's public key 3. Checks identity claims match expectations 4. 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: ```bash STELLAOPS_LOG_LEVEL=debug stella sign --mode keyless ... ``` ## Security Considerations 1. **Ephemeral keys never persist** - Keys exist only in memory during signing 2. **Short-lived certificates** - ~10 minute validity limits exposure window 3. **Identity verification** - Always configure expectedIssuers and expectedSubjectPatterns in production 4. **SCT validation** - Enable requireSct: true for public Fulcio instances ## Related Documentation - [Signer Architecture](../architecture.md) - [DSSE Envelope Format](../dsse-format.md) - [CI/CD Gate Integration](../../policy/guides/cicd-gates.md) - [Sigstore Documentation](https://docs.sigstore.dev/)