mirror of
https://github.com/Sea-Haven-Industries/seahaven-org-baseline.git
synced 2026-09-30 05:43:17 +00:00
Document centralized root access: README runbook + stack comment updates (#52)
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:
parent
75888e045e
commit
ed26ff937d
2 changed files with 81 additions and 5 deletions
65
README.md
65
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue