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:
StellaOps Bot
2025-12-26 15:17:15 +02:00
parent 7792749bb4
commit 907783f625
354 changed files with 79727 additions and 1346 deletions

View File

@@ -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/)