aws-samples / data-perimeter-policy-examples

Example policies demonstrating how to implement a data perimeter on AWS.
Other
128 stars 17 forks source link

S3 EnforceIdentityPerimeter's use of <third-party-account-a> is unclear #17

Closed cschneider-vertical-relevance closed 10 months ago

cschneider-vertical-relevance commented 10 months ago

Hi,

We're seeking clarity on the use of <third-party-account-a> in this Bucket Policy statement.

        {
            "Sid": "EnforceIdentityPerimeter",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": [
              "arn:aws:s3:::<my-data-bucket>",
              "arn:aws:s3:::<my-data-bucket>/*"
            ],
            "Condition": {
                "StringNotEqualsIfExists": {
                    "aws:PrincipalOrgID": "<my-org-id>",
                    "aws:PrincipalAccount": [
                        "<load-balancing-account-id>",
                        "<third-party-account-a>",
                        "<third-party-account-b>"
                    ]
                },
                "BoolIfExists": {
                  "aws:PrincipalIsAWSService": "false"
                }
            }
        }

Given these IAM docs state:

If your policy statement has multiple context keys attached to a single condition operator, the context keys are evaluated using a logical AND.

Given that a third-party account external to the Org will never have "aws:PrincipalOrgID": "<my-org-id>",

What is the use of <third-party-account-a> in this BP?

liwadman commented 10 months ago

Howdy, thank you for your question!

This policy example is a deny statement, and will deny access to the s3 bucket if all the conditions are met (evaluate to true). So any account that is not from your organization will be denied access, because their aws:PrincipalOrgId is not equal to that of your own and we're using a StringNotEquals operator. So it's like a double negative.

If you want to allow list a specific AWS account that is not part of your AWS organization, you'd list them in the array for aws:PrincipalAccount. When you use an array of values against a context key, that is like an OR statement so it's like "this account, or this account, or this account". If the principal making the request to access does belong to one of those listed accounts, that means the value of aws:PrincipalAccount would match, and the deny statement would not take effect. This is because we're using the negated string operator StringNotEquals.

So the logical AND is "all context keys must evaluate to true", which means the test of aws:PrincipalAccount and aws:PrincipalOrgId, but those tests themselves can be against an array of values which is an OR statement.

Hope this helps, let me know if it does or if I've made it worse.

cschneider-vertical-relevance commented 10 months ago

To sum up expected behavior in a couple formats then:

$p$: aws:PrincipalOrgId == '\<my-org-id>' -> "intra org" $q$: aws:PrincipalAccount present in ['\<third-party-account-a>', ...] -> "whitelisted external account" $\neg p$: "external org" $\neg q$: "not whitelisted external account" Deny: $\neg p \land \neg q$

$p$ $q$ $\neg$ $p$ $\land$ $\neg$ $q$
T T F T F F T
T F F T F T F
F T T F F F T
F F T F T T F

Deny if neither intra-org nor whitelisted external account

from itertools import product
for intra_org, whitelisted_external in product([True,False], [True,False]):
    deny=not intra_org and not whitelisted_external
liwadman commented 10 months ago

Glad to be of help. We explain this a bit longer form in this blog: https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-identities-to-access-company-data/ written by @tatyatsk