IAMSpy icon indicating copy to clipboard operation
IAMSpy copied to clipboard

Unexpected cross-account behaviour?

Open orzello opened this issue 5 months ago • 4 comments

Hi there, i'm running into some unexpected behaviour which might suggest a potential bug. For cross-account access, the can_i and who_can queries don't appear to validate the resource policy within the trusting account.

My understanding for cross-account access is that both of these conditions must be met:

  • the resource policy in the trusting account should grant access to the user/role in the trusted account
  • the user/role in the trusted account must have an identity-based policy which grants access to the resource in the trusting account

This is where i've got this from: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic-cross-account.html

I've included some information on the steps below. I may be misunderstanding something though. I'd be interested to get your thoughts and see if you can replicate this :)

Gaad files:

Resource policy files:

1. Setup both accounts

Account 1 (111111111111)

Account 1 is the 'trusted account' and has three users:

arn:aws:iam::111111111111:user/userA
arn:aws:iam::111111111111:user/userB
arn:aws:iam::111111111111:user/userC

arn:aws:iam::111111111111:user/userA has permissions:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowCrossAccountGetObject",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::somenewtestbucket2/*"
        }
    ]
}

arn:aws:iam::111111111111:user/userB has no permissions assigned.

arn:aws:iam::111111111111:user/userC has permissions:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowGetObject",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::somenewtestbucket2/*"
        }
    ]
}

Account 2 (222222222222)

Account 2 is the 'trusting account' and has an S3 bucket that users in account 1 will try to access.

S3 bucket arn:aws:s3:::somenewtestbucket2 with bucket policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowGetObject",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::111111111111:user/userA",
                    "arn:aws:iam::111111111111:user/userB"
                ]
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::somenewtestbucket2/*"
        }
    ]
}

2. Manually verify effective access

From my understanding of IAM, only arn:aws:iam::111111111111:user/userA should have access to the bucket. I manually verified this below:

$ aws --profile account1UserA s3 cp s3://somenewtestbucket2/test.txt ./account1UserADownloaded.txt
download: s3://somenewtestbucket2/test.txt to ./account1UserADownloaded.txt

$ aws --profile account1UserB s3 cp s3://somenewtestbucket2/test.txt ./account1UserBDownloaded.txt
fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden

$ aws --profile account1UserC s3 cp s3://somenewtestbucket2/test.txt ./account1UserCDownloaded.txt
fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden

3. run can_i and observe unexpected output

# load account 1 GAAD and Resource Policies files
model.load_gaad("gaad-111111111111.json")
model.load_resource_policies("resource-111111111111.json") 

# load account 2 GAAD and Resource Policies files
model.load_gaad("gaad-222222222222.json")
model.load_resource_policies("resource-222222222222.json")

# Returns True, which is expected
model.can_i("arn:aws:iam::111111111111:user/userA", "s3:GetObject", "arn:aws:s3:::somenewtestbucket2/test.txt", conditions=[], condition_file=None, strict_conditions=True)

# Returns True, which is unexpected
model.can_i("arn:aws:iam::111111111111:user/userB", "s3:GetObject", "arn:aws:s3:::somenewtestbucket2/test.txt", conditions=[], condition_file=None, strict_conditions=True)

# Returns False, which is expected
model.can_i("arn:aws:iam::111111111111:user/userC", "s3:GetObject", "arn:aws:s3:::somenewtestbucket2/test.txt", conditions=[], condition_file=None, strict_conditions=True)

4. run who_can and observe unexpected output

Run who_can query:

who_can_identities = model.who_can("s3:GetObject", "arn:aws:s3:::somenewtestbucket2/test.txt", conditions=[], condition_file=None, strict_conditions=True)

print("who_can results:")
for identity in who_can_identities:
	print(identity)

Prints:

arn:aws:iam::111111111111:user/userA (expected)
arn:aws:iam::111111111111:user/userB (unexpected)

Tried so far:

  • ran using both strict_conditions set to True and False
  • merged the gaad and resource policy files for both accounts (e.g gaad-merged.json andresource-merged.json) and loaded them to the model in one go, e.g model.load_gaad("gaad-merged.json"). This returned the same results as above

orzello avatar Sep 09 '25 14:09 orzello