mirror of
https://github.com/Sea-Haven-Industries/seahaven-org-baseline.git
synced 2026-10-03 03:23:12 +00:00
Docs + tooling: README phase-2 backup, iam-user-delete script (#10)
README: document AWS Backup phase-2 (phase2-offsite-everything selection), remove retired database-1 from phase-1 scope (audit H-19), update roadmap + verify smoke-test to a live resource. scripts/iam-user-delete.sh: reusable full IAM user teardown (keys, policies, groups, MFA, login profile, certs, SSH keys, service creds, then user) with --profile/--yes and a guard against deleting the caller's own identity. Built from the Day 4 audit IAM cleanup.
This commit is contained in:
parent
1d6668090c
commit
0dd8d2a7af
2 changed files with 148 additions and 4 deletions
43
README.md
43
README.md
|
|
@ -59,9 +59,12 @@ recovery point cross-region into a governance-locked vault.
|
|||
| Service role | `seahaven-backup-service-role` | **Backup-only** (Backup + S3-Backup managed policies); restore perms intentionally deferred |
|
||||
|
||||
**Phase-1 scope** (selected by explicit ARN, not tags, to avoid drifting other
|
||||
stacks): RDS `database-1`, RDS `proposal-system-db`, DynamoDB `PaymentsDashboard`,
|
||||
stacks): RDS `proposal-system-db`, DynamoDB `PaymentsDashboard`,
|
||||
DynamoDB `purchase-orders`, S3 `accounting.seahaven.com`,
|
||||
`seahaven-payments-csv-328440206208`, `google-workspace-seahavenind.com`.
|
||||
*(RDS `database-1` was originally in this set but was retired 2026-06-03 —
|
||||
audit H-19, idle 0 conn/60d — and removed from the selection; its final
|
||||
encrypted recovery point is retained in `seahaven-offsite` for 7 years.)*
|
||||
|
||||
**Coexists with** existing EBS DLM snapshots and DynamoDB PITR — it supplements
|
||||
them with the missing offsite + immutable leg; it does not replace them.
|
||||
|
|
@ -84,6 +87,35 @@ them with the missing offsite + immutable leg; it does not replace them.
|
|||
it to us-west-2 first; if the copy fails, encrypt it or drop it from the copy.
|
||||
3. Enable DynamoDB PITR (H-7) on the two tables for between-window recovery.
|
||||
|
||||
### AWS Backup phase 2 (audit Day 4)
|
||||
|
||||
Expands the same `seahaven-critical-daily` plan to every remaining data store, so
|
||||
all of DynamoDB + EBS get the offsite + immutable leg ("offsite for everything").
|
||||
|
||||
| Resource | Logical ID | Notes |
|
||||
|---|---|---|
|
||||
| Phase-2 selection | `Plan/Phase2Resources` (`phase2-offsite-everything`) | Same plan, same `seahaven-backup-service-role`, same daily + cross-region copy rule |
|
||||
|
||||
**Phase-2 scope:** the 15 remaining DynamoDB tables (all except the two phase-1
|
||||
financial tables + the deleted ledgerflow tables) and all 9 in-use EBS volumes,
|
||||
again **by explicit ARN** — tag-based selection was deliberately avoided because
|
||||
the file-share volumes are standalone-managed and the tables are owned by other
|
||||
stacks, so tagging here would drift them.
|
||||
|
||||
**No IAM change:** `AWSBackupServiceRolePolicyForBackup` already grants the
|
||||
DynamoDB/RDS/EBS backup actions, so phase 2 reuses the phase-1 role unchanged
|
||||
(cross-reviewed, no BLOCK).
|
||||
|
||||
**Known tradeoff (→ Jira INFRA-31):** explicit-ARN EBS entries go stale if a
|
||||
volume is replaced (new volume id), silently dropping it from backup. Migrating
|
||||
the EBS portion to tag-based selection (with the tag codified in each owning
|
||||
stack) is the resilient follow-up; scheduled drift detection is the interim
|
||||
backstop.
|
||||
|
||||
**Also enabled outside this stack (audit H-7, via CLI — codify per stack →
|
||||
INFRA-30):** PITR + `DeletionProtectionEnabled` on 12 more DynamoDB tables
|
||||
(account-wide PITR now 19/21).
|
||||
|
||||
### Detective controls + budget (audit Day 1)
|
||||
|
||||
Account-level detective layer, in `lib/detective-controls.ts`, plus the cost
|
||||
|
|
@ -184,8 +216,9 @@ aws cloudwatch put-metric-alarm --alarm-name forgejo-backup-verification-errors
|
|||
## Roadmap (same stack)
|
||||
|
||||
Detective layer multi-region expansion (GuardDuty/Config/Security Hub beyond
|
||||
us-east-1). Backup phase 2: expand past the phase-1 set via tag-based selection
|
||||
and graduate the offsite vault to compliance mode.
|
||||
us-east-1). Backup: phase 2 is deployed (see above); remaining is migrating the
|
||||
phase-2 EBS entries to tag-based selection (INFRA-31) and graduating the offsite
|
||||
vault to compliance mode.
|
||||
|
||||
## Deploy
|
||||
|
||||
|
|
@ -214,9 +247,11 @@ aws backup describe-backup-vault --backup-vault-name seahaven-offsite --region u
|
|||
aws backup get-backup-plan --backup-plan-id <id> # daily rule + CopyAction
|
||||
# Smoke test: on-demand backup of one resource, then confirm the cross-region copy lands
|
||||
aws backup start-backup-job --backup-vault-name seahaven-primary \
|
||||
--resource-arn arn:aws:rds:us-east-1:328440206208:db:database-1 \
|
||||
--resource-arn arn:aws:rds:us-east-1:328440206208:db:proposal-system-db \
|
||||
--iam-role-arn arn:aws:iam::328440206208:role/seahaven-backup-service-role
|
||||
aws backup list-copy-jobs --region us-west-2 # copy to offsite present + COMPLETED
|
||||
# Phase-2 selections live on the plan:
|
||||
aws backup list-backup-selections --backup-plan-id <id> --query 'BackupSelectionsList[].SelectionName' # critical-data + phase2-offsite-everything
|
||||
```
|
||||
|
||||
Detective layer + governance (Day 1):
|
||||
|
|
|
|||
109
scripts/iam-user-delete.sh
Executable file
109
scripts/iam-user-delete.sh
Executable file
|
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# iam-user-delete.sh — fully delete one or more IAM users (account 328440206208).
|
||||
#
|
||||
# IAM refuses to delete a user that still has attached/inline policies, access
|
||||
# keys, group memberships, MFA devices, a login profile, signing certs, SSH keys,
|
||||
# or service-specific credentials. This tears all of that down in order, then
|
||||
# deletes the user. Built from the Day 4 audit cleanup (frappe/termius/ledgerflow,
|
||||
# then office_mac/home_desktop/Personal-laptop). See reference memory
|
||||
# `reference_identity_center_workmail` for the SSO context.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/iam-user-delete.sh [--profile NAME] [--yes] USER [USER ...]
|
||||
#
|
||||
# --profile NAME AWS CLI profile (default: $AWS_PROFILE or the default chain).
|
||||
# Post-SSO-cutover this is normally `amoussa-seahaven`.
|
||||
# --yes Skip the per-user confirmation prompt.
|
||||
#
|
||||
# Safety:
|
||||
# * Refuses to delete the user the current credentials authenticate as.
|
||||
# * Deactivates access keys before deleting them (a brief, reversible window
|
||||
# if you remove --yes and inspect between users).
|
||||
# * Prints each user's attachments before deleting so there is a record.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
PROFILE_ARG=()
|
||||
ASSUME_YES=0
|
||||
USERS=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--profile) PROFILE_ARG=(--profile "$2"); shift 2 ;;
|
||||
--yes|-y) ASSUME_YES=1; shift ;;
|
||||
-h|--help) sed -n '2,30p' "$0"; exit 0 ;;
|
||||
-*) echo "unknown flag: $1" >&2; exit 2 ;;
|
||||
*) USERS+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
[[ ${#USERS[@]} -eq 0 ]] && { echo "usage: $0 [--profile NAME] [--yes] USER [USER ...]" >&2; exit 2; }
|
||||
|
||||
aws_() { aws "${PROFILE_ARG[@]}" "$@"; }
|
||||
|
||||
# Guard: never delete the identity we're running as.
|
||||
SELF_ARN="$(aws_ sts get-caller-identity --query Arn --output text)"
|
||||
echo "running as: $SELF_ARN"
|
||||
|
||||
delete_user() {
|
||||
local u="$1"
|
||||
if ! aws_ iam get-user --user-name "$u" >/dev/null 2>&1; then
|
||||
echo " $u: does not exist, skipping"; return 0
|
||||
fi
|
||||
if [[ "$SELF_ARN" == *":user/$u" ]]; then
|
||||
echo " $u: REFUSING — this is the identity you are authenticated as" >&2; return 1
|
||||
fi
|
||||
|
||||
echo "== $u =="
|
||||
echo " keys: $(aws_ iam list-access-keys --user-name "$u" --query 'AccessKeyMetadata[].AccessKeyId' --output text)"
|
||||
echo " attached: $(aws_ iam list-attached-user-policies --user-name "$u" --query 'AttachedPolicies[].PolicyName' --output text)"
|
||||
echo " inline: $(aws_ iam list-user-policies --user-name "$u" --query 'PolicyNames' --output text)"
|
||||
echo " groups: $(aws_ iam list-groups-for-user --user-name "$u" --query 'Groups[].GroupName' --output text)"
|
||||
|
||||
if [[ $ASSUME_YES -eq 0 ]]; then
|
||||
read -r -p " delete user '$u'? [y/N] " ans
|
||||
[[ "$ans" =~ ^[Yy]$ ]] || { echo " skipped"; return 0; }
|
||||
fi
|
||||
|
||||
# access keys: deactivate (reversible) then delete
|
||||
for k in $(aws_ iam list-access-keys --user-name "$u" --query 'AccessKeyMetadata[].AccessKeyId' --output text); do
|
||||
aws_ iam update-access-key --user-name "$u" --access-key-id "$k" --status Inactive
|
||||
aws_ iam delete-access-key --user-name "$u" --access-key-id "$k"
|
||||
done
|
||||
# detach managed policies
|
||||
for p in $(aws_ iam list-attached-user-policies --user-name "$u" --query 'AttachedPolicies[].PolicyArn' --output text); do
|
||||
aws_ iam detach-user-policy --user-name "$u" --policy-arn "$p"
|
||||
done
|
||||
# delete inline policies
|
||||
for ip in $(aws_ iam list-user-policies --user-name "$u" --query 'PolicyNames[]' --output text); do
|
||||
aws_ iam delete-user-policy --user-name "$u" --policy-name "$ip"
|
||||
done
|
||||
# remove from groups
|
||||
for g in $(aws_ iam list-groups-for-user --user-name "$u" --query 'Groups[].GroupName' --output text); do
|
||||
aws_ iam remove-user-from-group --user-name "$u" --group-name "$g"
|
||||
done
|
||||
# MFA devices
|
||||
for m in $(aws_ iam list-mfa-devices --user-name "$u" --query 'MFADevices[].SerialNumber' --output text); do
|
||||
aws_ iam deactivate-mfa-device --user-name "$u" --serial-number "$m"
|
||||
done
|
||||
# login profile (console password)
|
||||
aws_ iam delete-login-profile --user-name "$u" 2>/dev/null || true
|
||||
# signing certs
|
||||
for c in $(aws_ iam list-signing-certificates --user-name "$u" --query 'Certificates[].CertificateId' --output text 2>/dev/null); do
|
||||
aws_ iam delete-signing-certificate --user-name "$u" --certificate-id "$c"
|
||||
done
|
||||
# SSH public keys (CodeCommit)
|
||||
for s in $(aws_ iam list-ssh-public-keys --user-name "$u" --query 'SSHPublicKeys[].SSHPublicKeyId' --output text 2>/dev/null); do
|
||||
aws_ iam delete-ssh-public-key --user-name "$u" --ssh-public-key-id "$s"
|
||||
done
|
||||
# service-specific credentials
|
||||
for sc in $(aws_ iam list-service-specific-credentials --user-name "$u" --query 'ServiceSpecificCredentials[].ServiceSpecificCredentialId' --output text 2>/dev/null); do
|
||||
aws_ iam delete-service-specific-credential --user-name "$u" --service-specific-credential-id "$sc"
|
||||
done
|
||||
|
||||
aws_ iam delete-user --user-name "$u"
|
||||
echo " $u: deleted"
|
||||
}
|
||||
|
||||
rc=0
|
||||
for u in "${USERS[@]}"; do delete_user "$u" || rc=1; done
|
||||
exit $rc
|
||||
Loading…
Add table
Reference in a new issue