Document centralized root access: README runbook + stack comment updates (#52)
Some checks are pending
Deploy / deploy-management (push) Waiting to run
Deploy / deploy-external-dev (push) Waiting to run
Deploy / deploy-security (push) Waiting to run
Deploy / deploy-dev (push) Waiting to run
Deploy / deploy-prod (push) Waiting to run

Comment/docs only, no template change (synth verified). Records the
2026-07-14 rollout: features enabled, member root credentials deleted,
recovery runbook (manual detach, inheritance + propagation gotchas),
new-account flow superseding root-harden-before-OU-move, extdev 5-SCP
quota saturation.
This commit is contained in:
Adam Moussa 2026-07-14 18:32:40 -04:00 • committed by GitHub
parent 75888e045e
commit ed26ff937d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 81 additions and 5 deletions

View file

@ -266,6 +266,71 @@ aws ce update-cost-allocation-tags-status --cost-allocation-tags-status \
'TagKey=Project,Status=Active' 'TagKey=Owner,Status=Active' 'TagKey=Environment,Status=Active'
```
### Centralized root access management (org-level, no CloudFormation resource)
**STATUS: ENABLED 2026-07-14, all member root credentials DELETED** (evidence:
`~/Documents/repositories/_audits/centralized-root-access-evidence-2026-07-14.md`).
Member accounts have NO root credentials; the only root path is a privileged
session from the management account. The management account's own root is NOT
centrally manageable and stays password+MFA hardened.
```bash
# Enable (mgmt account). ORDER MATTERS: trusted access must be enabled
# explicitly first — enable-organizations-root-credentials-management does
# NOT auto-enable it (fails ServiceAccessNotEnabledException).
aws organizations enable-aws-service-access --service-principal iam.amazonaws.com
aws iam enable-organizations-root-credentials-management
aws iam enable-organizations-root-sessions
# Periodic verification (add to governance checks): expect BOTH features
aws iam list-organizations-features
```
**Audit / delete member root credentials** (task-scoped root sessions, 15-min):
```bash
aws sts assume-root --target-principal <acct> \
--task-policy-arn arn=arn:aws:iam::aws:policy/root-task/IAMAuditRootUserCredentials
# then, with the session creds (no --user-name; root has none):
# get-login-profile / list-mfa-devices / list-access-keys / list-signing-certificates
aws sts assume-root --target-principal <acct> \
--task-policy-arn arn=arn:aws:iam::aws:policy/root-task/IAMDeleteRootUserCredentials
# delete-login-profile; deactivate-mfa-device --serial-number <arn>
# GOTCHA: the delete task policy explicitly DENIES iam:DeleteVirtualMFADevice —
# remove the orphaned virtual-device OBJECT via OrganizationAccountAccessRole.
# DONE = four surfaces clear: login profile NoSuchEntity, MFA/keys/certs all empty.
```
**Root recovery runbook** (proven by drill on prod 2026-07-14):
1. `deny-root-user` (p-2idoxozz) DENIES root sessions in every covered OU
(SCPs evaluate `sts:AssumeRoot` sessions — the principal is the member
root ARN). Recovery therefore starts with a **manual, temporary detach**
(`aws organizations detach-policy` — NOT a CDK deploy), timeboxed minutes.
2. GOTCHA (inheritance): p-2idoxozz is attached to `workloads` AND its child
OUs — for an account under workloads/, detach from BOTH the child OU and
workloads, or the inherited deny still applies. Allow ~10s propagation.
3. Freeze deploys of `seahaven-org-governance` for the window (a concurrent
deploy would re-attach mid-recovery); verify no CD run in flight first.
4. `aws sts assume-root --target-principal <acct> --task-policy-arn
arn=arn:aws:iam::aws:policy/root-task/IAMCreateRootUserPassword` →
`create-login-profile` (no args) restores a login profile.
5. Do the root-only task, DELETE the credentials again (four-surface verify),
reattach the SCP(s), confirm `list-targets-for-policy` matches the
pre-detach capture and stack drift is IN_SYNC.
6. extdev extra: `external-dev-iam-guardrails` also denies
`iam:CreateLoginProfile` — recovery there needs that SCP temporarily
detached too. The extdev OU sits at the **5-SCP hard quota**: any new
guardrail for extdev must attach at the ACCOUNT (396287094661) or
consolidate into an existing policy.
**New-account flow (supersedes root-harden-before-OU-move):** create the
account at the org ROOT → it has no root credentials from birth (verify with
the audit session) → bootstrap + deploy role + baseline → `move-account` into
the target OU → verify SCP inheritance + region-lock canary. No mailbox or
MFA enrollment step. Root-usage monitoring: GuardDuty
`Policy:IAMUser/RootCredentialUsage` + CIS 4.3 alarm remain active.
### Delegated security administration (Phase 3, no CloudFormation resource)
Account **seahaven-security (001520130573)** is the org's delegated

View file

@ -44,8 +44,10 @@ const scpContent = (name: string): Record<string, unknown> =>
*
* Cross-review dispositions (GPT-4.1, 2026-07-14):
* - deny-root-user blocks root MFA enrollment (iam:EnableMFADevice as root).
* OPERATIONAL REQUIREMENT: create new accounts at the org ROOT, complete
* root hardening (MFA, contacts), THEN move-account into the target OU.
* SUPERSEDED same day by centralized root access management: member root
* credentials are DELETED (none exist to harden), so new accounts go
* create-at-ROOT → verify credential-free → baseline → move-account.
* Recovery = assume-root + temporary manual SCP detach (README runbook).
* - Delegated-admin ops (Phase 3) are unaffected by protect-security-baseline:
* org-managed GuardDuty/SecurityHub act on members via service-linked
* roles, which SCPs do not evaluate. If a legitimate admin action is ever
@ -291,9 +293,14 @@ export class OrgGovernanceStack extends cdk.Stack {
retain(securityGuardrails);
// Root-user lockout for member accounts: root has no operational role
// (OrganizationAccountAccessRole + Identity Center cover everything). If a
// genuinely root-only task ever arises (account closure, certain tax
// settings), detach temporarily via a gated targetIds change.
// (OrganizationAccountAccessRole + Identity Center cover everything), and
// since 2026-07-14 member root credentials are DELETED via centralized
// root access management. This deny ALSO catches centralized root
// sessions (sts:AssumeRoot runs as the member root principal; member
// SCPs apply) — root recovery starts with a MANUAL, timeboxed
// detach-policy call (never a deploy: a concurrent deploy re-attaches),
// and for accounts under workloads/ the detach must cover BOTH the child
// OU and workloads (inherited attachment). Full runbook in the README.
const denyRootUser = new organizations.CfnPolicy(this, "DenyRootUser", {
name: "deny-root-user",
type: "SERVICE_CONTROL_POLICY",
@ -325,6 +332,10 @@ export class OrgGovernanceStack extends cdk.Stack {
retain(denyRootUser);
// ── Adopted (cdk-imported) external-dev OU + its 3 SCPs ─────────────────
// QUOTA: with deny-root-user attached (2026-07-14) this OU carries 5 SCPs
// = the AWS hard limit per target. Any new guardrail for external-dev
// must attach at the ACCOUNT (396287094661, own 5-slot budget) or
// consolidate into one of these policies — an OU-level attach will fail.
// Imported 2026-07-14 by Id (ou-nbuj-q34yz3ql, p-i59g24mz, p-8yty5mnd,
// p-ivmwtipw). Properties are byte-exact to the live resources at import
// time (content JSON in lib/scp/, descriptions/targets verified via