fix tests. new product advisories enhancements

This commit is contained in:
master
2026-01-25 19:11:36 +02:00
parent c70e83719e
commit 6e687b523a
504 changed files with 40610 additions and 3785 deletions

View File

@@ -0,0 +1,170 @@
#!/bin/bash
# -----------------------------------------------------------------------------
# bootstrap-trust-offline.sh
# Sprint: SPRINT_20260125_003_Attestor_trust_workflows_conformance
# Task: WORKFLOW-001 - Create bootstrap workflow script
# Description: Initialize trust for air-gapped StellaOps deployment
# -----------------------------------------------------------------------------
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
usage() {
echo "Usage: $0 <trust-bundle> [options]"
echo ""
echo "Initialize trust for an air-gapped StellaOps deployment."
echo ""
echo "Arguments:"
echo " trust-bundle Path to trust bundle (tar.zst or directory)"
echo ""
echo "Options:"
echo " --key-dir DIR Directory for signing keys (default: /etc/stellaops/keys)"
echo " --reject-if-stale D Reject bundle if older than D (e.g., 7d, 24h)"
echo " --skip-keygen Skip signing key generation"
echo " --force Force import even if validation fails"
echo " -h, --help Show this help message"
echo ""
echo "Example:"
echo " $0 /media/usb/trust-bundle-2026-01-25.tar.zst"
exit 1
}
BUNDLE_PATH=""
KEY_DIR="/etc/stellaops/keys"
REJECT_STALE=""
SKIP_KEYGEN=false
FORCE=false
while [[ $# -gt 0 ]]; do
case $1 in
--key-dir) KEY_DIR="$2"; shift 2 ;;
--reject-if-stale) REJECT_STALE="$2"; shift 2 ;;
--skip-keygen) SKIP_KEYGEN=true; shift ;;
--force) FORCE=true; shift ;;
-h|--help) usage ;;
-*) log_error "Unknown option: $1"; usage ;;
*)
if [[ -z "$BUNDLE_PATH" ]]; then
BUNDLE_PATH="$1"
else
log_error "Unexpected argument: $1"
usage
fi
shift
;;
esac
done
if [[ -z "$BUNDLE_PATH" ]]; then
log_error "Trust bundle path is required"
usage
fi
if [[ ! -e "$BUNDLE_PATH" ]]; then
log_error "Trust bundle not found: $BUNDLE_PATH"
exit 1
fi
echo ""
echo "================================================"
echo " StellaOps Offline Trust Bootstrap"
echo "================================================"
echo ""
log_info "Trust Bundle: $BUNDLE_PATH"
log_info "Key Directory: $KEY_DIR"
if [[ -n "$REJECT_STALE" ]]; then
log_info "Staleness Threshold: $REJECT_STALE"
fi
echo ""
# Step 1: Generate signing keys (if using local keys)
if [[ "$SKIP_KEYGEN" != "true" ]]; then
log_step "Step 1: Generating signing keys..."
mkdir -p "$KEY_DIR"
chmod 700 "$KEY_DIR"
if [[ ! -f "$KEY_DIR/signing-key.pem" ]]; then
openssl ecparam -name prime256v1 -genkey -noout -out "$KEY_DIR/signing-key.pem"
chmod 600 "$KEY_DIR/signing-key.pem"
log_info "Generated signing key: $KEY_DIR/signing-key.pem"
else
log_info "Signing key already exists: $KEY_DIR/signing-key.pem"
fi
else
log_step "Step 1: Skipping key generation (--skip-keygen)"
fi
# Step 2: Import trust bundle
log_step "Step 2: Importing trust bundle..."
IMPORT_ARGS="--verify-manifest"
if [[ -n "$REJECT_STALE" ]]; then
IMPORT_ARGS="$IMPORT_ARGS --reject-if-stale $REJECT_STALE"
fi
if [[ "$FORCE" == "true" ]]; then
IMPORT_ARGS="$IMPORT_ARGS --force"
fi
stella trust import "$BUNDLE_PATH" $IMPORT_ARGS
if [[ $? -ne 0 ]]; then
log_error "Failed to import trust bundle"
exit 1
fi
log_info "Trust bundle imported successfully"
# Step 3: Verify trust state
log_step "Step 3: Verifying trust state..."
stella trust status --show-keys
if [[ $? -ne 0 ]]; then
log_error "Failed to verify trust status"
exit 1
fi
# Step 4: Test offline verification
log_step "Step 4: Testing offline verification capability..."
# Check that we have TUF metadata
CACHE_DIR="${HOME}/.local/share/StellaOps/TufCache"
if [[ -f "$CACHE_DIR/root.json" ]] && [[ -f "$CACHE_DIR/timestamp.json" ]]; then
log_info "TUF metadata present"
else
log_warn "TUF metadata may be incomplete"
fi
# Check for tiles (if snapshot included them)
if [[ -d "$CACHE_DIR/tiles" ]]; then
TILE_COUNT=$(find "$CACHE_DIR/tiles" -name "*.tile" 2>/dev/null | wc -l)
log_info "Tiles cached: $TILE_COUNT"
fi
echo ""
echo "================================================"
echo -e "${GREEN} Offline Bootstrap Complete!${NC}"
echo "================================================"
echo ""
log_info "Trust state imported to: $CACHE_DIR"
log_info "Signing key (if generated): $KEY_DIR/signing-key.pem"
echo ""
log_info "This system can now verify attestations offline using the imported trust state."
log_warn "Remember to periodically update the trust bundle to maintain freshness."
echo ""
log_info "To update trust state:"
echo " 1. On connected system: stella trust snapshot export --out bundle.tar.zst"
echo " 2. Transfer bundle to this system"
echo " 3. Run: $0 bundle.tar.zst"
echo ""

View File

@@ -0,0 +1,196 @@
#!/bin/bash
# -----------------------------------------------------------------------------
# bootstrap-trust.sh
# Sprint: SPRINT_20260125_003_Attestor_trust_workflows_conformance
# Task: WORKFLOW-001 - Create bootstrap workflow script
# Description: Initialize trust for new StellaOps deployment
# -----------------------------------------------------------------------------
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
usage() {
echo "Usage: $0 [options]"
echo ""
echo "Initialize trust for a new StellaOps deployment."
echo ""
echo "Options:"
echo " --tuf-url URL TUF repository URL (required)"
echo " --service-map NAME Service map target name (default: sigstore-services-v1)"
echo " --pin KEY Rekor key to pin (can specify multiple)"
echo " --key-dir DIR Directory for signing keys (default: /etc/stellaops/keys)"
echo " --skip-keygen Skip signing key generation"
echo " --skip-test Skip sign/verify test"
echo " --offline Initialize in offline mode"
echo " -h, --help Show this help message"
echo ""
echo "Example:"
echo " $0 --tuf-url https://trust.example.com/tuf/ --pin rekor-key-v1"
exit 1
}
TUF_URL=""
SERVICE_MAP="sigstore-services-v1"
PIN_KEYS=()
KEY_DIR="/etc/stellaops/keys"
SKIP_KEYGEN=false
SKIP_TEST=false
OFFLINE=false
while [[ $# -gt 0 ]]; do
case $1 in
--tuf-url) TUF_URL="$2"; shift 2 ;;
--service-map) SERVICE_MAP="$2"; shift 2 ;;
--pin) PIN_KEYS+=("$2"); shift 2 ;;
--key-dir) KEY_DIR="$2"; shift 2 ;;
--skip-keygen) SKIP_KEYGEN=true; shift ;;
--skip-test) SKIP_TEST=true; shift ;;
--offline) OFFLINE=true; shift ;;
-h|--help) usage ;;
*) log_error "Unknown option: $1"; usage ;;
esac
done
if [[ -z "$TUF_URL" ]]; then
log_error "TUF URL is required"
usage
fi
if [[ ${#PIN_KEYS[@]} -eq 0 ]]; then
PIN_KEYS=("rekor-key-v1")
fi
echo ""
echo "================================================"
echo " StellaOps Trust Bootstrap"
echo "================================================"
echo ""
log_info "TUF URL: $TUF_URL"
log_info "Service Map: $SERVICE_MAP"
log_info "Pinned Keys: ${PIN_KEYS[*]}"
log_info "Key Directory: $KEY_DIR"
echo ""
# Step 1: Generate signing keys (if using local keys)
if [[ "$SKIP_KEYGEN" != "true" ]]; then
log_step "Step 1: Generating signing keys..."
mkdir -p "$KEY_DIR"
chmod 700 "$KEY_DIR"
if [[ ! -f "$KEY_DIR/signing-key.pem" ]]; then
stella keys generate --type ecdsa-p256 --out "$KEY_DIR/signing-key.pem" 2>/dev/null || \
openssl ecparam -name prime256v1 -genkey -noout -out "$KEY_DIR/signing-key.pem"
chmod 600 "$KEY_DIR/signing-key.pem"
log_info "Generated signing key: $KEY_DIR/signing-key.pem"
else
log_info "Signing key already exists: $KEY_DIR/signing-key.pem"
fi
else
log_step "Step 1: Skipping key generation (--skip-keygen)"
fi
# Step 2: Initialize TUF client
log_step "Step 2: Initializing TUF client..."
PIN_ARGS=""
for key in "${PIN_KEYS[@]}"; do
PIN_ARGS="$PIN_ARGS --pin $key"
done
OFFLINE_ARG=""
if [[ "$OFFLINE" == "true" ]]; then
OFFLINE_ARG="--offline"
fi
stella trust init \
--tuf-url "$TUF_URL" \
--service-map "$SERVICE_MAP" \
$PIN_ARGS \
$OFFLINE_ARG \
--force
if [[ $? -ne 0 ]]; then
log_error "Failed to initialize TUF client"
exit 1
fi
log_info "TUF client initialized successfully"
# Step 3: Verify TUF metadata loaded
log_step "Step 3: Verifying TUF metadata..."
stella trust status --show-keys --show-endpoints
if [[ $? -ne 0 ]]; then
log_error "Failed to verify TUF status"
exit 1
fi
# Step 4: Test sign/verify cycle
if [[ "$SKIP_TEST" != "true" ]] && [[ "$SKIP_KEYGEN" != "true" ]]; then
log_step "Step 4: Testing sign/verify cycle..."
TEST_FILE=$(mktemp)
TEST_SIG=$(mktemp)
echo "StellaOps bootstrap test $(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$TEST_FILE"
stella sign "$TEST_FILE" --key "$KEY_DIR/signing-key.pem" --out "$TEST_SIG" 2>/dev/null || {
# Fallback to openssl if stella sign not available
openssl dgst -sha256 -sign "$KEY_DIR/signing-key.pem" -out "$TEST_SIG" "$TEST_FILE"
}
if [[ -f "$TEST_SIG" ]] && [[ -s "$TEST_SIG" ]]; then
log_info "Sign/verify test passed"
else
log_warn "Sign test could not be verified (this may be expected)"
fi
rm -f "$TEST_FILE" "$TEST_SIG"
else
log_step "Step 4: Skipping sign/verify test"
fi
# Step 5: Test Rekor connectivity (if online)
if [[ "$OFFLINE" != "true" ]]; then
log_step "Step 5: Testing Rekor connectivity..."
REKOR_URL=$(stella trust status --output json 2>/dev/null | grep -o '"rekor_url"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | cut -d'"' -f4 || echo "")
if [[ -n "$REKOR_URL" ]]; then
if curl -sf "${REKOR_URL}/api/v1/log" >/dev/null 2>&1; then
log_info "Rekor connectivity: OK"
else
log_warn "Rekor connectivity check failed (service may be unavailable)"
fi
else
log_warn "Could not determine Rekor URL from trust status"
fi
else
log_step "Step 5: Skipping Rekor test (offline mode)"
fi
echo ""
echo "================================================"
echo -e "${GREEN} Bootstrap Complete!${NC}"
echo "================================================"
echo ""
log_info "Trust repository initialized at: ~/.local/share/StellaOps/TufCache"
log_info "Signing key (if generated): $KEY_DIR/signing-key.pem"
echo ""
log_info "Next steps:"
echo " 1. Configure your CI/CD to use the signing key"
echo " 2. Set up periodic 'stella trust sync' for metadata freshness"
echo " 3. For air-gap deployments, run 'stella trust export' to create bundles"
echo ""

View File

@@ -0,0 +1,195 @@
#!/bin/bash
# -----------------------------------------------------------------------------
# disaster-swap-endpoint.sh
# Sprint: SPRINT_20260125_003_Attestor_trust_workflows_conformance
# Task: WORKFLOW-003 - Create disaster endpoint swap script
# Description: Emergency endpoint swap via TUF (no client reconfiguration)
# -----------------------------------------------------------------------------
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
usage() {
echo "Usage: $0 --repo <dir> --new-rekor-url <url> [options]"
echo ""
echo "Emergency endpoint swap via TUF update."
echo "Clients will auto-discover new endpoints without reconfiguration."
echo ""
echo "Options:"
echo " --repo DIR TUF repository directory (required)"
echo " --new-rekor-url URL New Rekor URL (required)"
echo " --new-fulcio-url URL New Fulcio URL (optional)"
echo " --note TEXT Note explaining the change"
echo " --version N New service map version (auto-increment if not specified)"
echo " -h, --help Show this help message"
echo ""
echo "Example:"
echo " $0 --repo /path/to/tuf \\"
echo " --new-rekor-url https://rekor-mirror.internal:8080 \\"
echo " --note 'Emergency: Production Rekor outage'"
echo ""
echo "IMPORTANT: This changes where ALL clients send requests!"
exit 1
}
REPO_DIR=""
NEW_REKOR_URL=""
NEW_FULCIO_URL=""
NOTE=""
VERSION=""
while [[ $# -gt 0 ]]; do
case $1 in
--repo) REPO_DIR="$2"; shift 2 ;;
--new-rekor-url) NEW_REKOR_URL="$2"; shift 2 ;;
--new-fulcio-url) NEW_FULCIO_URL="$2"; shift 2 ;;
--note) NOTE="$2"; shift 2 ;;
--version) VERSION="$2"; shift 2 ;;
-h|--help) usage ;;
*) log_error "Unknown argument: $1"; usage ;;
esac
done
if [[ -z "$REPO_DIR" ]] || [[ -z "$NEW_REKOR_URL" ]]; then
log_error "--repo and --new-rekor-url are required"
usage
fi
if [[ ! -d "$REPO_DIR" ]]; then
log_error "TUF repository not found: $REPO_DIR"
exit 1
fi
echo ""
echo "================================================"
echo -e "${RED} EMERGENCY ENDPOINT SWAP${NC}"
echo "================================================"
echo ""
log_warn "This will redirect ALL clients to new endpoints!"
echo ""
log_info "TUF Repository: $REPO_DIR"
log_info "New Rekor URL: $NEW_REKOR_URL"
if [[ -n "$NEW_FULCIO_URL" ]]; then
log_info "New Fulcio URL: $NEW_FULCIO_URL"
fi
if [[ -n "$NOTE" ]]; then
log_info "Note: $NOTE"
fi
echo ""
read -p "Type 'SWAP' to confirm endpoint change: " CONFIRM
if [[ "$CONFIRM" != "SWAP" ]]; then
log_error "Aborted"
exit 1
fi
# Find current service map
CURRENT_MAP=$(ls "$REPO_DIR/targets/" 2>/dev/null | grep -E '^sigstore-services-v[0-9]+\.json$' | sort -V | tail -1 || echo "")
if [[ -z "$CURRENT_MAP" ]]; then
log_error "No service map found in $REPO_DIR/targets/"
exit 1
fi
CURRENT_PATH="$REPO_DIR/targets/$CURRENT_MAP"
log_info "Current service map: $CURRENT_MAP"
# Determine new version
if [[ -z "$VERSION" ]]; then
CURRENT_VERSION=$(echo "$CURRENT_MAP" | grep -oE '[0-9]+' | tail -1)
VERSION=$((CURRENT_VERSION + 1))
fi
NEW_MAP="sigstore-services-v${VERSION}.json"
NEW_PATH="$REPO_DIR/targets/$NEW_MAP"
log_step "Creating new service map: $NEW_MAP"
# Read current map and update
if command -v python3 &>/dev/null; then
python3 - "$CURRENT_PATH" "$NEW_PATH" "$NEW_REKOR_URL" "$NEW_FULCIO_URL" "$NOTE" "$VERSION" << 'PYTHON_SCRIPT'
import json
import sys
from datetime import datetime
current_path = sys.argv[1]
new_path = sys.argv[2]
new_rekor_url = sys.argv[3]
new_fulcio_url = sys.argv[4] if len(sys.argv) > 4 and sys.argv[4] else None
note = sys.argv[5] if len(sys.argv) > 5 and sys.argv[5] else None
version = int(sys.argv[6]) if len(sys.argv) > 6 else 1
with open(current_path) as f:
data = json.load(f)
# Update endpoints
data['version'] = version
data['rekor']['url'] = new_rekor_url
if new_fulcio_url and 'fulcio' in data:
data['fulcio']['url'] = new_fulcio_url
# Update metadata
if 'metadata' not in data:
data['metadata'] = {}
data['metadata']['updated_at'] = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
if note:
data['metadata']['note'] = note
with open(new_path, 'w') as f:
json.dump(data, f, indent=2)
print(f"Created: {new_path}")
PYTHON_SCRIPT
else
# Fallback: simple JSON creation
cat > "$NEW_PATH" << EOF
{
"version": $VERSION,
"rekor": {
"url": "$NEW_REKOR_URL"
},
"metadata": {
"updated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"note": "$NOTE"
}
}
EOF
fi
log_info "New service map created: $NEW_PATH"
# Add to targets
log_step "Adding new service map to TUF targets..."
if [[ -x "$REPO_DIR/scripts/add-target.sh" ]]; then
"$REPO_DIR/scripts/add-target.sh" "$NEW_PATH" "$NEW_MAP" --repo "$REPO_DIR"
fi
echo ""
echo "================================================"
echo -e "${GREEN} Endpoint Swap Prepared${NC}"
echo "================================================"
echo ""
log_warn "NEXT STEPS (REQUIRED):"
echo " 1. Review the new service map: cat $NEW_PATH"
echo " 2. Sign the updated targets.json with targets key"
echo " 3. Update snapshot.json and sign with snapshot key"
echo " 4. Update timestamp.json and sign with timestamp key"
echo " 5. Deploy updated metadata to TUF server"
echo ""
log_info "Clients will auto-discover the new endpoint within their refresh interval."
log_info "For immediate effect, clients can run: stella trust sync --force"
echo ""
log_warn "Monitor client traffic to ensure failover is working!"
echo ""

View File

@@ -0,0 +1,197 @@
#!/bin/bash
# -----------------------------------------------------------------------------
# rotate-rekor-key.sh
# Sprint: SPRINT_20260125_003_Attestor_trust_workflows_conformance
# Task: WORKFLOW-002 - Create key rotation workflow script
# Description: Rotate Rekor public key with grace period
# -----------------------------------------------------------------------------
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
usage() {
echo "Usage: $0 <phase> [options]"
echo ""
echo "Rotate Rekor public key through a dual-key grace period."
echo ""
echo "Phases:"
echo " add-key Add new key to TUF (starts grace period)"
echo " verify Verify both keys are active"
echo " remove-old Remove old key (after grace period)"
echo ""
echo "Options:"
echo " --repo DIR TUF repository directory"
echo " --new-key FILE Path to new Rekor public key"
echo " --new-key-name NAME Target name for new key (default: rekor-key-v{N+1})"
echo " --old-key-name NAME Target name for old key to remove"
echo " --grace-days N Grace period in days (default: 7)"
echo " -h, --help Show this help message"
echo ""
echo "Example (3-phase rotation):"
echo " # Phase 1: Add new key"
echo " $0 add-key --repo /path/to/tuf --new-key rekor-key-v2.pub"
echo ""
echo " # Wait for grace period (clients sync)"
echo " sleep 7d"
echo ""
echo " # Phase 2: Verify"
echo " $0 verify"
echo ""
echo " # Phase 3: Remove old key"
echo " $0 remove-old --repo /path/to/tuf --old-key-name rekor-key-v1"
exit 1
}
PHASE=""
REPO_DIR=""
NEW_KEY=""
NEW_KEY_NAME=""
OLD_KEY_NAME=""
GRACE_DAYS=7
while [[ $# -gt 0 ]]; do
case $1 in
add-key|verify|remove-old)
PHASE="$1"
shift
;;
--repo) REPO_DIR="$2"; shift 2 ;;
--new-key) NEW_KEY="$2"; shift 2 ;;
--new-key-name) NEW_KEY_NAME="$2"; shift 2 ;;
--old-key-name) OLD_KEY_NAME="$2"; shift 2 ;;
--grace-days) GRACE_DAYS="$2"; shift 2 ;;
-h|--help) usage ;;
*) log_error "Unknown argument: $1"; usage ;;
esac
done
if [[ -z "$PHASE" ]]; then
log_error "Phase is required"
usage
fi
echo ""
echo "================================================"
echo " Rekor Key Rotation - Phase: $PHASE"
echo "================================================"
echo ""
case "$PHASE" in
add-key)
if [[ -z "$REPO_DIR" ]] || [[ -z "$NEW_KEY" ]]; then
log_error "add-key requires --repo and --new-key"
usage
fi
if [[ ! -f "$NEW_KEY" ]]; then
log_error "New key file not found: $NEW_KEY"
exit 1
fi
if [[ ! -d "$REPO_DIR" ]]; then
log_error "TUF repository not found: $REPO_DIR"
exit 1
fi
# Determine new key name if not specified
if [[ -z "$NEW_KEY_NAME" ]]; then
# Find highest version and increment
HIGHEST=$(ls "$REPO_DIR/targets/" 2>/dev/null | grep -E '^rekor-key-v[0-9]+' | \
sed 's/rekor-key-v//' | sed 's/\.pub$//' | sort -n | tail -1 || echo "0")
NEW_VERSION=$((HIGHEST + 1))
NEW_KEY_NAME="rekor-key-v${NEW_VERSION}"
fi
log_step "Adding new Rekor key: $NEW_KEY_NAME"
log_info "Source: $NEW_KEY"
# Copy key to targets
cp "$NEW_KEY" "$REPO_DIR/targets/${NEW_KEY_NAME}.pub"
# Add to targets.json
if [[ -x "$REPO_DIR/scripts/add-target.sh" ]]; then
"$REPO_DIR/scripts/add-target.sh" "$REPO_DIR/targets/${NEW_KEY_NAME}.pub" "${NEW_KEY_NAME}.pub" --repo "$REPO_DIR"
else
log_warn "add-target.sh not found, updating targets.json manually required"
fi
log_info ""
log_info "Key added: $NEW_KEY_NAME"
log_info ""
log_warn "IMPORTANT: Dual-key period has started."
log_warn "Wait at least $GRACE_DAYS days before running 'remove-old' phase."
log_warn "During this time, clients will sync and receive both keys."
log_info ""
log_info "Next steps:"
echo " 1. Sign and publish updated TUF metadata"
echo " 2. Monitor client sync status"
echo " 3. After $GRACE_DAYS days, run: $0 remove-old --repo $REPO_DIR --old-key-name <old-key>"
;;
verify)
log_step "Verifying key rotation status..."
# Check local trust state
stella trust status --show-keys
log_info ""
log_info "Verify that:"
echo " 1. Both old and new Rekor keys are listed"
echo " 2. Service endpoints are resolving correctly"
echo " 3. Attestations signed with old key still verify"
;;
remove-old)
if [[ -z "$REPO_DIR" ]] || [[ -z "$OLD_KEY_NAME" ]]; then
log_error "remove-old requires --repo and --old-key-name"
usage
fi
if [[ ! -d "$REPO_DIR" ]]; then
log_error "TUF repository not found: $REPO_DIR"
exit 1
fi
OLD_KEY_FILE="$REPO_DIR/targets/${OLD_KEY_NAME}.pub"
if [[ ! -f "$OLD_KEY_FILE" ]]; then
OLD_KEY_FILE="$REPO_DIR/targets/${OLD_KEY_NAME}"
fi
if [[ ! -f "$OLD_KEY_FILE" ]]; then
log_error "Old key not found: $OLD_KEY_NAME"
exit 1
fi
log_step "Removing old Rekor key: $OLD_KEY_NAME"
log_warn "This is IRREVERSIBLE. Ensure all clients have synced the new key."
read -p "Type 'CONFIRM' to proceed: " CONFIRM
if [[ "$CONFIRM" != "CONFIRM" ]]; then
log_error "Aborted"
exit 1
fi
# Remove key file
rm -f "$OLD_KEY_FILE"
# Remove from targets.json (simplified - production should use proper JSON manipulation)
log_warn "Remember to update targets.json to remove the old key entry"
log_warn "Then sign and publish the updated metadata"
log_info ""
log_info "Old key removed: $OLD_KEY_NAME"
log_info "Key rotation complete!"
;;
esac
echo ""

View File

@@ -0,0 +1,265 @@
#!/bin/bash
# -----------------------------------------------------------------------------
# rotate-signing-key.sh
# Sprint: SPRINT_20260125_003_Attestor_trust_workflows_conformance
# Task: WORKFLOW-002 - Create key rotation workflow script
# Description: Rotate organization signing key with dual-key grace period
# -----------------------------------------------------------------------------
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
usage() {
echo "Usage: $0 <phase> [options]"
echo ""
echo "Rotate organization signing key through a dual-key grace period."
echo ""
echo "Phases:"
echo " generate Generate new signing key"
echo " activate Activate new key (dual-key period starts)"
echo " verify Verify both keys are functional"
echo " retire Retire old key (after grace period)"
echo ""
echo "Options:"
echo " --key-dir DIR Directory for signing keys (default: /etc/stellaops/keys)"
echo " --key-type TYPE Key type: ecdsa-p256, ecdsa-p384, rsa-4096 (default: ecdsa-p256)"
echo " --new-key NAME Name for new key (default: signing-key-v{N+1})"
echo " --old-key NAME Name of old key to retire"
echo " --grace-days N Grace period in days (default: 14)"
echo " --ci-config FILE CI config file to update"
echo " -h, --help Show this help message"
echo ""
echo "Example (4-phase rotation):"
echo " # Phase 1: Generate new key"
echo " $0 generate --key-dir /etc/stellaops/keys"
echo ""
echo " # Phase 2: Activate (update CI to use both keys)"
echo " $0 activate --ci-config .gitea/workflows/ci.yaml"
echo ""
echo " # Wait for grace period"
echo " sleep 14d"
echo ""
echo " # Phase 3: Verify"
echo " $0 verify"
echo ""
echo " # Phase 4: Retire old key"
echo " $0 retire --old-key signing-key-v1"
exit 1
}
PHASE=""
KEY_DIR="/etc/stellaops/keys"
KEY_TYPE="ecdsa-p256"
NEW_KEY_NAME=""
OLD_KEY_NAME=""
GRACE_DAYS=14
CI_CONFIG=""
while [[ $# -gt 0 ]]; do
case $1 in
generate|activate|verify|retire)
PHASE="$1"
shift
;;
--key-dir) KEY_DIR="$2"; shift 2 ;;
--key-type) KEY_TYPE="$2"; shift 2 ;;
--new-key) NEW_KEY_NAME="$2"; shift 2 ;;
--old-key) OLD_KEY_NAME="$2"; shift 2 ;;
--grace-days) GRACE_DAYS="$2"; shift 2 ;;
--ci-config) CI_CONFIG="$2"; shift 2 ;;
-h|--help) usage ;;
*) log_error "Unknown argument: $1"; usage ;;
esac
done
if [[ -z "$PHASE" ]]; then
log_error "Phase is required"
usage
fi
echo ""
echo "================================================"
echo " Signing Key Rotation - Phase: $PHASE"
echo "================================================"
echo ""
case "$PHASE" in
generate)
log_step "Generating new signing key..."
mkdir -p "$KEY_DIR"
chmod 700 "$KEY_DIR"
# Determine new key name if not specified
if [[ -z "$NEW_KEY_NAME" ]]; then
HIGHEST=$(ls "$KEY_DIR" 2>/dev/null | grep -E '^signing-key-v[0-9]+' | \
sed 's/signing-key-v//' | sed 's/\.pem$//' | sort -n | tail -1 || echo "0")
NEW_VERSION=$((HIGHEST + 1))
NEW_KEY_NAME="signing-key-v${NEW_VERSION}"
fi
NEW_KEY_PATH="$KEY_DIR/${NEW_KEY_NAME}.pem"
NEW_PUB_PATH="$KEY_DIR/${NEW_KEY_NAME}.pub"
if [[ -f "$NEW_KEY_PATH" ]]; then
log_error "Key already exists: $NEW_KEY_PATH"
exit 1
fi
case "$KEY_TYPE" in
ecdsa-p256)
openssl ecparam -name prime256v1 -genkey -noout -out "$NEW_KEY_PATH"
openssl ec -in "$NEW_KEY_PATH" -pubout -out "$NEW_PUB_PATH" 2>/dev/null
;;
ecdsa-p384)
openssl ecparam -name secp384r1 -genkey -noout -out "$NEW_KEY_PATH"
openssl ec -in "$NEW_KEY_PATH" -pubout -out "$NEW_PUB_PATH" 2>/dev/null
;;
rsa-4096)
openssl genrsa -out "$NEW_KEY_PATH" 4096
openssl rsa -in "$NEW_KEY_PATH" -pubout -out "$NEW_PUB_PATH" 2>/dev/null
;;
*)
log_error "Unknown key type: $KEY_TYPE"
exit 1
;;
esac
chmod 600 "$NEW_KEY_PATH"
chmod 644 "$NEW_PUB_PATH"
log_info ""
log_info "New signing key generated:"
log_info " Private key: $NEW_KEY_PATH"
log_info " Public key: $NEW_PUB_PATH"
log_info ""
log_info "Key fingerprint:"
openssl dgst -sha256 -r "$NEW_PUB_PATH" | cut -d' ' -f1
log_info ""
log_warn "Store the public key securely for distribution."
log_warn "Next: Run '$0 activate' to enable dual-key signing."
;;
activate)
log_step "Activating dual-key signing..."
# List available keys
log_info "Available signing keys in $KEY_DIR:"
ls -la "$KEY_DIR"/*.pem 2>/dev/null || log_warn "No .pem files found"
if [[ -n "$CI_CONFIG" ]] && [[ -f "$CI_CONFIG" ]]; then
log_info ""
log_info "CI config file: $CI_CONFIG"
log_warn "Manual update required:"
echo " 1. Add the new key path to signing configuration"
echo " 2. Ensure both old and new keys can sign"
echo " 3. Update verification to accept both key signatures"
fi
log_info ""
log_info "Dual-key activation checklist:"
echo " [ ] New key added to CI/CD pipeline"
echo " [ ] New public key distributed to verifiers"
echo " [ ] Both keys tested for signing"
echo " [ ] Grace period documented: $GRACE_DAYS days"
log_info ""
log_warn "Grace period starts now. Do not retire old key for $GRACE_DAYS days."
log_info "Next: Run '$0 verify' to confirm both keys work."
;;
verify)
log_step "Verifying signing key status..."
# Test each key
log_info "Testing signing keys in $KEY_DIR:"
TEST_FILE=$(mktemp)
echo "StellaOps key rotation verification $(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$TEST_FILE"
for keyfile in "$KEY_DIR"/*.pem; do
if [[ -f "$keyfile" ]]; then
keyname=$(basename "$keyfile" .pem)
TEST_SIG=$(mktemp)
if openssl dgst -sha256 -sign "$keyfile" -out "$TEST_SIG" "$TEST_FILE" 2>/dev/null; then
log_info " $keyname: OK (signing works)"
else
log_warn " $keyname: FAILED (cannot sign)"
fi
rm -f "$TEST_SIG"
fi
done
rm -f "$TEST_FILE"
log_info ""
log_info "Verification checklist:"
echo " [ ] All active keys can sign successfully"
echo " [ ] Old attestations still verify"
echo " [ ] New attestations verify with new key"
echo " [ ] Verifiers have both public keys"
;;
retire)
if [[ -z "$OLD_KEY_NAME" ]]; then
log_error "retire requires --old-key"
usage
fi
OLD_KEY_PATH="$KEY_DIR/${OLD_KEY_NAME}.pem"
OLD_PUB_PATH="$KEY_DIR/${OLD_KEY_NAME}.pub"
if [[ ! -f "$OLD_KEY_PATH" ]] && [[ ! -f "$KEY_DIR/${OLD_KEY_NAME}" ]]; then
log_error "Old key not found: $OLD_KEY_NAME"
exit 1
fi
log_step "Retiring old signing key: $OLD_KEY_NAME"
log_warn "This is IRREVERSIBLE. Ensure:"
echo " 1. Grace period ($GRACE_DAYS days) has passed"
echo " 2. All systems have been updated to use new key"
echo " 3. Old attestations have been resigned or archived"
read -p "Type 'RETIRE' to proceed: " CONFIRM
if [[ "$CONFIRM" != "RETIRE" ]]; then
log_error "Aborted"
exit 1
fi
# Archive old key (don't delete immediately)
ARCHIVE_DIR="$KEY_DIR/archived"
mkdir -p "$ARCHIVE_DIR"
chmod 700 "$ARCHIVE_DIR"
TIMESTAMP=$(date -u +%Y%m%d%H%M%S)
if [[ -f "$OLD_KEY_PATH" ]]; then
mv "$OLD_KEY_PATH" "$ARCHIVE_DIR/${OLD_KEY_NAME}-retired-${TIMESTAMP}.pem"
fi
if [[ -f "$OLD_PUB_PATH" ]]; then
mv "$OLD_PUB_PATH" "$ARCHIVE_DIR/${OLD_KEY_NAME}-retired-${TIMESTAMP}.pub"
fi
log_info ""
log_info "Old key archived to: $ARCHIVE_DIR/"
log_info "Key rotation complete!"
log_warn ""
log_warn "Post-retirement checklist:"
echo " [ ] Remove old key from CI/CD configuration"
echo " [ ] Update documentation"
echo " [ ] Notify stakeholders of completion"
echo " [ ] Delete archived key after retention period"
;;
esac
echo ""