Files
git.stella-ops.org/docs/operations/key-rotation-runbook.md
master 8bbfe4d2d2 feat(rate-limiting): Implement core rate limiting functionality with configuration, decision-making, metrics, middleware, and service registration
- 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.
2025-12-17 18:02:37 +02:00

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

  1. Never mutate old DSSE envelopes - Signed content is immutable
  2. Never remove keys from history - Move to revokedKeys, don't delete
  3. Publish key material - Via attestation feed or Rekor-mirror
  4. Audit all changes - Full log of key lifecycle events
  5. 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:

  1. Lookup trust anchor for the artifact PURL
  2. Find keys that were valid at time T:
    • Key was added before T
    • Key was not revoked, OR revoked after T
  3. Verify signature against valid keys
  4. 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

  1. Revoke the compromised key immediately

    stellaops anchor revoke-key \
      --anchor-id ALL \
      --key-id compromised-key-id \
      --reason "compromise" \
      --effective-at "NOW"
    
  2. Generate new key

    stellaops key generate \
      --profile default \
      --key-id emergency-key-$(date +%Y%m%d)
    
  3. Add new key to all affected anchors

    stellaops anchor add-key \
      --anchor-id ALL \
      --key-id emergency-key-$(date +%Y%m%d)
    
  4. Publish updated key material

    stellaops feed publish --include-keys --urgent
    

Post-Incident Actions

  1. Review all proofs signed with compromised key
  2. Determine if any tampering occurred
  3. Re-sign critical proofs with new key if needed
  4. 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"