AWS Resource Control Policies: Practical RCP Rule Sets for 11 AWS Services
AWS Resource Control Policies: Practical RCP Rule Sets for 11 AWS Services
AWS Resource Control Policies provide a centrally managed resource-side security boundary. This guide presents practical RCP rule sets for eleven widely used AWS services and explains where RCPs complement—rather than replace—SCPs, IAM policies, key policies, and resource policies.
What is an AWS Resource Control Policy?
A Resource Control Policy, or RCP, is an AWS Organizations policy that defines the maximum permissions available on resources in member accounts. An RCP can prevent an external or unintended principal from accessing an organizational resource even when a resource policy mistakenly permits that access.
RCPs do not grant permissions. Actual access must still be granted through an identity-based or resource-based policy. Effective access is the intersection of the applicable RCPs, SCPs, IAM policies, and resource policies.
Recommended controls by service
| Service | RCP prefix | Recommended guardrails |
|---|---|---|
| Amazon S3 | s3 |
Block external principals; require TLS; require SSE-KMS; control service-originated access. |
| AWS KMS | kms |
Block external principals from customer-managed keys; protect against confused-deputy access. |
| CloudWatch Logs | logs |
Protect log groups, destinations, and service-delivery paths. |
| DynamoDB | dynamodb |
Prevent unintended cross-organization table access. |
| EC2 Auto Scaling | autoscaling |
Prevent external access to supported Auto Scaling resources. |
| Amazon Inspector Scan | inspector2 |
Prevent external access to supported Inspector Scan resources. |
| Kinesis Video Streams | kinesisvideo |
Protect video streams from external access. |
| Amazon SQS | sqs |
Protect queues and restrict service-originated message delivery. |
| AWS CodeBuild | codebuild |
Prevent external access to supported build resources. |
| AWS CodePipeline | codepipeline |
Prevent external access to supported pipeline resources. |
| AWS Secrets Manager | secretsmanager |
Prevent external secret access and unintended resource-policy sharing. |
firehose.Rule set 1: Establish an organizational resource perimeter
This consolidated policy denies access from IAM principals outside the organization while allowing AWS service principals. Replace o-xxxxxxxxxx with the actual AWS Organization ID.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyExternalPrincipalAccessToProtectedResources",
"Effect": "Deny",
"Principal": "*",
"Action": [
"s3:*",
"kms:*",
"logs:*",
"dynamodb:*",
"autoscaling:*",
"inspector2:*",
"kinesisvideo:*",
"sqs:*",
"codebuild:*",
"codepipeline:*",
"secretsmanager:*"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:PrincipalIsAWSService": "false"
},
"StringNotEqualsIfExists": {
"aws:PrincipalOrgID": "o-xxxxxxxxxx"
}
}
}
]
}
Exempt approved external roles
If a partner or controlled break-glass role requires access, add a narrow ArnNotLike exception. Avoid broad account-wide exceptions.
"Condition": {
"BoolIfExists": {
"aws:PrincipalIsAWSService": "false"
},
"StringNotEqualsIfExists": {
"aws:PrincipalOrgID": "o-xxxxxxxxxx"
},
"ArnNotLike": {
"aws:PrincipalArn": [
"arn:aws:iam::111122223333:role/ApprovedPartnerRole",
"arn:aws:iam::*:role/OrganizationBreakGlassRole"
]
}
}
Rule set 2: Reduce cross-service confused-deputy risk
AWS service principals frequently need legitimate resource access—for example, CloudTrail writing to S3 or EventBridge sending to SQS. Where the integration supplies the relevant source context, require the calling service to act for a resource associated with your organization.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyServiceAccessFromOutsideOrganization",
"Effect": "Deny",
"Principal": "*",
"Action": [
"s3:*", "kms:*", "logs:*", "dynamodb:*",
"autoscaling:*", "inspector2:*", "kinesisvideo:*",
"sqs:*", "codebuild:*", "codepipeline:*",
"secretsmanager:*"
],
"Resource": "*",
"Condition": {
"Bool": {
"aws:PrincipalIsAWSService": "true"
},
"Null": {
"aws:SourceAccount": "false"
},
"StringNotEqualsIfExists": {
"aws:SourceOrgID": "o-xxxxxxxxxx"
}
}
}
]
}
Test this rule carefully because AWS integrations do not all populate source context keys identically. Where appropriate, use aws:SourceAccount, aws:SourceArn, aws:SourceOrgID, or aws:SourceOrgPaths based on the integration.
Rule set 3: Enforce S3 transport security
Require HTTPS
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyUnencryptedS3Transport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"Bool": { "aws:SecureTransport": "false" }
}
}]
}
Require TLS 1.2 or later
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyS3RequestsBelowTLS12",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"NumericLessThan": { "s3:TlsVersion": "1.2" }
}
}]
}
TLS 1.2 is a broadly compatible enterprise baseline. AWS Control Tower also provides a stricter preventive control that requires TLS 1.3.
Rule set 4: Require SSE-KMS for S3 object uploads
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyS3UploadsWithoutSSEKMSKey",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::*/*",
"Condition": {
"Null": {
"s3:x-amz-server-side-encryption-aws-kms-key-id": "true"
}
}
},
{
"Sid": "DenyIncorrectS3EncryptionAlgorithm",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::*/*",
"Condition": {
"StringNotEqualsIfExists": {
"s3:x-amz-server-side-encryption": "aws:kms"
}
}
}
]
}
Rule set 5: Protect customer-managed KMS keys
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyExternalAccessToCustomerManagedKeys",
"Effect": "Deny",
"Principal": "*",
"Action": "kms:*",
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:PrincipalIsAWSService": "false"
},
"StringNotEqualsIfExists": {
"aws:PrincipalOrgID": "o-xxxxxxxxxx"
},
"ArnNotLike": {
"aws:PrincipalArn": "arn:aws:iam::111122223333:role/ApprovedExternalKMSRole"
}
}
}]
}
RCPs do not apply to AWS-managed KMS keys or to kms:RetireGrant. Key policies and IAM policies remain necessary to grant access.
Service-specific organizational perimeter policies
The same tested perimeter pattern can be applied separately when different OUs, exemptions, or rollout schedules are required. Change the Sid, action prefix, and—in the SQS example—the resource ARN as shown below.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyExternalAccessToSERVICE",
"Effect": "Deny",
"Principal": "*",
"Action": "SERVICE_PREFIX:*",
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:PrincipalIsAWSService": "false"
},
"StringNotEqualsIfExists": {
"aws:PrincipalOrgID": "o-xxxxxxxxxx"
}
}
}]
}
| Service-specific policy | Sid |
Action | Resource |
|---|---|---|---|
| CloudWatch Logs | DenyExternalAccessToCloudWatchLogs |
logs:* |
* |
| DynamoDB | DenyExternalAccessToDynamoDB |
dynamodb:* |
* |
| EC2 Auto Scaling | DenyExternalAccessToAutoScaling |
autoscaling:* |
* |
| Inspector Scan | DenyExternalAccessToInspectorScan |
inspector2:* |
* |
| Kinesis Video Streams | DenyExternalAccessToKinesisVideo |
kinesisvideo:* |
* |
| SQS | DenyExternalAccessToSQSQueues |
sqs:* |
arn:aws:sqs:*:*:* |
| CodeBuild | DenyExternalAccessToCodeBuild |
codebuild:* |
* |
| CodePipeline | DenyExternalAccessToCodePipeline |
codepipeline:* |
* |
| Secrets Manager | DenyExternalAccessToSecrets |
secretsmanager:* |
* |
What these RCPs do not control
- CodeBuild and CodePipeline execution roles: use IAM policies and SCPs to control what build and pipeline roles can access.
- Kinesis Data Streams: use SCPs, IAM, and stream resource policies because the
kinesisprefix is not currently listed for RCP support. - Management-account resources: RCPs apply to member-account resources, not resources in the AWS Organizations management account.
- Service-linked roles: RCPs do not restrict calls made by service-linked roles.
- Permission grants: an RCP establishes a ceiling; it never grants access by itself.
Safe deployment sequence
- Inventory existing public and cross-account access with IAM Access Analyzer.
- Identify partner roles and required AWS service integrations.
- Add narrow, documented exceptions.
- Attach the RCP to a dedicated test account.
- Review CloudTrail for unexpected
AccessDeniedevents. - Expand to a nonproduction OU.
- Progressively deploy to production OUs.
- Attach at the organization root only after broad validation.
RCP syntax reminders
- Customer-created RCPs use explicit
Denystatements. - The
Principalin an RCP must be"*"; use conditions to distinguish principals. - A customer-managed RCP cannot use a bare
"Action": "*"; specify one or more supported service prefixes. - The AWS-managed
RCPFullAWSAccesspolicy remains attached automatically and does not grant permissions. - Narrow resources and exceptions where service authorization semantics allow it.
Conclusion
The strongest RCP design begins with a common organizational resource perimeter, adds confused-deputy protection for AWS service access, and then layers service-specific controls such as S3 transport encryption and SSE-KMS requirements. SCPs constrain identities; RCPs protect resources. Used together, they create a stronger preventive boundary for a multi-account AWS environment.
Leave a Reply