mirror of
https://github.com/Sea-Haven-Industries/seahaven-account-baseline.git
synced 2026-08-04 16:56:14 +00:00
fix(scripts): account-id assertion in cfn-stack-decommission; complete the aws-cdk-lib 2.262.0 bump (patched brace-expansion); document CMK cutover trap
- cfn-stack-decommission.sh: --account-id is now REQUIRED and asserted against sts get-caller-identity before anything runs. Stack names are no longer org-unique (seahaven-dynamodb-cmk now exists in mgmt AND prod), so a name-only lookup under the wrong ambient profile could report or delete the wrong account's stack (security-review LOGIC-001). - package.json/lock: PR #56's bump-for-patched-brace-expansion landed the commit title but not the pin; package.json still said 2.261.0 and the lockfile still resolved brace-expansion 5.0.6 (GHSA-3jxr-9vmj-r5cp HIGH, blocking the pre-commit scanner). Pin 2.262.0 and regenerate; npm audit now clean. - bin/app.ts comments: slack-bot cutover MUST grant the PROD key ARN, never the account-local mgmt SSM param (LOGIC-005); failed-first-create orphan CMK recovery note (LOGIC-004).
This commit is contained in:
parent
5dab14ce68
commit
c1347fcdb5
4 changed files with 63 additions and 26 deletions
11
bin/app.ts
11
bin/app.ts
|
|
@ -185,6 +185,17 @@ const prodDynamoCmk = new DynamoDbCmkStack(app, "dynamodb-cmk-prod", {
|
|||
// PrincipalArn is wildcarded by stack prefix because CFN role suffixes rotate;
|
||||
// each bot role must ALSO carry an identity-policy grant on this key ARN
|
||||
// (added in the slack-bot repo's cutover PR).
|
||||
//
|
||||
// ⚠️ CUTOVER TRAP: the slack-bot's existing pattern resolves the CMK via SSM
|
||||
// /seahaven/dynamodb/cmk-arn — that parameter is ACCOUNT-LOCAL and in mgmt
|
||||
// resolves the MGMT key forever. The cutover PR's identity grant must use the
|
||||
// PROD key ARN (this stack's DynamoDbCmkArn output), never the mgmt param;
|
||||
// reusing the SSM pattern grants the wrong key and fails only at runtime.
|
||||
//
|
||||
// Recovery note (failed FIRST create): the key is RETAIN, its alias/SSM param
|
||||
// are not — a CREATE_FAILED rollback orphans an unaliased rotation-enabled
|
||||
// key. Before re-running the deploy, list unaliased CMKs in prod and schedule
|
||||
// deletion of the orphan.
|
||||
prodDynamoCmk.key.addToResourcePolicy(
|
||||
new iam.PolicyStatement({
|
||||
sid: "AllowMgmtSlackBotReadViaDynamoDb",
|
||||
|
|
|
|||
53
package-lock.json
generated
53
package-lock.json
generated
|
|
@ -1,14 +1,14 @@
|
|||
{
|
||||
"name": "seahaven-account-baseline",
|
||||
"name": "seahaven-org-baseline",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "seahaven-account-baseline",
|
||||
"name": "seahaven-org-baseline",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"aws-cdk-lib": "2.261.0",
|
||||
"aws-cdk-lib": "2.262.0",
|
||||
"constructs": "^10.0.0"
|
||||
},
|
||||
"bin": {
|
||||
|
|
@ -36,9 +36,9 @@
|
|||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@aws-cdk/cloud-assembly-schema": {
|
||||
"version": "54.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-54.2.0.tgz",
|
||||
"integrity": "sha512-u3lFXmiXSBozxGBmKTCVD/2mTDsaXzLZH3KYiIQKcB+zPldXOeE5TnooBgKV9ih2jVTo8ML0HpkhfAq2eiv0eQ==",
|
||||
"version": "54.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-54.13.0.tgz",
|
||||
"integrity": "sha512-C6LS1YxugR7j6BPVjhVsWRu/VNN8eoGDOf7dHafPviz6Y5Nv/FjVIGIPoC6jJmgJcea7VOM9TPHbBEwapCUySg==",
|
||||
"bundleDependencies": [
|
||||
"jsonschema",
|
||||
"semver"
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"jsonschema": "^1.5.0",
|
||||
"semver": "^7.8.1"
|
||||
"semver": "^7.8.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18.0.0"
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": {
|
||||
"version": "7.8.1",
|
||||
"version": "7.8.5",
|
||||
"inBundle": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
|
|
@ -887,10 +887,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib": {
|
||||
"version": "2.261.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.261.0.tgz",
|
||||
"integrity": "sha512-e52e3Abjg0HkuRWlWwtSv5+ZiMW1rhCDdL9ff7lzWXInU8xdfLJpuoimfa0IJwjiNGyphppgg52Azx9M80OA0g==",
|
||||
"version": "2.262.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.262.0.tgz",
|
||||
"integrity": "sha512-6zRVoWRd8kQs9ZZ9xhERSm36W8uWS2vW3/g9Zx0xlhvRRiUwTc80bzfJkohKGdT/5AOW+wy6fSDvohavG94pcA==",
|
||||
"bundleDependencies": [
|
||||
"@aws/cloudformation-validate",
|
||||
"@balena/dockerignore",
|
||||
"@aws-cdk/cloud-assembly-api",
|
||||
"case",
|
||||
|
|
@ -907,17 +908,18 @@
|
|||
"dependencies": {
|
||||
"@aws-cdk/asset-awscli-v1": "2.2.282",
|
||||
"@aws-cdk/asset-node-proxy-agent-v6": "^2.1.2",
|
||||
"@aws-cdk/cloud-assembly-api": "^2.2.5",
|
||||
"@aws-cdk/cloud-assembly-schema": "^54.0.0",
|
||||
"@aws-cdk/cloud-assembly-api": "^2.2.6",
|
||||
"@aws-cdk/cloud-assembly-schema": "^54.11.0",
|
||||
"@aws/cloudformation-validate": "1.5.0-beta",
|
||||
"@balena/dockerignore": "^1.0.2",
|
||||
"case": "1.6.3",
|
||||
"fs-extra": "^11.3.5",
|
||||
"fs-extra": "^11.3.6",
|
||||
"ignore": "^5.3.2",
|
||||
"jsonschema": "^1.5.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"minimatch": "^10.2.5",
|
||||
"punycode": "^2.3.1",
|
||||
"semver": "^7.8.1",
|
||||
"semver": "^7.8.5",
|
||||
"yaml": "1.10.3"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -928,18 +930,27 @@
|
|||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api": {
|
||||
"version": "2.2.5",
|
||||
"version": "2.2.6",
|
||||
"inBundle": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"jsonschema": "^1.5.0",
|
||||
"semver": "^7.8.0"
|
||||
"semver": "^7.8.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@aws-cdk/cloud-assembly-schema": ">=53.28.0"
|
||||
"@aws-cdk/cloud-assembly-schema": ">=54.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/@aws/cloudformation-validate": {
|
||||
"version": "1.5.0-beta",
|
||||
"inBundle": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "^22.15.0",
|
||||
"npm": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": {
|
||||
|
|
@ -956,7 +967,7 @@
|
|||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"version": "5.0.7",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -975,7 +986,7 @@
|
|||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/fs-extra": {
|
||||
"version": "11.3.5",
|
||||
"version": "11.3.6",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -1061,7 +1072,7 @@
|
|||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/semver": {
|
||||
"version": "7.8.1",
|
||||
"version": "7.8.5",
|
||||
"inBundle": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"typescript": "~7.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws-cdk-lib": "2.261.0",
|
||||
"aws-cdk-lib": "2.262.0",
|
||||
"constructs": "^10.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,28 +12,43 @@
|
|||
# `feedback_cfn_decommission_and_remediation`.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/cfn-stack-decommission.sh [--profile NAME] [--execute] STACK
|
||||
# scripts/cfn-stack-decommission.sh --account-id ID [--profile NAME] [--execute] STACK
|
||||
#
|
||||
# --account-id REQUIRED. Asserted against the credentials' actual account
|
||||
# before anything runs. Stack names are NOT org-unique
|
||||
# (seahaven-dynamodb-cmk exists in both mgmt and prod), so a
|
||||
# name-only lookup with the wrong ambient profile would
|
||||
# report — or with --execute, DELETE — the wrong account's
|
||||
# stack and then purge its Retain orphans.
|
||||
# (no --execute) REPORT only: termination protection, consumed exports,
|
||||
# Retain resources (orphans-to-be), in-stack S3 buckets.
|
||||
# --execute Disable termination protection, empty Delete-policy buckets,
|
||||
# delete the stack, wait, then delete the Retain orphans.
|
||||
#
|
||||
set -euo pipefail
|
||||
PROFILE_ARG=(); EXECUTE=0; STACK=""
|
||||
PROFILE_ARG=(); EXECUTE=0; STACK=""; EXPECTED_ACCOUNT=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--profile) PROFILE_ARG=(--profile "$2"); shift 2 ;;
|
||||
--account-id) EXPECTED_ACCOUNT="$2"; shift 2 ;;
|
||||
--execute) EXECUTE=1; shift ;;
|
||||
-h|--help) sed -n '2,22p' "$0"; exit 0 ;;
|
||||
-h|--help) sed -n '2,28p' "$0"; exit 0 ;;
|
||||
-*) echo "unknown flag: $1" >&2; exit 2 ;;
|
||||
*) STACK="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
[[ -z "$STACK" ]] && { echo "usage: $0 [--profile NAME] [--execute] STACK" >&2; exit 2; }
|
||||
[[ -z "$STACK" ]] && { echo "usage: $0 --account-id ID [--profile NAME] [--execute] STACK" >&2; exit 2; }
|
||||
[[ -z "$EXPECTED_ACCOUNT" ]] && { echo "ERROR: --account-id is required (stack names are not org-unique)." >&2; exit 2; }
|
||||
aws_() { aws "${PROFILE_ARG[@]}" "$@"; }
|
||||
R="us-east-1"
|
||||
|
||||
CALLER_ACCOUNT="$(aws_ sts get-caller-identity --query Account --output text)"
|
||||
if [[ "$CALLER_ACCOUNT" != "$EXPECTED_ACCOUNT" ]]; then
|
||||
echo "ABORT: credentials resolve to account $CALLER_ACCOUNT, expected $EXPECTED_ACCOUNT." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "== account: $CALLER_ACCOUNT (verified) =="
|
||||
|
||||
echo "== stack: $STACK =="
|
||||
aws_ cloudformation describe-stacks --stack-name "$STACK" --region "$R" \
|
||||
--query 'Stacks[0].{Status:StackStatus,TermProt:EnableTerminationProtection}' --output table
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue