Files
acdl/scripts/rotate_spike_key.sh
T
Jon Chery 0e6ecae26d feat(P4): Nova rebrand — AWS resource migration (REQ-163)
Rename all acdl-* AWS resources → nova-* across terraform (DynamoDB,
Secrets Manager, Lambda, SNS, SG, KMS alias, ECS, ECR, IAM user/policy,
state bucket, ALB, VPC/subnet names). Lambda default table names → nova-*
(D-111). State bucket backend → nova-tfstate (-migrate-state documented).
New docs/NOVA_AWS_MIGRATION.md runbook (staged migration + rollback).
New scripts/migrate_dynamodb_data.py (scan+copy, dry-run default).
acdl-deploy- → nova-deploy- role ARN in deploy workflows. Test fixtures
updated; terraform validate + pytest + run_ci.sh PASS.

---ci---
project: acdl
phase: 4
milestone: v1.15
status: execute
---/ci---
2026-07-30 01:54:26 +00:00

109 lines
4.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# scripts/rotate_spike_key.sh - rotate the nova-spike-runner IAM access key.
#
# Uses the bootstrap root key (NOVA_BOOTSTRAP_AWS_*, ACDL_BOOTSTRAP_AWS_*
# fallback) from the env to:
# 1. List nova-spike-runner's access keys.
# 2. Create a new key.
# 3. Deactivate + delete the old key(s).
# 4. Write the new key to gitignored .env.secrets (chmod 600).
# 5. Optionally upload to Gitea secrets if NOVA_GITEA_TOKEN is set.
#
# Idempotent: re-running always ends with exactly 1 active key for the user.
# Does NOT rotate the bootstrap root key (D-034 closure = manual user step).
#
# Spike scope (D-039): the spike user key is per-run-rotated; real OIDC is
# v1.2 (blocked on go-gitea/gitea#36988).
# Nova rebrand (P4, REQ-163): IAM user renamed acdl-spike-runner →
# nova-spike-runner.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
ENV_FILE="$ROOT/.env.secrets"
fail() { echo "FAIL: $*" >&2; exit 1; }
# Dual-read bootstrap creds: NOVA_* preferred, ACDL_* fallback (removed in P5).
: "${NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID:-${ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID:?set NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID (or ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID) to the root key}}"
: "${NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY:-${ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY:?set NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY (or ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY) to the root key}}"
REGION="${AWS_DEFAULT_REGION:-us-east-1}"
USER_NAME="nova-spike-runner"
# Confirm .env.secrets is gitignored before writing to it.
git check-ignore -q "$ENV_FILE" || fail "$ENV_FILE is not gitignored — refusing to write the key"
python3 - <<'PY'
import os
import sys
import json
import boto3
region = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
user = "nova-spike-runner"
env_file = os.path.join(os.getcwd(), ".env.secrets")
# Dual-read bootstrap creds: NOVA_* preferred, ACDL_* fallback (G-106, removed in P5).
bootstrap_key = os.environ.get("NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID") or os.environ["ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID"]
bootstrap_secret = os.environ.get("NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY") or os.environ["ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY"]
session = boto3.Session(
aws_access_key_id=bootstrap_key,
aws_secret_access_key=bootstrap_secret,
region_name=region,
)
iam = session.client("iam")
# List current keys.
keys = iam.list_access_keys(UserName=user).get("AccessKeyMetadata", [])
active = [k for k in keys if k["Status"] == "Active"]
# Create a new key first (so the user always has a working key during rotation).
new = iam.create_access_key(UserName=user)["AccessKey"]
new_id = new["AccessKeyId"]
new_secret = new["SecretAccessKey"]
print(f"iam: created new key {new_id} for {user}", file=sys.stderr)
# Deactivate + delete the old keys.
for k in active:
old_id = k["AccessKeyId"]
if old_id == new_id:
continue
iam.update_access_key(UserName=user, AccessKeyId=old_id, Status="Inactive")
iam.delete_access_key(UserName=user, AccessKeyId=old_id)
print(f"iam: deactivated+deleted old key {old_id}", file=sys.stderr)
# Write the new key to gitignored .env.secrets (chmod 600).
# Nova rebrand (P2): keys are NOVA_*; the ACDL_* legacy keys are the
# dual-read fallback source until P5 (kept as comments in .env.secrets).
with open(env_file, "w") as fh:
fh.write(f"NOVA_AWS_ACCESS_KEY_ID={new_id}\n")
fh.write(f"NOVA_AWS_SECRET_ACCESS_KEY={new_secret}\n")
fh.write(f"AWS_DEFAULT_REGION={region}\n")
os.chmod(env_file, 0o600)
print(f"rotated key written to {env_file} (chmod 600)", file=sys.stderr)
# Optionally upload to Gitea secrets.
# Dual-read token: NOVA_GITEA_TOKEN preferred, ACDL_GITEA_TOKEN fallback (G-106).
gitea_token = os.environ.get("NOVA_GITEA_TOKEN") or os.environ.get("ACDL_GITEA_TOKEN")
if gitea_token:
import urllib.request
base = "https://git.cloudinit.dev/api/v1/repos/continuous-intelligence/acdl/actions/secrets"
for name, value in [("NOVA_AWS_ACCESS_KEY_ID", new_id),
("NOVA_AWS_SECRET_ACCESS_KEY", new_secret)]:
req = urllib.request.Request(
f"{base}/{name}",
data=json.dumps({"value": value}).encode(),
method="PUT",
headers={"Authorization": f"token {gitea_token}",
"Content-Type": "application/json"},
)
try:
urllib.request.urlopen(req).read()
print(f"gitea: secret {name} uploaded", file=sys.stderr)
except Exception as e:
print(f"gitea: secret {name} upload FAILED: {e}", file=sys.stderr)
else:
print("gitea: NOVA_GITEA_TOKEN not set; Gitea secret upload skipped (v1.2 hardening)", file=sys.stderr)
print(f"OK: {user} now has exactly 1 active key: {new_id}")
PY