In a multi-account AWS environment, maintaining consistent security and compliance controls across resources is a significant challenge. AWS Resource Control Policies (RCPs) offer a robust solution by enabling centralized governance. These policies define permission guardrails that limit what actions can be performed on resources within member accounts.
Unlike traditional IAM policies that grant access, RCPs act as guardrails. They restrict the maximum set of permissions that can be used, regardless of what resource-based or identity-based policies allow. This ensures that even if an overly permissive resource policy is attached in an account, RCPs can still prevent unintended access.
Key characteristics of RCPs:
-
Hierarchical Attachment: RCPs can be applied at the organization, organizational unit (OU), or individual account level. All applicable policies are evaluated together, and the most restrictive outcome is enforced. In practice, this means that a single Deny statement in any relevant RCP will block access, regardless of other permissions.
-
Global Impact: RCPs affect all principals within member accounts including root users. The only exception is service-linked roles, which remain exempt to ensure AWS services continue to function properly.
-
No Permission Granting: RCPs do not grant access. They simply constrain it. Users and roles must still be explicitly granted permissions through IAM or resource-based policies.
By using RCPs, organizations can mitigate the risks of resource misconfigurations and strengthen compliance across their AWS environment. In the sections that follow, we’ll explore the need for RCSs, common use cases, current limitations and best practices for deploying them effectively.
The Need for RCPs
Imagine you’re part of a central cloud team responsible for strengthening the security and compliance posture of your organization’s AWS environment.
A common first step is to define Service Control Policies (SCPs) to limit which API actions IAM principals can perform. These SCPs serve as guardrails for identity-based policies, ensuring users can’t exceed their assigned permissions.
However, SCPs do not restrict resource-based policies, which are often used to grant cross-account access. For example, a developer might add a bucket policy to allow an external account access to an S3 bucket. If misconfigured, intentionally or accidentally, this can expose sensitive data. The diagram below shows several AWS services that support resource-based policies. As the number of such services grows, governing resource policies at scale becomes increasingly complex, particularly in large organizations.

Services Supporting Resource Policies
This is where Resource Control Policies (RCPs) come in. RCPs extend governance to resource policies by enforcing organization-level guardrails that restrict how resources can be shared. Even if someone tries to permit unauthorized access through a resource policy, an RCP can centrally block it to ensure consistent and secure boundaries across all accounts.
For example, attaching an RCP at the root level establishes a centralized data perimeter that protects both existing and newly created accounts from accidental data exposure. In the scenario illustrated below, an external principal is denied access to an S3 bucket even though the bucket policy explicitly allows it. RCP enforces an additional layer of control that overrides overly permissive resource policies.

Resource Control Policy in Action
Use-Cases
- Only allow trusted identities to access my resources.
This control ensures that only trusted identities, whether from your AWS Organization or approved third-party accounts, can access your resources. Trusted identities are defined using your organization ID (aws:SourceOrgID), specific account IDs (aws:PrincipalAccount), and AWS services acting on your behalf (aws:PrincipalIsAWSService).
For example, several accounts in my organization use ELB access logging. To support this, I explicitly allow the ELB service accounts for the eu-west-1 (156460612806) and eu-north-1 (897822967062) regions to write to my S3 buckets. These ELB accounts can deliver logs to my buckets only if the bucket policies also permit access.
{
"Sid": "TrustOnlyOrgIdentities",
"Effect": "Deny",
"Principal": "*",
"Action": [
"s3:*",
"sqs:*",
"kms:*",
"secretsmanager:*",
"sts:AssumeRole",
"sts:DecodeAuthorizationMessage",
"sts:GetAccessKeyInfo",
"sts:GetFederationToken",
"sts:GetServiceBearerToken",
"sts:GetSessionToken",
"sts:SetContext"
],
"Resource": "*",
"Condition": {
"StringNotEqualsIfExists": {
"aws:PrincipalOrgID": "o-01abc24def",
"aws:PrincipalAccount": [
"156460612806",
"897822967062"
],
"aws:ResourceTag/dp:exclude:identity": "true"
},
"BoolIfExists": {
"aws:PrincipalIsAWSService": "false"
}
}
}
You might be wondering why the policy above doesn’t cover all STS actions (sts:*). This is because the aws:PrincipalOrgID condition key is only present in the request context if the calling principal is a member of an AWS Organization. This key is not included for federated users. For example, when using SAML federation (e.g., through Entra ID), users assume roles via the sts:AssumeRoleWithSAML API. Since these federated users are not part of an AWS Organization, the aws:PrincipalOrgID condition key is not populated in their requests. If the policy denied all sts:* actions based on this key, federated users would be unintentionally blocked from accessing resources—even if their access is legitimate.
- Prevent Cross-Service Confused Deputy Problem
Let’s suppose you created a bucket and updated its policy as follows:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bucket-name/*",
}
]
}
This policy allows Amazon CloudFront to access objects in the specified S3 bucket, but it does so without restricting who CloudFront is acting on behalf of. This creates a classic cross-service confused deputy problem: any AWS customer can configure their own CloudFront distribution to use your S3 bucket as an origin, and CloudFront will appear as a legitimate caller and can bypass the intended access controls. Because the bucket policy does not include a condition your bucket becomes vulnerable to unauthorized use via another party’s CloudFront distribution.
To prevent confused deputy attacks when trusting AWS services, it’s a best practice to limit the scope of that trust. Specifically, allow the service to access your resources only if the request originates from within your AWS Organization.
You can enforce this restriction by using the aws:SourceOrgID and aws:SourceAccount condition keys in your Resource Control Policies. The following RCP protects all supported services from confused deputy vulnerabilities by denying access if the origin of the request is outside your organization.
This policy applies to any AWS service principal (aws:PrincipalIsAWSService = true). It works as follows:
- If an AWS service doesn’t support these condition keys, aws:SourceAccount will be null and the policy is not evaluated.
- If the request originates from an account not in an AWS Organization, only aws:SourceAccount key will be set. In this case, aws:sourceOrgId control evaluates to false and the request is denied.
- If the request comes from an AWS account in an organization other than yours (aws:SourceOrgID does not match your organization ID), and the resource is not explicitly excluded using the tag (rcp:exclude=true), the request is also denied.
When a service enables support for the aws:SourceAccount, aws:SourceOrgPaths, and aws:SourceOrgID condition keys, it will automatically be subject to this policy.
{
"Sid": "EnforceConfusedDeputyProtection",
"Effect": "Deny",
"Principal": "*",
"Action": [
"s3:*",
"sqs:*",
"kms:*",
"secretsmanager:*",
"sts:*"
],
"Resource": "*",
"Condition": {
"StringNotEqualsIfExists": {
"aws:SourceOrgID": "o-01abc24def",
"aws:ResourceTag/rcp:exclude": "true"
},
"Null": {
"aws:SourceAccount": "false"
},
"Bool": {
"aws:PrincipalIsAWSService": "true"
}
}
}
- Enforce Secure Defaults Centrally
To protect network traffic from eavesdropping or tampering, it’s essential to require encrypted connections. HTTPS (TLS) ensures that sensitive data is transmitted securely, even if a malicious actor attempts to intercept the traffic. In AWS, you can enforce this by using the aws:SecureTransport
condition key in resource policies to deny requests that use unencrypted HTTP. You can implement an RCP using this condition to maintain security by default across your accounts. This guardrail ensures that only secure (HTTPS) traffic is allowed to interact with your resources.
{
"Sid": "EnforceSecureTransport",
"Effect": "Deny",
"Principal": "*",
"Action": [
"sts:*",
"s3:*",
"sqs:*",
"secretsmanager:*",
"kms:*"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:SecureTransport": "false"
}
}
}
For S3 buckets, you can enforce a minimum TLS protocol version. Maintaining a minimum TLS version is important to ensure connections use strong, up-to-date encryption protocols that helps to protect against known vulnerabilities in older TLS versions. You can enforce this control for S3 access by using the s3:TlsVersion
condition key. The following policy requires clients to use TLS version 1.2 or higher.
{
"Sid": "EnforceS3TlsVersion",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"NumericLessThan": {
"s3:TlsVersion": [
"1.2"
]
}
}
}
Current Limitations
- The number of supported services is only 5 as of May, 2025.
This is definitely going to change in the future and more services will be covered. The diagram below highlights several AWS services that support resource-based policies, alongside the limited set currently supported by RCPs. Note that this is not a comprehensive, more services support resource policies but are not shown here.

RCP Coverage as of May 2025
- Excluding Resources – It’s Not That Simple.
Resource tags can be used to exclude certain resources from the scope of a Resource Control Policy (RCP). However, S3 buckets are an exception—bucket tags are not included in request context, so they can’t be used for exclusion. This limitation complicates the rollout of RCP guardrails at the organization root or OU level. For instance, if an account contains a public S3 bucket, you can’t simply exclude that bucket using tags. Instead, you’ll need to detach the relevant RCPs from the account and all OUs it belongs to.
- RCPs do not apply to resources in the management account.
This creates a gap, especially in preventing confused deputy scenarios. Ideally, there should be a way to apply RCP-like protections to sensitive roles and resources in the management account. Without such safeguards, teams may assume full protection is in place and overlook this exposure, leaving the management account vulnerable.
Best Practices
- Know who you trust
In large organizations, it can be difficult to maintain visibility over which external principals are granted access through resource policies, especially without automated governance tools in place. To address this, use tools like IAM Access Analyzer or develop custom solutions to audit resource and trust policies for unintended external access. Once you’ve identified which external accounts require legitimate access, you can fine-tune your RCPs to explicitly allow those trusted accounts.
- Group policies by impact level
Categorize your policies into low-impact and high-impact groups. Begin with low-impact policies such as enforcing secure defaults, before rolling out high-impact policies that may affect data access or production traffic.
- Deploy incrementally
Start by attaching policies to sandbox or test environments. Once you’ve validated that the policies behave as expected and do not disrupt functionality, roll them out to production accounts gradually.
- Monitor for access issues
Monitor for unexpected access issues by tracking AccessDenied errors in AWS CloudTrail. Setting up CloudWatch metric filters or enabling CloudTrail Insights can help detect and respond to policy-related disruptions quickly.