- Add RateLimitConfig for configuration management with YAML binding support. - Introduce RateLimitDecision to encapsulate the result of rate limit checks. - Implement RateLimitMetrics for OpenTelemetry metrics tracking. - Create RateLimitMiddleware for enforcing rate limits on incoming requests. - Develop RateLimitService to orchestrate instance and environment rate limit checks. - Add RateLimitServiceCollectionExtensions for dependency injection registration.
10 KiB
Key Rotation Runbook
Module: Signer / Key Management
Version: 1.0.0
Last Updated: 2025-12-17
This runbook describes procedures for managing signing key lifecycle in StellaOps, including key rotation, revocation, and trust anchor management.
Overview
StellaOps uses signing keys to create DSSE envelopes for proof chain attestations. Key rotation is critical for:
- Limiting exposure from compromised keys
- Compliance with key age policies (e.g., NIST SP 800-57)
- Transitioning between cryptographic algorithms
Key Principles
- Never mutate old DSSE envelopes - Signed content is immutable
- Never remove keys from history - Move to
revokedKeys, don't delete - Publish key material - Via attestation feed or Rekor-mirror
- Audit all changes - Full log of key lifecycle events
- Maintain key version history - For forensic verification
Signing Key Profiles
StellaOps supports multiple signing key profiles for different security requirements:
| Profile | Algorithm | Key Store | Use Case |
|---|---|---|---|
default |
SHA256-ED25519 | AWS KMS | Standard production |
fips |
SHA256-ECDSA-P256 | HSM (PKCS#11) | FIPS 140-2 environments |
gost |
GOST-R-34.10-2012 | Local HSM | Russian regulatory |
sm2 |
SM2-P256 | Local HSM | Chinese regulatory |
pqc |
ML-DSA-65 | Software | Post-quantum ready |
Profile Configuration
# /etc/stellaops/signer.yaml
signer:
profiles:
default:
algorithm: "SHA256-ED25519"
keyStore: "kms://aws/key/stellaops-default"
rotation:
enabled: true
maxAgeMonths: 12
warningMonths: 2
fips:
algorithm: "SHA256-ECDSA-P256"
keyStore: "hsm://pkcs11/slot/0"
rotation:
enabled: true
maxAgeMonths: 12
warningMonths: 2
Key Rotation Workflow
Step 1: Generate New Key
Generate a new signing key in the configured key store:
# Using CLI
stellaops key generate \
--profile default \
--key-id key-2025-prod \
--algorithm SHA256-ED25519
# Via API
curl -X POST https://api.stellaops.local/v1/signer/keys \
-H "Authorization: Bearer $TOKEN" \
-d '{"profile": "default", "keyId": "key-2025-prod", "algorithm": "SHA256-ED25519"}'
Step 2: Add Key to Trust Anchor
Add the new key to the trust anchor without removing the old key:
# Using CLI
stellaops anchor add-key \
--anchor-id 550e8400-e29b-41d4-a716-446655440000 \
--key-id key-2025-prod
# Via API
curl -X POST https://api.stellaops.local/v1/anchors/550e8400.../keys \
-H "Authorization: Bearer $TOKEN" \
-d '{"keyid": "key-2025-prod", "publicKey": "<pem-encoded>"}'
Result: Trust anchor now accepts signatures from both old and new keys.
Step 3: Transition Period
During transition:
- New signatures are created with the new key
- Old proofs are verified with either key
- Monitor for verification failures
Recommended transition period: 2-4 weeks
# Check verification status
stellaops anchor status --anchor-id 550e8400...
# Expected output:
# Anchor: 550e8400-e29b-41d4-a716-446655440000
# Active Keys: key-2024-prod, key-2025-prod
# Verification Success Rate: 100%
# Pending Rescans: 0
Step 4: Revoke Old Key (Optional)
After transition is complete, revoke the old key:
# Using CLI
stellaops anchor revoke-key \
--anchor-id 550e8400... \
--key-id key-2024-prod \
--reason "annual-rotation" \
--effective-at "2025-02-01T00:00:00Z"
# Via API
curl -X POST https://api.stellaops.local/v1/anchors/550e8400.../keys/key-2024-prod/revoke \
-H "Authorization: Bearer $TOKEN" \
-d '{"reason": "annual-rotation", "effectiveAt": "2025-02-01T00:00:00Z"}'
Important: The old key remains valid for verifying proofs signed before the revocation date.
Step 5: Publish Key Material
Publish updated key material:
# Update attestation feed
stellaops feed publish --include-keys
# Sync to Rekor mirror (if applicable)
stellaops rekor sync --keys-only
Trust Anchor Management
Trust Anchor Structure
{
"trustAnchorId": "550e8400-e29b-41d4-a716-446655440000",
"purlPattern": "pkg:npm/*",
"allowedKeyids": ["key-2024-prod", "key-2025-prod"],
"allowedPredicateTypes": [
"evidence.stella/v1",
"reasoning.stella/v1",
"cdx-vex.stella/v1",
"proofspine.stella/v1"
],
"policyVersion": "v2.3.1",
"revokedKeys": ["key-2023-prod"],
"keyHistory": [
{
"keyid": "key-2023-prod",
"addedAt": "2023-01-15T00:00:00Z",
"revokedAt": "2024-01-15T00:00:00Z",
"revokeReason": "annual-rotation"
}
]
}
Create Trust Anchor
stellaops anchor create \
--purl-pattern "pkg:npm/*" \
--key-ids key-2025-prod \
--predicate-types evidence.stella/v1,reasoning.stella/v1
List Trust Anchors
stellaops anchor list
# Output:
# ID Pattern Keys Status
# 550e8400-e29b-41d4-a716-446655440000 pkg:npm/* key-2025-prod active
# 660f9500-f39c-51e5-b827-557766551111 pkg:maven/* key-2025-java active
PURL Pattern Matching
Trust anchors use PURL patterns for scope:
| Pattern | Matches |
|---|---|
pkg:npm/* |
All npm packages |
pkg:maven/org.apache.* |
Apache Maven packages |
pkg:docker/myregistry/* |
All images from myregistry |
* |
Universal (all packages) |
Verification with Key History
When verifying a proof signed at time T:
- Lookup trust anchor for the artifact PURL
- Find keys that were valid at time T:
- Key was added before T
- Key was not revoked, OR revoked after T
- Verify signature against valid keys
- Return success if any valid key verifies
Temporal Verification
# Verify proof at specific point in time
stellaops verify \
--proof-bundle sha256:abc123... \
--at-time "2024-06-15T12:00:00Z"
# Check key validity at time
stellaops key check-validity \
--key-id key-2024-prod \
--at-time "2024-06-15T12:00:00Z"
Emergency Key Revocation
In case of key compromise:
Immediate Actions
-
Revoke the compromised key immediately
stellaops anchor revoke-key \ --anchor-id ALL \ --key-id compromised-key-id \ --reason "compromise" \ --effective-at "NOW" -
Generate new key
stellaops key generate \ --profile default \ --key-id emergency-key-$(date +%Y%m%d) -
Add new key to all affected anchors
stellaops anchor add-key \ --anchor-id ALL \ --key-id emergency-key-$(date +%Y%m%d) -
Publish updated key material
stellaops feed publish --include-keys --urgent
Post-Incident Actions
- Review all proofs signed with compromised key
- Determine if any tampering occurred
- Re-sign critical proofs with new key if needed
- File incident report
Rotation Warnings
Configure rotation warnings to proactively manage key lifecycle:
signer:
rotation:
warningMonths: 2
alerts:
- type: slack
channel: "#security-ops"
- type: email
recipients: ["security@example.com"]
Check Rotation Warnings
stellaops key rotation-warnings
# Output:
# Key ID Profile Age Max Age Warning
# key-2024-prod default 10mo 12mo ⚠️ Rotation due in 2 months
# key-2024-java fips 6mo 12mo ✓ OK
Audit Trail
All key operations are logged to key_audit_log:
| Field | Description |
|---|---|
event_id |
Unique event identifier |
event_type |
KEY_GENERATED, KEY_ADDED, KEY_REVOKED, etc. |
key_id |
Affected key identifier |
anchor_id |
Affected trust anchor (if applicable) |
actor |
User/service that performed action |
timestamp |
UTC timestamp |
details |
JSON with additional context |
Query Audit Log
stellaops audit query \
--type KEY_* \
--from "2025-01-01" \
--to "2025-12-31"
# Via SQL
SELECT * FROM signer.key_audit_log
WHERE event_type LIKE 'KEY_%'
AND timestamp >= '2025-01-01'
ORDER BY timestamp DESC;
Database Schema
key_history Table
CREATE TABLE signer.key_history (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
anchor_id UUID NOT NULL REFERENCES signer.trust_anchors(id),
key_id TEXT NOT NULL,
public_key TEXT NOT NULL,
algorithm TEXT NOT NULL,
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
revoked_at TIMESTAMPTZ,
revoke_reason TEXT,
metadata JSONB,
UNIQUE(anchor_id, key_id)
);
CREATE INDEX idx_key_history_validity
ON signer.key_history (anchor_id, added_at, revoked_at);
key_audit_log Table
CREATE TABLE signer.key_audit_log (
event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_type TEXT NOT NULL,
key_id TEXT,
anchor_id UUID,
actor TEXT NOT NULL,
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
details JSONB
);
CREATE INDEX idx_audit_log_time ON signer.key_audit_log (timestamp DESC);
CREATE INDEX idx_audit_log_key ON signer.key_audit_log (key_id);
Metrics
Key rotation metrics exposed via Prometheus:
| Metric | Type | Description |
|---|---|---|
signer_key_age_days |
Gauge | Age of each active key in days |
signer_keys_active_total |
Gauge | Number of active keys per profile |
signer_keys_revoked_total |
Counter | Total revoked keys |
signer_rotation_events_total |
Counter | Key rotation events |
signer_verification_key_lookups_total |
Counter | Temporal key lookups |
Alerting Rules
groups:
- name: key-rotation
rules:
- alert: SigningKeyNearExpiry
expr: signer_key_age_days > (365 - 60)
for: 1d
labels:
severity: warning
annotations:
summary: "Signing key approaching rotation deadline"
- alert: SigningKeyExpired
expr: signer_key_age_days > 365
for: 1h
labels:
severity: critical
annotations:
summary: "Signing key exceeded maximum age"
Related Documentation
- Proof Chain API
- Attestor Architecture
- Signer Architecture
- NIST SP 800-57 - Key Management Guidelines