Skip to content
Ashish.
All posts
Diagram illustrating AWS cross-account access via resource policies and trust boundaries.
6 min readDevelopmentCloud EngineersFeatured#aws#iam#cross-account-access#resource-policies#security#cloud-engineering#principal-element

Cross-Account Access With Resource Policies

Learn how to configure resource policies for secure cross-account access in AWS, covering principal elements, trust relationships, and confused deputy prevention.

By Ashish KumarPart 5 of AWS IAM Deep Dive

In the AWS Identity and Access Management (IAM) model, most engineers are familiar with identity-based policies: permissions attached directly to IAM users, groups, or roles. These policies answer the question, "What can Alice do?" However, when you need to grant access across organizational boundaries—such as allowing a data lake in Account A to be read by an analytics service in Account B—you must use resource policies.

Resource policies are attached to the resource itself (e.g., an S3 bucket, an SQS queue, or a KMS key). They answer a different question: "Who is allowed to interact with this resource?" This distinction is critical for cross-account architectures because it shifts the trust boundary from the identity provider to the resource owner.

The Mechanism of Resource Policies

Unlike identity-based policies, which are evaluated by AWS during the authorization phase of the API call, after authentication has occurred, resource policies are evaluated by the resource service at the time of the API call. When a request arrives, the service checks if the resource has an attached policy that explicitly allows the action for the requesting principal.

The core of a resource policy is the Principal element. In identity-based policies, the principal is implicit (the identity attached to the policy). In resource policies, the principal is explicit.

Consider an S3 bucket policy in Account A that grants read access to a role in Account B:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111122223333:role/AnalyticsRole"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::secure-data-bucket/*"
    }
  ]
}

Here, Account A is saying, "I trust any entity assuming the role AnalyticsRole in Account 111122223333 to read this bucket." Note that the Principal element does not verify who assumed that role. It only verifies that the request is authenticated by that role. If AnalyticsRole is assumed by an attacker who compromised a developer's credentials in Account B, the resource policy still allows the access. This is the fundamental limitation of resource policies: they lack context about the original requester.

Constructing Trust Boundaries

Configuring cross-account access requires careful scoping of the Principal element in the resource policy. A common mistake is using overly broad principals, such as *, which grants access to any AWS account. This is a severe security risk.

Instead, you should specify the exact ARN of the role or account that needs access. For example, if Account B needs full access to an SQS queue in Account A, the policy might look like this:

{
  "Sid": "AllowAccountBAccess",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::111122223333:root"
  },
  "Action": "sqs:*",
  "Resource": "arn:aws:sqs:us-east-1:222233334444:my-queue"
}

While granting access to an entire account (root) is sometimes necessary for simplicity, it is generally safer to grant access to specific roles. This limits the blast radius if a credential in Account B is compromised. The resource policy owner must ensure that the target role in the remote account has appropriate permissions and is not overly permissive itself.

The Confused Deputy Problem

The most significant security challenge in cross-account access is the "confused deputy" problem. This occurs when a trusted actor (the deputy) is tricked into performing an action on behalf of an untrusted party.

In the context of resource policies, the deputy is the entity (role) authorized by the resource policy, and the 'confusion' lies in the policy's inability to distinguish the true originator from the deputy. The deputy is the role in Account B that is explicitly listed in Account A's resource policy. The resource policy trusts this role. However, the resource policy cannot distinguish between:

  1. A legitimate user in Account B assuming AnalyticsRole to read data.
  2. An attacker in Account C who has somehow managed to assume AnalyticsRole in Account B.

Because the resource policy only checks the immediate principal (AnalyticsRole), it cannot see that the original request originated from an untrusted source. This is a mechanism-level limitation: resource policies are stateless regarding the chain of trust beyond the immediate principal.

Mitigation via Condition Keys

To mitigate the confused deputy problem, you must add context to your resource policies using condition keys. aws:PrincipalArn is a common condition key used to validate the identity of the caller, but clarify that it does not verify the network source. For broader trust boundaries, aws:SourceAccount or aws:PrincipalOrgID may be more robust alternatives depending on your architecture.

By requiring that the principal ARN matches a specific pattern, you can ensure that the role assuming access is the one you intended, and potentially verify the source of the request. For example, you can restrict access to only those requests that originate from a specific VPC endpoint or a specific source account.

{
  "Sid": "PreventConfusedDeputy",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::111122223333:role/AnalyticsRole"
  },
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::secure-data-bucket/*",
  "Condition": {
    "StringEquals": {
      "aws:PrincipalArn": "arn:aws:iam::111122223333:role/AnalyticsRole"
    }
  }
}

While this example seems redundant, it becomes powerful when combined with other conditions. For instance, if Account B uses STS AssumeRole to access Account A, you can verify the aws:PrincipalArn in Account A's policy to ensure that the role being assumed is indeed the one you expect, and not a compromised or rogue role. Additionally, for services like S3, you can use aws:SourceVpc or aws:SourceIp to further restrict access based on network context.

Conclusion

Resource policies are essential for cross-account access in AWS, providing a way for resource owners to define who can interact with their assets. However, they introduce the confused deputy problem because they only validate the immediate principal, not the origin of the request. To secure cross-account access, engineers must combine precise Principal definitions with strict condition keys like aws:PrincipalArn to enforce strict trust boundaries. This approach ensures that only authorized entities, acting within expected contexts, can access sensitive resources.

Related posts