Add property-based tests for SBOM/VEX document ordering and Unicode normalization determinism
- Implement `SbomVexOrderingDeterminismProperties` for testing component list and vulnerability metadata hash consistency. - Create `UnicodeNormalizationDeterminismProperties` to validate NFC normalization and Unicode string handling. - Add project file for `StellaOps.Testing.Determinism.Properties` with necessary dependencies. - Introduce CI/CD template validation tests including YAML syntax checks and documentation content verification. - Create validation script for CI/CD templates ensuring all required files and structures are present.
This commit is contained in:
@@ -1,99 +1,40 @@
|
||||
# Keyless Signing Guide
|
||||
|
||||
This guide explains how to configure and use keyless signing with Sigstore Fulcio for CI/CD pipelines.
|
||||
|
||||
## Overview
|
||||
|
||||
Keyless signing uses ephemeral X.509 certificates from Sigstore Fulcio, eliminating the need for persistent signing keys. This approach is ideal for CI/CD pipelines where key management is complex and error-prone.
|
||||
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:
|
||||
|
||||
### How It Works
|
||||
- **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 Pipeline │────▶│ OIDC Provider│────▶│ Fulcio │────▶│ Rekor │
|
||||
│ │ │ (GitHub/GL) │ │ (Sigstore) │ │ (Sigstore) │
|
||||
│ 1. Get token │ │ 2. Issue JWT │ │ 3. Issue cert│ │ 4. Log entry │
|
||||
│ │ │ (5 min) │ │ (10 min) │ │ (permanent) │
|
||||
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
|
||||
│ │
|
||||
│ │
|
||||
└───────────── Attestation with cert + Rekor proof ───────────┘
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ CI Runner │────▶│ OIDC Token │────▶│ Fulcio │────▶│ Ephemeral │
|
||||
│ (GitHub/GL) │ │ Provider │ │ CA │ │ Cert │
|
||||
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Sign DSSE │
|
||||
│ Envelope │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
1. **OIDC Token**: Pipeline requests identity token from CI platform
|
||||
2. **Fulcio Certificate**: Token exchanged for short-lived signing certificate (~10 min)
|
||||
3. **Ephemeral Key**: Private key exists only in memory during signing
|
||||
4. **Rekor Logging**: Signature logged to transparency log for verification after cert expiry
|
||||
|
||||
### Key Benefits
|
||||
|
||||
| Benefit | Description |
|
||||
|---------|-------------|
|
||||
| **Zero Key Management** | No secrets to rotate, store, or protect |
|
||||
| **Identity Binding** | Signatures tied to OIDC identity (repo, branch, workflow) |
|
||||
| **Audit Trail** | All signatures logged to Rekor transparency log |
|
||||
| **Short-lived Certs** | Minimizes exposure window (~10 minutes) |
|
||||
| **Industry Standard** | Adopted by Kubernetes, npm, PyPI, and major ecosystems |
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. StellaOps CLI installed
|
||||
2. CI platform with OIDC support (GitHub Actions, GitLab CI, Gitea)
|
||||
3. Network access to Fulcio and Rekor (or private instances)
|
||||
|
||||
### GitHub Actions Example
|
||||
|
||||
```yaml
|
||||
name: Sign Container Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build-and-sign:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build and Push Image
|
||||
id: build
|
||||
run: |
|
||||
docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
|
||||
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
|
||||
echo "digest=$(docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/${{ github.repository }}:${{ github.sha }} | cut -d@ -f2)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Keyless Sign
|
||||
uses: stella-ops/sign-action@v1
|
||||
with:
|
||||
artifact-digest: ${{ steps.build.outputs.digest }}
|
||||
artifact-type: image
|
||||
```
|
||||
|
||||
### CLI Usage
|
||||
|
||||
```bash
|
||||
# Sign with ambient OIDC token (in CI environment)
|
||||
stella attest sign --keyless --artifact sha256:abc123...
|
||||
|
||||
# Sign with explicit token
|
||||
STELLAOPS_OIDC_TOKEN="..." stella attest sign --keyless --artifact sha256:abc123...
|
||||
|
||||
# Verify signature (checks Rekor proof)
|
||||
stella attest verify \
|
||||
--artifact sha256:abc123... \
|
||||
--certificate-identity "repo:myorg/myrepo:ref:refs/heads/main" \
|
||||
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
|
||||
```
|
||||
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
|
||||
|
||||
### Signer Configuration
|
||||
### Basic Configuration
|
||||
|
||||
```yaml
|
||||
# etc/signer.yaml
|
||||
@@ -107,21 +48,12 @@ signer:
|
||||
timeout: 30s
|
||||
retries: 3
|
||||
oidc:
|
||||
issuer: "https://authority.internal"
|
||||
clientId: "signer-keyless"
|
||||
useAmbientToken: true
|
||||
algorithms:
|
||||
preferred: "ECDSA_P256"
|
||||
allowed: ["ECDSA_P256", "Ed25519"]
|
||||
certificate:
|
||||
rootBundlePath: "/etc/stellaops/fulcio-roots.pem"
|
||||
validateChain: true
|
||||
requireSCT: true
|
||||
```
|
||||
|
||||
### Private Fulcio Instance
|
||||
|
||||
For air-gapped or high-security environments, deploy a private Fulcio instance:
|
||||
For air-gapped or private deployments:
|
||||
|
||||
```yaml
|
||||
signer:
|
||||
@@ -129,145 +61,170 @@ signer:
|
||||
keyless:
|
||||
fulcio:
|
||||
url: "https://fulcio.internal.example.com"
|
||||
oidc:
|
||||
issuer: "https://keycloak.internal.example.com/realms/stellaops"
|
||||
certificate:
|
||||
rootBundlePath: "/etc/stellaops/private-fulcio-roots.pem"
|
||||
rootBundlePath: "/etc/stellaops/fulcio-roots.pem"
|
||||
additionalRoots:
|
||||
- |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBjzCCATSgAwIBAgIRANZl...
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
## Identity Verification
|
||||
|
||||
### Identity Constraints
|
||||
|
||||
When verifying signatures, specify which identities are trusted:
|
||||
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
|
||||
stella attest verify \
|
||||
--artifact sha256:abc123... \
|
||||
--certificate-identity "repo:myorg/myrepo:ref:refs/heads/main" \
|
||||
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
|
||||
# Verify a signed bundle
|
||||
stella verify --bundle verdict.json \
|
||||
--expected-issuer "https://token.actions.githubusercontent.com" \
|
||||
--expected-subject "https://github.com/myorg/myrepo/*"
|
||||
```
|
||||
|
||||
### Platform Identity Patterns
|
||||
|
||||
#### GitHub Actions
|
||||
|
||||
| Pattern | Matches |
|
||||
|---------|---------|
|
||||
| `repo:org/repo:.*` | Any ref in repository |
|
||||
| `repo:org/repo:ref:refs/heads/main` | Main branch only |
|
||||
| `repo:org/repo:ref:refs/tags/v.*` | Version tags |
|
||||
| `repo:org/repo:environment:production` | Production environment |
|
||||
|
||||
**Issuer:** `https://token.actions.githubusercontent.com`
|
||||
|
||||
#### GitLab CI
|
||||
|
||||
| Pattern | Matches |
|
||||
|---------|---------|
|
||||
| `project_path:group/project:.*` | Any ref in project |
|
||||
| `project_path:group/project:ref_type:branch:ref:main` | Main branch |
|
||||
| `project_path:group/project:ref_protected:true` | Protected refs only |
|
||||
|
||||
**Issuer:** `https://gitlab.com` (or self-hosted URL)
|
||||
|
||||
## Long-Term Verification
|
||||
|
||||
### The Problem
|
||||
|
||||
Fulcio certificates expire in ~10 minutes. How do you verify signatures months later?
|
||||
|
||||
### The Solution: Rekor Proofs
|
||||
|
||||
```
|
||||
At signing time:
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Signature + Certificate + Signed-Certificate-Timestamp (SCT) │
|
||||
│ ↓ │
|
||||
│ Logged to Rekor │
|
||||
│ ↓ │
|
||||
│ Merkle Inclusion Proof returned │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
At verification time (even years later):
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 1. Check signature is valid (using cert public key) │
|
||||
│ 2. Check SCT proves cert was logged when valid │
|
||||
│ 3. Check Rekor inclusion proof (entry was logged) │
|
||||
│ 4. Check signing time was within cert validity window │
|
||||
│ ↓ │
|
||||
│ Signature is valid! ✓ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Attestation Bundles
|
||||
|
||||
For air-gapped verification, StellaOps bundles attestations with proofs:
|
||||
|
||||
```bash
|
||||
# Export bundle with Rekor proofs
|
||||
stella attest export-bundle \
|
||||
--image sha256:abc123... \
|
||||
--include-proofs \
|
||||
--output attestation-bundle.json
|
||||
|
||||
# Verify offline
|
||||
stella attest verify --offline \
|
||||
--bundle attestation-bundle.json \
|
||||
--artifact sha256:abc123...
|
||||
```
|
||||
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 Errors
|
||||
### Common Issues
|
||||
|
||||
| Error | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| `OIDC token expired` | Token older than 5 minutes | Re-acquire token before signing |
|
||||
| `Fulcio unavailable` | Network issues | Check connectivity, increase timeout |
|
||||
| `Certificate chain invalid` | Wrong Fulcio roots | Update root bundle |
|
||||
| `Identity mismatch` | Wrong verify constraints | Check issuer and identity patterns |
|
||||
| `Rekor proof missing` | Logging failed | Retry signing, check Rekor status |
|
||||
**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
|
||||
|
||||
### Debug Mode
|
||||
**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
|
||||
# Enable verbose logging
|
||||
STELLAOPS_LOG_LEVEL=debug stella attest sign --keyless --artifact sha256:...
|
||||
|
||||
# Inspect certificate details
|
||||
stella attest inspect --artifact sha256:... --show-cert
|
||||
STELLAOPS_LOG_LEVEL=debug stella sign --mode keyless ...
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Always verify identity**: Never accept `.*` as the full identity pattern
|
||||
2. **Require Rekor proofs**: Use `--require-rekor` for production verification
|
||||
3. **Pin OIDC issuers**: Only trust expected issuers
|
||||
4. **Use environment constraints**: More specific than branch names
|
||||
5. **Monitor signing activity**: Alert on unexpected identities
|
||||
|
||||
### Threat Model
|
||||
|
||||
| Threat | Mitigation |
|
||||
|--------|------------|
|
||||
| Stolen OIDC token | Short lifetime (~5 min), audience binding |
|
||||
| Fulcio compromise | Certificate Transparency (SCT), multiple roots |
|
||||
| Rekor compromise | Multiple witnesses, checkpoints, consistency proofs |
|
||||
| Private key theft | Ephemeral keys, never persisted |
|
||||
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)
|
||||
- [Attestor Bundle Format](../../attestor/bundle-format.md)
|
||||
- [Air-Gap Verification](../../../airgap/attestation-verification.md)
|
||||
- [CI/CD Integration](../../../guides/cicd-signing.md)
|
||||
|
||||
## External Resources
|
||||
|
||||
- [DSSE Envelope Format](../dsse-format.md)
|
||||
- [CI/CD Gate Integration](../../policy/guides/cicd-gates.md)
|
||||
- [Sigstore Documentation](https://docs.sigstore.dev/)
|
||||
- [Fulcio Overview](https://docs.sigstore.dev/certificate_authority/overview/)
|
||||
- [Rekor Transparency Log](https://docs.sigstore.dev/logging/overview/)
|
||||
- [cosign Keyless Signing](https://docs.sigstore.dev/signing/quickstart/)
|
||||
|
||||
Reference in New Issue
Block a user