src/config/service-control-policies/ contains the Service Control Policies that govern what actions are allowed across your AWS Organization. The landing zone ships five ready-made SCPs, all attached by default. You can use them as-is, modify their content, or add your own.
SCPs are attached in organization-structure.ts at the root, OU, or account level. AWS Organizations enforces a maximum of 10 directly attached SCPs per root, OU, or account.
Shipped SCPs
The policies live in three files: security-guardrails.ts, region-guardrails.ts, and suspended-account-guardrails.ts. The shared exemption lists live in administrator-principals.ts (see Exempt principals).
| SCP | Attached to | Intent |
|---|---|---|
criticalSecurityGuardrailsSCP | Root | Baseline account hygiene, and protection of the roles and automation the other guardrails exempt |
protectTaggedCloudFormationStacksSCP | Root | Stops changes to CloudFormation stacks and StackSets the landing zone manages |
denyAllOutsidePrimaryAndSecondaryRegionsSCP | Root | Keeps regional API usage inside your configured Regions |
protectSecurityHubConfigurationSCP | Security account | Protects Security Hub configuration in the delegated administrator account |
lockdownSuspendedAccountsSCP | Suspended OU (inherited by the close-account OU) | Quarantines accounts you suspend or close |
Critical security guardrails
Attached to the root, so it applies to every account. It denies:
- Leaving the organization.
- Creating IAM users and access keys.
- Disabling EBS encryption by default.
- Changing or deleting the account password policy, except by landing-zone administration roles and your external administrators.
- Creating, changing, deleting, or passing the roles whose names the guardrails exempt (see Exempt principals), except by landing-zone deployment roles.
- Putting the
cdk-landing-zone:automationtag on roles, users, Lambda functions, EventBridge rules, or sessions, and changing tagged landing-zone functions, rules, and roles, except by landing-zone deployment roles.
Protect tagged CloudFormation stacks
Attached to the root. It denies updating, deleting, rolling back, tagging, and interrupting stacks and StackSets that carry the cdk-landing-zone:managed tag, which the landing zone puts on everything it owns. Landing-zone administrators (SSO AdministratorAccess, landing-zone roles including GitHubActionsServiceRole, and your external administrators) are exempt. Once the landing zone account exists, it also limits creating and updating StackSets, and using the landing zone's CDK deployment roles, to those administrators. Stacks you tag with cdk-landing-zone:managed yourself get the same protection.
Deny outside primary and secondary Regions
Attached to the root. It denies regional AWS API calls outside primaryRegion and secondaryRegions. Global services stay allowed (IAM, Route 53, CloudFront, AWS Organizations, billing, KMS, S3, STS, support, and others with no regional API boundary), AWS Cost Optimization Hub and AWS User Notifications (with its email contacts) are also allowed. Landing-zone administration and automation roles are exempt. When you add a Region to secondaryRegions in landing-zone-settings.ts and redeploy, the allowed list updates automatically. A removed Region stays allowed until no landing-zone StackSet uses it, so a failed removal can roll back; the starter marks its Region list with ActiveRegions.of([...]) for this. A list without that wrapper follows your configuration exactly.
Protect Security Hub configuration
Attached to the security account, the Security Hub delegated administrator. It denies disabling Security Hub or its standards, changing standards controls, finding aggregation, and organization configuration, and deleting or disassociating members and invitations. SSO administrators, the Security Hub service role, and landing-zone roles are exempt.
Lockdown suspended accounts
Attached to SuspendedOU and inherited by the nested CloseAccountOU. It denies every action in the account except for administrators (SSO AdministratorAccess, OrganizationAccountAccessRole, and your external administrators), the StackSet execution roles and GitHubActionsServiceRole, and landing-zone automation. Existing resources keep running, but the account's own users and workload roles can no longer call AWS. See organization-structure.ts for what suspending and closing accounts does.
Exempt principals
A guardrail that denies an action still has to let the landing zone, and you, operate. src/config/service-control-policies/administrator-principals.ts is the single place those exemptions are defined, and the guardrails share its allowlists:
| Function | Used by | Covers |
|---|---|---|
securityServiceAdministratorPrincipalArns() | Region guardrail, Security Hub protection | SSO administrators, the Security Hub service role, the landing zone's own roles, and your external admins |
landingZoneAdministratorPrincipalArns() | CloudFormation stack protection | SSO administrators, the landing zone's own roles, your external admins, and the landing zone account's CDK deployment roles |
suspendedAccountExemptPrincipalArns() | Suspended-account lockdown | The principals that must keep access while an account is suspended |
guardrailExemptRoleArns() | Critical security guardrails, suspended-account lockdown | The role names the guardrails exempt, which no one else may create or modify |
guardrailRoleAdministratorPrincipalArns() | Critical security guardrails | The principals allowed to manage those reserved roles and apply the automation tag |
The allowlists cover landing-zone administration and deployment roles, including GitHubActionsServiceRole, plus your configured external administrators. Set externalAdmins in landing-zone-settings.ts to include them in the shared exemptions. Their role names become reserved: do not create other roles with the same names in member accounts.
Edit this file when you have a break-glass or automation role that must survive the guardrails. The guardrails share these lists: adding a principal once applies it to every SCP that uses the list.
The landing zone applies two tags you will see in these policies: cdk-landing-zone:managed marks the stacks, StackSets, and resources the landing zone owns, and cdk-landing-zone:automation marks the landing zone's own automation and the roles it runs as, which keep running in suspended accounts. Do not apply the automation tag to your own resources.
Authoring a custom SCP
Create a new file (or add to an existing one) in src/config/service-control-policies/. This example denies bucket deletion using the supported s3:DeleteBucket action. It does not prevent object deletion:
export const denyS3BucketDeletionSCP: ServiceControlPolicy = {
content: {
Version: '2012-10-17',
Statement: [
{
Sid: 'DenyS3BucketDeletion',
Effect: 'Deny',
Action: 's3:DeleteBucket',
Resource: 'arn:aws:s3:::*',
},
],
},
description: 'Prevents deletion of S3 buckets',
policyName: 'DenyS3BucketDeletion',
policyType: PolicyType.SERVICE_CONTROL_POLICY,
};
Export it from src/config/service-control-policies/index.ts:
export * from './your-new-guardrails';
Then append it to the target OU’s serviceControlPolicies array in organization-structure.ts. Preserve existing policies and leave the OU’s accounts and other fields unchanged:
// Inside the target OU, append to its existing array:
serviceControlPolicies: [
// Keep existing SCP entries here.
denyS3BucketDeletionSCP,
],
Things to know
- The AWS limit is 10 directly attached SCPs per root, OU, or account. SCPs inherited from a parent do not count against an attachment point's limit; only those attached directly do (quotas).
FullAWSAccesstakes one of the slots. AWS attaches it by default when SCPs are enabled, and an attachment point must always keep at least one SCP, so keep an allow policy at each level. With it attached, a point has room for at most 9 additional SCPs.- Check existing attachments before deploying. Synthesis validates the SCPs configured in
organizationStructureand rejects policy documents over 10,240 characters. It does not account forFullAWSAccessor other policies attached outside that configuration, so a passing synth does not guarantee the deployed attachment count stays within the quota. - The shipped SCPs use explicit denies. SCPs can contain allow or deny statements, but they never grant permissions by themselves.
- Test in a non-production OU first: attach a new SCP to a development account before applying it at the root or to production accounts.
Slots used in the starter
| Attachment point | Directly attached | Slots used of 10 |
|---|---|---|
| Root | FullAWSAccess plus the three root SCPs | 4 |
| Security account | FullAWSAccess plus protectSecurityHubConfigurationSCP | 2 |
| Suspended OU | FullAWSAccess plus lockdownSuspendedAccountsSCP | 2 |
| Other OUs and accounts | FullAWSAccess | 1 |
The Region, tag, and lockdown guardrails therefore leave room for several more SCPs at every level before you reach the limit.