From 10e66c92c08fee93271242743d1a2c635458567d Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Tue, 14 Jul 2026 14:10:10 -0400 Subject: [PATCH] Adopt external-dev OU and its 3 SCPs via resource import (#45) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cdk import by Id (non-mutating), content byte-exact from describe-policy, targetIds = exact live attachments. Post-import drift detection: IN_SYNC, 0 drifted. All four Retain — the full org guardrail set is now drift-checked IaC. --- lib/org-governance-stack.ts | 53 ++++++++++++++++++++++ lib/scp/external-dev-iam-guardrails.json | 50 ++++++++++++++++++++ lib/scp/external-dev-protect-security.json | 26 +++++++++++ lib/scp/external-dev-region-lock.json | 18 ++++++++ 4 files changed, 147 insertions(+) create mode 100644 lib/scp/external-dev-iam-guardrails.json create mode 100644 lib/scp/external-dev-protect-security.json create mode 100644 lib/scp/external-dev-region-lock.json diff --git a/lib/org-governance-stack.ts b/lib/org-governance-stack.ts index 4990e20..040d8b7 100644 --- a/lib/org-governance-stack.ts +++ b/lib/org-governance-stack.ts @@ -1,7 +1,15 @@ +import * as fs from "fs"; +import * as path from "path"; import * as cdk from "aws-cdk-lib"; import * as organizations from "aws-cdk-lib/aws-organizations"; import { Construct } from "constructs"; +/** Byte-exact live SCP content (lib/scp/*.json) — see import block below. */ +const scpContent = (name: string): Record => + JSON.parse( + fs.readFileSync(path.join(__dirname, "scp", `${name}.json`), "utf8") + ); + /** * AWS Organizations structure for org o-9kufuzz6b4: OU skeleton + generalized * service-control policies (multi-account segregation plan Phase 2, @@ -226,6 +234,51 @@ export class OrgGovernanceStack extends cdk.Stack { }); retain(denyRootUser); + // ── Adopted (cdk-imported) external-dev OU + its 3 SCPs ───────────────── + // 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 + // describe-policy). RULES: content stays a JSON object (string form kills + // drift detection); targetIds is the exact live attachment set — any edit + // here detaches/attaches a LIVE guardrail on the isolated contractor + // account and requires the mandatory review gates; never rename these + // logical ids (rename = delete+create; Retain would orphan, not detach, + // but the stack would lose the resource). + const externalDevOu = new organizations.CfnOrganizationalUnit(this, "ExternalDevOu", { + name: "external-dev", + parentId: ROOT_ID, + }); + retain(externalDevOu); + + const externalDevRegionLock = new organizations.CfnPolicy(this, "ExternalDevRegionLock", { + name: "external-dev-region-lock", + type: "SERVICE_CONTROL_POLICY", + description: "external-dev OU guardrail: external-dev-region-lock", + targetIds: ["ou-nbuj-q34yz3ql"], + content: scpContent("external-dev-region-lock"), + }); + retain(externalDevRegionLock); + + const externalDevIamGuardrails = new organizations.CfnPolicy(this, "ExternalDevIamGuardrails", { + name: "external-dev-iam-guardrails", + type: "SERVICE_CONTROL_POLICY", + description: "external-dev OU guardrail: external-dev-iam-guardrails", + targetIds: ["ou-nbuj-q34yz3ql"], + content: scpContent("external-dev-iam-guardrails"), + }); + retain(externalDevIamGuardrails); + + const externalDevProtectSecurity = new organizations.CfnPolicy(this, "ExternalDevProtectSecurity", { + name: "external-dev-protect-security", + type: "SERVICE_CONTROL_POLICY", + description: "external-dev OU guardrail: external-dev-protect-security", + targetIds: ["ou-nbuj-q34yz3ql"], + content: scpContent("external-dev-protect-security"), + }); + retain(externalDevProtectSecurity); + + new cdk.CfnOutput(this, "ExternalDevOuId", { value: externalDevOu.attrId }); + new cdk.CfnOutput(this, "WorkloadsOuId", { value: workloadsOu.attrId }); new cdk.CfnOutput(this, "ProdOuId", { value: prodOu.attrId }); new cdk.CfnOutput(this, "NonprodOuId", { value: nonprodOu.attrId }); diff --git a/lib/scp/external-dev-iam-guardrails.json b/lib/scp/external-dev-iam-guardrails.json new file mode 100644 index 0000000..f85148f --- /dev/null +++ b/lib/scp/external-dev-iam-guardrails.json @@ -0,0 +1,50 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "RequireBoundaryOnRoleAndUserCreate", + "Effect": "Deny", + "Action": ["iam:CreateRole", "iam:CreateUser"], + "Resource": "*", + "Condition": { + "StringNotEquals": { "iam:PermissionsBoundary": "arn:aws:iam::396287094661:policy/external-dev-execution-boundary" }, + "ArnNotLike": { "aws:PrincipalArn": ["arn:aws:iam::396287094661:role/OrganizationAccountAccessRole", "arn:aws:iam::396287094661:role/cdk-hnb659fds-*", "arn:aws:iam::396287094661:role/githubdeploy-seahaven-external-dev-baseline"] } + } + }, + { + "Sid": "ProtectBoundaryPolicyFromEdits", + "Effect": "Deny", + "Action": ["iam:CreatePolicyVersion", "iam:SetDefaultPolicyVersion", "iam:DeletePolicy", "iam:DeletePolicyVersion"], + "Resource": "arn:aws:iam::396287094661:policy/external-dev-execution-boundary" + }, + { + "Sid": "DenyAlteringPermissionsBoundaries", + "Effect": "Deny", + "Action": ["iam:DeleteRolePermissionsBoundary", "iam:DeleteUserPermissionsBoundary", "iam:PutRolePermissionsBoundary", "iam:PutUserPermissionsBoundary"], + "Resource": "*", + "Condition": { "ArnNotLike": { "aws:PrincipalArn": ["arn:aws:iam::396287094661:role/OrganizationAccountAccessRole", "arn:aws:iam::396287094661:role/cdk-hnb659fds-*"] } } + }, + { + "Sid": "DenyIamUserAndAccessKeyCreation", + "Effect": "Deny", + "Action": ["iam:CreateUser", "iam:CreateAccessKey", "iam:CreateLoginProfile"], + "Resource": "*", + "Condition": { "ArnNotLike": { "aws:PrincipalArn": ["arn:aws:iam::396287094661:role/OrganizationAccountAccessRole"] } } + }, + { + "Sid": "ProtectPrivilegedRoles", + "Effect": "Deny", + "Action": ["iam:UpdateAssumeRolePolicy", "iam:AttachRolePolicy", "iam:DetachRolePolicy", "iam:PutRolePolicy", "iam:DeleteRolePolicy", "iam:DeleteRole", "iam:UpdateRole", "iam:TagRole", "iam:UntagRole"], + "Resource": [ + "arn:aws:iam::396287094661:role/OrganizationAccountAccessRole", + "arn:aws:iam::396287094661:role/cdk-hnb659fds-*", + "arn:aws:iam::396287094661:role/githubdeploy-*", + "arn:aws:iam::396287094661:role/seahaven-extdev-config-recorder-role", + "arn:aws:iam::396287094661:role/seahaven-extdev-config-custom-resource-role", + "arn:aws:iam::396287094661:role/aws-service-role/*" + ], + "Condition": { "ArnNotLike": { "aws:PrincipalArn": ["arn:aws:iam::396287094661:role/OrganizationAccountAccessRole", "arn:aws:iam::396287094661:role/cdk-hnb659fds-*"] } } + } + ] +} + diff --git a/lib/scp/external-dev-protect-security.json b/lib/scp/external-dev-protect-security.json new file mode 100644 index 0000000..f4809a6 --- /dev/null +++ b/lib/scp/external-dev-protect-security.json @@ -0,0 +1,26 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "DenyDisablingSecurityServices", + "Effect": "Deny", + "Action": [ + "cloudtrail:StopLogging", "cloudtrail:DeleteTrail", "cloudtrail:UpdateTrail", + "guardduty:DeleteDetector", "guardduty:UpdateDetector", "guardduty:DisassociateFromMasterAccount", + "config:StopConfigurationRecorder", "config:DeleteConfigurationRecorder", "config:DeleteDeliveryChannel", + "securityhub:DisableSecurityHub", "securityhub:BatchDisableStandards", + "accessanalyzer:DeleteAnalyzer", "inspector2:Disable" + ], + "Resource": "*", + "Condition": { + "ArnNotLike": { "aws:PrincipalArn": [ + "arn:aws:iam::396287094661:role/OrganizationAccountAccessRole", + "arn:aws:iam::396287094661:role/cdk-hnb659fds-*", + "arn:aws:iam::396287094661:role/seahaven-extdev-config-custom-resource-role" + ]} + } + }, + { "Sid": "DenyLeavingOrganization", "Effect": "Deny", "Action": ["organizations:LeaveOrganization"], "Resource": "*" } + ] +} + diff --git a/lib/scp/external-dev-region-lock.json b/lib/scp/external-dev-region-lock.json new file mode 100644 index 0000000..81ad877 --- /dev/null +++ b/lib/scp/external-dev-region-lock.json @@ -0,0 +1,18 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "DenyRegionsOutsideUsEast1", + "Effect": "Deny", + "NotAction": [ + "iam:*", "organizations:*", "account:*", "sts:*", "route53:*", "route53domains:*", + "cloudfront:*", "waf:*", "shield:*", "globalaccelerator:*", "budgets:*", "ce:*", + "cur:*", "health:*", "support:*", "supportplans:*", "trustedadvisor:*", "artifact:*", + "aws-portal:*" + ], + "Resource": "*", + "Condition": { "StringNotEquals": { "aws:RequestedRegion": "us-east-1" } } + } + ] +} +