Created
September 16, 2025 04:47
-
-
Save mrcrilly/d0beb1bf909268939d44c80e6b3fe8cc to your computer and use it in GitHub Desktop.
Common SCP policies for denying certain actions by default. Permits the use of certain Role ARNs to bypass these restrictions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
variable "org_root_id" { | |
description = "Root or OU ID" | |
type = string | |
} | |
variable "global_overriding_role_arns" { | |
description = "Roles that can bypass ALL deny rules (wildcards allowed)" | |
type = list(string) | |
default = [ | |
# "arn:aws:iam::*:role/*", | |
] | |
} | |
variable "ec2_overriding_role_arns" { | |
description = "Roles that can bypass EC2 related deny rules (wildcards allowed)" | |
type = list(string) | |
default = [ | |
# "arn:aws:iam::*:role/*", | |
] | |
} | |
variable "backup_overriding_role_arns" { | |
description = "Roles that can bypass Backup related deny rules (wildcards allowed)" | |
type = list(string) | |
default = [ | |
# "arn:aws:iam::*:role/*", | |
] | |
} | |
variable "controltower_overriding_role_arns" { | |
description = "Roles that can bypass Control Tower related deny rules (wildcards allowed)" | |
type = list(string) | |
default = [ | |
# "arn:aws:iam::*:role/*", | |
] | |
} | |
variable "dx_overriding_role_arns" { | |
description = "Roles that can bypass Direct Connect (DX) related deny rules (wildcards allowed)" | |
type = list(string) | |
default = [ | |
# "arn:aws:iam::*:role/*", | |
] | |
} | |
variable "ecr_overriding_role_arns" { | |
description = "Roles that can bypass ECR related deny rules (wildcards allowed)" | |
type = list(string) | |
default = [ | |
# "arn:aws:iam::*:role/*", | |
] | |
} | |
variable "iam_overriding_role_arns" { | |
description = "Roles that can bypass IAM related deny rules (wildcards allowed)" | |
type = list(string) | |
default = [ | |
# "arn:aws:iam::*:role/*", | |
] | |
} | |
variable "lambda_overriding_role_arns" { | |
description = "Roles that can bypass Lambda Function related deny rules (wildcards allowed)" | |
type = list(string) | |
default = [ | |
# "arn:aws:iam::*:role/*", | |
] | |
} | |
variable "ai_overriding_role_arns" { | |
description = "Roles that can bypass IAM related deny rules (wildcards allowed)" | |
type = list(string) | |
default = [ | |
# "arn:aws:iam::*:role/*", | |
] | |
} | |
variable "rds_overriding_role_arns" { | |
description = "Roles that can bypass RDS related deny rules (wildcards allowed)" | |
type = list(string) | |
default = [ | |
# "arn:aws:iam::*:role/*", | |
] | |
} | |
variable "route53_overriding_role_arns" { | |
description = "Roles that can bypass Route53 related deny rules (wildcards allowed)" | |
type = list(string) | |
default = [ | |
# "arn:aws:iam::*:role/*", | |
] | |
} | |
variable "s3_overriding_role_arns" { | |
description = "Roles that can bypass S3 related deny rules (wildcards allowed)" | |
type = list(string) | |
default = [ | |
# "arn:aws:iam::*:role/*", | |
] | |
} | |
variable "ses_overriding_role_arns" { | |
description = "Roles that can bypass SES related deny rules (wildcards allowed)" | |
type = list(string) | |
default = [ | |
# "arn:aws:iam::*:role/*", | |
] | |
} | |
data "aws_iam_policy_document" "scp_ec2_networking_guardrails" { | |
statement { | |
sid = "DenyVpcCoreNetworkChanges" | |
effect = "Deny" | |
actions = [ | |
"ec2:CreateVpc", | |
"ec2:DeleteVpc", | |
"ec2:CreateSubnet", | |
"ec2:DeleteSubnet", | |
"ec2:CreateInternetGateway", | |
"ec2:DeleteInternetGateway", | |
"ec2:AttachInternetGateway", | |
"ec2:DetachInternetGateway", | |
"ec2:CreateRouteTable", | |
"ec2:DeleteRouteTable", | |
"ec2:AssociateRouteTable", | |
"ec2:DisassociateRouteTable", | |
"ec2:CreateRoute", | |
"ec2:ReplaceRoute", | |
"ec2:DeleteRoute", | |
"ec2:CreateNatGateway", | |
"ec2:DeleteNatGateway", | |
"ec2:AllocateAddress", | |
"ec2:ReleaseAddress", | |
"ec2:CreateNetworkAcl", | |
"ec2:DeleteNetworkAcl", | |
"ec2:CreateSecurityGroup", | |
"ec2:DeleteSecurityGroup", | |
"ec2:AuthorizeSecurityGroupIngress", | |
"ec2:AuthorizeSecurityGroupEgress", | |
"ec2:RevokeSecurityGroupIngress", | |
"ec2:RevokeSecurityGroupEgress", | |
"ec2:CreateVpcEndpoint*", | |
"ec2:DeleteVpcEndpoints", | |
"ec2:ModifyVpcEndpoint*", | |
"ec2:CreateTransitGateway*", | |
"ec2:DeleteTransitGateway*", | |
"ec2:AssociateTransitGateway*", | |
"ec2:DisassociateTransitGateway*", | |
"ec2:CreateVpcPeeringConnection", | |
"ec2:DeleteVpcPeeringConnection", | |
"ec2:AcceptVpcPeeringConnection", | |
"ec2:RejectVpcPeeringConnection", | |
] | |
resources = ["*"] | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.ec2_overriding_role_arns) | |
} | |
} | |
} | |
resource "aws_organizations_policy" "scp_ec2_networking_guardrails" { | |
name = "SCP-EC2-Networking-Guardrails" | |
description = "Deny creation/modification/deletion of core VPC networking constructs." | |
content = data.aws_iam_policy_document.scp_ec2_networking_guardrails.json | |
} | |
data "aws_iam_policy_document" "scp_backup_no_delete" { | |
statement { | |
sid = "DenyBackupDeletion" | |
effect = "Deny" | |
actions = [ | |
"backup:DeleteBackupPlan", | |
"backup:DeleteBackupVault", | |
"backup:DeleteBackupVaultAccessPolicy", | |
"backup:DeleteRecoveryPoint", | |
"backup:BatchDeleteRecoveryPoint", | |
] | |
resources = ["*"] | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.backup_overriding_role_arns) | |
} | |
} | |
} | |
resource "aws_organizations_policy" "scp_backup_no_delete" { | |
name = "SCP-Backup-NoDeletion" | |
description = "Deny deletion of Backup plans, vaults, and recovery points." | |
content = data.aws_iam_policy_document.scp_backup_no_delete.json | |
} | |
data "aws_iam_policy_document" "scp_controltower_no_lz_delete" { | |
statement { | |
sid = "DenyControlTowerLZDestruction" | |
effect = "Deny" | |
actions = [ | |
"controltower:DeleteLandingZone", | |
"controltower:DeregisterManagedOrganizationalUnit", | |
"controltower:DisableControl", | |
] | |
resources = ["*"] | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.controltower_overriding_role_arns) | |
} | |
} | |
} | |
resource "aws_organizations_policy" "scp_controltower_no_lz_delete" { | |
name = "SCP-ControlTower-ProtectLandingZone" | |
description = "Deny destructive Control Tower actions against the Landing Zone." | |
content = data.aws_iam_policy_document.scp_controltower_no_lz_delete.json | |
} | |
data "aws_iam_policy_document" "scp_dx_no_delete" { | |
statement { | |
sid = "DenyDXDelete" | |
effect = "Deny" | |
actions = [ | |
"directconnect:DeleteConnection", | |
"directconnect:DeleteVirtualInterface", | |
"directconnect:DeleteLag", | |
"directconnect:DeleteInterconnect", | |
] | |
resources = ["*"] | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.dx_overriding_role_arns) | |
} | |
} | |
} | |
resource "aws_organizations_policy" "scp_dx_no_delete" { | |
name = "SCP-DirectConnect-NoDelete" | |
description = "Deny deleting Direct Connect connections and virtual interfaces." | |
content = data.aws_iam_policy_document.scp_dx_no_delete.json | |
} | |
data "aws_iam_policy_document" "scp_ecr_protect_images" { | |
statement { | |
sid = "DenyECRDeletes" | |
effect = "Deny" | |
actions = [ | |
"ecr:BatchDeleteImage", | |
"ecr:DeleteRepository", | |
"ecr:DeleteRepositoryPolicy", | |
] | |
resources = ["*"] | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.ecr_overriding_role_arns) | |
} | |
} | |
} | |
resource "aws_organizations_policy" "scp_ecr_protect_images" { | |
name = "SCP-ECR-ProtectImages" | |
description = "Deny deleting images and repos in ECR." | |
content = data.aws_iam_policy_document.scp_ecr_protect_images.json | |
} | |
data "aws_iam_policy_document" "scp_iam_no_user_group_policy_create" { | |
statement { | |
sid = "DenyUserGroupPolicyCreate" | |
effect = "Deny" | |
actions = [ | |
"iam:CreateUser", | |
"iam:CreateLoginProfile", | |
"iam:AddUserToGroup", | |
"iam:CreateGroup", | |
"iam:CreatePolicy", | |
"iam:AttachGroupPolicy", | |
"iam:AttachUserPolicy", | |
"iam:PutUserPolicy", | |
"iam:PutGroupPolicy", | |
] | |
resources = ["*"] | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.iam_overriding_role_arns) | |
} | |
} | |
} | |
resource "aws_organizations_policy" "scp_iam_no_user_group_policy_create" { | |
name = "SCP-IAM-NoUserGroupPolicyCreate" | |
description = "Deny creation of IAM users, groups, and customer managed policies." | |
content = data.aws_iam_policy_document.scp_iam_no_user_group_policy_create.json | |
} | |
data "aws_iam_policy_document" "scp_lambda_create_restricted" { | |
statement { | |
sid = "DenyLambdaCreateFunction" | |
effect = "Deny" | |
actions = [ | |
"lambda:DeleteFunction", | |
"lambda:CreateFunction", | |
"lambda:CreateFunctionUrlConfig", | |
] | |
resources = ["*"] | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.lambda_overriding_role_arns) | |
} | |
} | |
} | |
resource "aws_organizations_policy" "scp_lambda_create_restricted" { | |
name = "SCP-Lambda-Create-Restricted" | |
description = "Deny creation/deletion of Lambda Functions except for allowed roles." | |
content = data.aws_iam_policy_document.scp_lambda_create_restricted.json | |
} | |
data "aws_iam_policy_document" "scp_disable_ai" { | |
statement { | |
sid = "DenyQBusinessAndQDeveloper" | |
effect = "Deny" | |
actions = [ | |
"qbusiness:*", | |
"qdeveloper:*", | |
] | |
resources = ["*"] | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.ai_overriding_role_arns) | |
} | |
} | |
statement { | |
sid = "DenyBedrock" | |
effect = "Deny" | |
actions = [ | |
"bedrock:*", | |
"bedrock-agent:*", | |
] | |
resources = ["*"] | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.ai_overriding_role_arns) | |
} | |
} | |
} | |
resource "aws_organizations_policy" "scp_disable_ai" { | |
name = "SCP-Disable-AI-Services" | |
description = "Deny use of AWS AI assistant/features (Q services, Bedrock)." | |
content = data.aws_iam_policy_document.scp_disable_ai.json | |
} | |
data "aws_iam_policy_document" "scp_rds_no_delete" { | |
statement { | |
sid = "DenyRDSDelete" | |
effect = "Deny" | |
actions = [ | |
"rds:DeleteDBInstance", | |
"rds:DeleteDBCluster", | |
"rds:RemoveRoleFromDBInstance", | |
] | |
resources = ["*"] | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.rds_overriding_role_arns) | |
} | |
} | |
} | |
resource "aws_organizations_policy" "scp_rds_no_delete" { | |
name = "SCP-RDS-NoDelete" | |
description = "Deny deletion of RDS instances/clusters except for allowed roles." | |
content = data.aws_iam_policy_document.scp_rds_no_delete.json | |
} | |
data "aws_iam_policy_document" "scp_route53_zone_create_delete_restricted" { | |
statement { | |
sid = "DenyZoneCreateDelete" | |
effect = "Deny" | |
actions = [ | |
"route53:CreateHostedZone", | |
"route53:DeleteHostedZone", | |
] | |
resources = ["*"] | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.route53_overriding_role_arns) | |
} | |
} | |
} | |
resource "aws_organizations_policy" "scp_route53_zone_create_delete_restricted" { | |
name = "SCP-Route53-Zone-Guardrails" | |
description = "Deny create/delete hosted zones except for allowed roles." | |
content = data.aws_iam_policy_document.scp_route53_zone_create_delete_restricted.json | |
} | |
data "aws_iam_policy_document" "scp_s3_bucket_protection" { | |
statement { | |
sid = "DenyDeleteBucket" | |
effect = "Deny" | |
actions = ["s3:DeleteBucket"] | |
resources = ["*"] | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.s3_overriding_role_arns) | |
} | |
} | |
statement { | |
sid = "DenyPublicACLsOnBuckets" | |
effect = "Deny" | |
actions = ["s3:PutBucketAcl"] | |
resources = ["*"] | |
condition { | |
test = "StringEquals" | |
variable = "s3:x-amz-acl" | |
values = ["public-read", "public-read-write", "authenticated-read"] | |
} | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.s3_overriding_role_arns) | |
} | |
} | |
statement { | |
sid = "RestrictBucketPolicyChanges" | |
effect = "Deny" | |
actions = ["s3:PutBucketPolicy"] | |
resources = ["*"] | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.s3_overriding_role_arns) | |
} | |
} | |
statement { | |
sid = "RestrictCreateBucket" | |
effect = "Deny" | |
actions = ["s3:CreateBucket"] | |
resources = ["*"] | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.s3_overriding_role_arns) | |
} | |
} | |
} | |
resource "aws_organizations_policy" "scp_s3_bucket_protection" { | |
name = "SCP-S3-Bucket-Protection" | |
description = "Deny DeleteBucket; deny public ACLs; restrict policy and create to allow-list." | |
content = data.aws_iam_policy_document.scp_s3_bucket_protection.json | |
} | |
data "aws_iam_policy_document" "scp_ses_no_domain_setup" { | |
statement { | |
sid = "DenySESIdentityAndDomainSetup" | |
effect = "Deny" | |
actions = [ | |
# SESv2 | |
"ses:CreateEmailIdentity", | |
"ses:PutEmailIdentityDkimSigningAttributes", | |
"ses:PutEmailIdentityMailFromAttributes", | |
"ses:PutEmailIdentityPolicy", | |
"ses:CreateConfigurationSet", | |
"ses:PutAccountDedicatedIpWarmupAttributes", | |
# Classic | |
"ses:VerifyDomainIdentity", | |
"ses:VerifyDomainDkim", | |
] | |
resources = ["*"] | |
condition { | |
test = "StringNotLikeIfExists" | |
variable = "aws:PrincipalArn" | |
values = concat(var.global_overriding_role_arns, var.ses_overriding_role_arns) | |
} | |
} | |
} | |
resource "aws_organizations_policy" "scp_ses_no_domain_setup" { | |
name = "SCP-SES-NoDomainSetup" | |
description = "Deny creating/verifying domains and enabling DKIM/MailFrom." | |
content = data.aws_iam_policy_document.scp_ses_no_domain_setup.json | |
} | |
resource "aws_organizations_policy_attachment" "attach_ec2" { | |
policy_id = aws_organizations_policy.scp_ec2_networking_guardrails.id | |
target_id = var.org_root_id | |
} | |
resource "aws_organizations_policy_attachment" "attach_backup" { | |
policy_id = aws_organizations_policy.scp_backup_no_delete.id | |
target_id = var.org_root_id | |
} | |
resource "aws_organizations_policy_attachment" "attach_ct" { | |
policy_id = aws_organizations_policy.scp_controltower_no_lz_delete.id | |
target_id = var.org_root_id | |
} | |
resource "aws_organizations_policy_attachment" "attach_dx" { | |
policy_id = aws_organizations_policy.scp_dx_no_delete.id | |
target_id = var.org_root_id | |
} | |
resource "aws_organizations_policy_attachment" "attach_ecr" { | |
policy_id = aws_organizations_policy.scp_ecr_protect_images.id | |
target_id = var.org_root_id | |
} | |
resource "aws_organizations_policy_attachment" "attach_iam" { | |
policy_id = aws_organizations_policy.scp_iam_no_user_group_policy_create.id | |
target_id = var.org_root_id | |
} | |
resource "aws_organizations_policy_attachment" "attach_lambda" { | |
policy_id = aws_organizations_policy.scp_lambda_create_restricted.id | |
target_id = var.org_root_id | |
} | |
resource "aws_organizations_policy_attachment" "attach_ai" { | |
policy_id = aws_organizations_policy.scp_disable_ai.id | |
target_id = var.org_root_id | |
} | |
resource "aws_organizations_policy_attachment" "attach_rds" { | |
policy_id = aws_organizations_policy.scp_rds_no_delete.id | |
target_id = var.org_root_id | |
} | |
resource "aws_organizations_policy_attachment" "attach_route53" { | |
policy_id = aws_organizations_policy.scp_route53_zone_create_delete_restricted.id | |
target_id = var.org_root_id | |
} | |
resource "aws_organizations_policy_attachment" "attach_s3" { | |
policy_id = aws_organizations_policy.scp_s3_bucket_protection.id | |
target_id = var.org_root_id | |
} | |
resource "aws_organizations_policy_attachment" "attach_ses" { | |
policy_id = aws_organizations_policy.scp_ses_no_domain_setup.id | |
target_id = var.org_root_id | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment