A customer I work with wanted to add a custom restriction to prevent all accounts from using AWS regions outside the US. They weren’t sure if they could add their own SCPs alongside Landing Zone’s managed SCPs, or if that would cause conflicts. After walking them through the process, we safely added a custom SCP that works perfectly with Landing Zone’s guardrails. In this post, I’ll walk through exactly how to add custom SCPs to your Landing Zone deployment.
The Problem
Your AWS Landing Zone deployment comes with default SCPs attached to Organizational Units (OUs). These control things like preventing account deletion and requiring CloudTrail. Now you need to add your own custom restrictions—maybe enforce a specific AWS region, prevent public S3 bucket creation, or deny EC2 instance types. You’re concerned about conflicting with Landing Zone’s SCPs and breaking guardrails.
The challenge is: Landing Zone manages its own SCPs via CloudFormation StackSets. If you modify them directly, you risk drift and losing your changes on the next Landing Zone update.
Why This Matters
AWS applies SCPs as a whitelist/blacklist. Multiple SCPs are AND’d together. If any SCP denies an action, the action is denied. This means you can safely add custom SCPs alongside Landing Zone’s managed ones—they’ll work together in defense-in-depth.
The Fix: Add Custom SCPs Alongside Landing Zone
Step 1: Create Your Custom SCP
First, define your custom SCP. Here’s an example that restricts usage to US regions only:
Create a file called deny-non-us-regions.json:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"NotAction": [
"iam:*",
"organizations:*",
"account:*"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": [
"us-east-1",
"us-west-2",
"us-east-2"
]
}
}
}
]
}
This SCP denies all API calls outside the specified regions, with exceptions for global services like IAM and Organizations.
Step 2: Create the Policy in Organizations
Create the SCP in AWS Organizations:
aws organizations create-policy \
--name DenyNonUSRegions \
--description "Restrict all API calls to US regions" \
--type SERVICE_CONTROL_POLICY \
--content file://deny-non-us-regions.json \
--region us-east-1
The command returns the policy ID, which you’ll need next.
Step 3: Attach the SCP to an OU
Attach the SCP to the OU where you want it to apply. For example, attach it to your “Development” OU:
# First, list your OUs to find the OU ID
aws organizations list-organizational-units-for-parent \
--parent-id r-xxxx \
--region us-east-1
# Then attach the SCP
aws organizations attach-policy \
--policy-id p-xxxxxxxxxx \
--target-id ou-xxxx-xxxxxxxx \
--region us-east-1
The policy now applies to all accounts in that OU and its child OUs.
Step 4: Test the SCP
In one of the accounts under that OU, try to perform an action in a denied region:
# This should be denied
aws ec2 describe-instances \
--region eu-west-1 \
--region us-east-1
You should see an error like:
An error occurred (UnauthorizedOperation) when calling the DescribeInstances
operation: You are not authorized to perform this operation.
Encoded authorization failure message...
Try the same command in an allowed region (us-east-1) and it should work.
For Landing Zone Accelerator (LZA) Users
If you’re using LZA instead of the original Landing Zone, you can also add custom SCPs via the configuration files. This keeps everything version-controlled and deployed alongside your infrastructure:
Step 1: Add SCP to customizations-config.yaml
In your LZA configuration repo, edit customizations-config.yaml:
serviceControlPolicies:
- name: DenyNonUSRegions
description: Restrict all API calls to US regions
type: SERVICE_CONTROL_POLICY
strategy: DENY
statements:
- effect: Deny
actions:
- '*'
excludedActions:
- 'iam:*'
- 'organizations:*'
- 'account:*'
resources:
- '*'
conditions:
- condition: StringNotEquals
key: aws:RequestedRegion
value:
- us-east-1
- us-west-2
- us-east-2
targets:
organizationalUnits:
- Development
- Staging
Step 2: Deploy the LZA Pipeline
Commit and push your changes:
git add customizations-config.yaml
git commit -m "Add custom SCP for US regions only"
git push origin main
The LZA pipeline automatically detects the change and deploys the SCP to the specified OUs.
Common Custom SCPs
Here are a few other useful SCPs you might want to add:
Deny Public S3 Buckets:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": [
"s3:PutAccountPublicAccessBlock"
],
"Resource": "*",
"Condition": {
"Bool": {
"s3:x-amz-acl": [
"public-read",
"public-read-write"
]
}
}
}
]
}
Require MFA for Sensitive Actions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": [
"iam:DeleteUser",
"iam:DeleteRole"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
Is This Safe?
Yes, adding custom SCPs alongside Landing Zone’s managed SCPs is completely safe. SCPs are evaluated as AND logic—if any policy denies an action, it’s denied. Your custom SCPs work in conjunction with Landing Zone’s guardrails, not against them.
Just remember:
- Always test SCPs in a non-production OU first
- SCPs apply immediately—there’s no deployment window
- Root user and the management account are exempt (by design)
Key Takeaway
You can safely add custom SCPs to your Landing Zone deployment by creating them in Organizations and attaching them to OUs. For LZA users, define SCPs in customizations-config.yaml for version control. Custom SCPs work alongside Landing Zone’s guardrails in defense-in-depth.
Have questions or ran into a different Landing Zone issue? Connect with me on LinkedIn or X.