简体   繁体   中英

Cognito Identity Pools - Attribute-based access control with "dynamic" attributes

I have hundreds of S3 buckets and dozens of users in Cognito User Pool. I want to be able to select which user can access which S3 bucket, for example:

  • user_a can access bucket_1 , bucket_2 , bucket_3
  • user_b can access bucket_2
  • user_c can access bucket_1 , bucket_4

and so on.

I would love to be able to do it without creating a dedicated API creating a dynamic policies. I thought about utilising Cognito Identity Pools and Attribute-based access control.

There is a cool example where an user gets an attribute "department": "legal" and is then assigned a role that is allowed to query only the buckets with -legal suffix, thanks to ${aws:PrincipalTag/department} magic.

If my users were to access only one bucket, that would be a solution. However, in my case a user could get assigned to dozens or hundreds of buckets (think "multiple departments" in the example from AWS docs).

I thought of using multiple custom attributes on each user:

  • bucket_1: true
  • bucket_2: false
  • bucket_3: false
  • ..and so on

and creating a policy that allows you to access given bucket_n if and only if you have an attribute bucket_n: true .

This would work if I had at most 50 buckets (the hard limit of Custom Attributes in Cognito).

In my case, this value is slightly higher (a couple hundreds). I can have users having access to 200+ buckets as well as ones being allowed to only one bucket.

Is there any way to achieve my goal with Cognito Identity Pools and IAM Policies?

Well, I have found a "workaround" that might be useful for some of you.

First, go to Cognito User Pools and create a custom attribute of your choice that will hold all your values. Bear in mind that Cognito has 2048 character limit of an attribute value.

Let's call that attribute attribute_groups . Put all your values there as a string with a delimiter of your choice. In my case user1 with attribute custom:attribute_groups = "123o789" would mean that user1 is logically in group 123 and 789 .

Map these attributes in Cognito Identity Pools . Following the example Tag key for principal : groups would be selected from Attribute name : custom:attribute_groups .

Lastly, set up the IAM policy that your authenticated users in user pool assume as follows. In my case, I want to allow group 123 to list s3://my_bucket/123 , 456 to list s3://my_bucket/456 and so on.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::my_bucket"
            ],
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "123/*"
                    ],
                    "aws:PrincipalTag/groups": [
                        "*123*"
                    ]
                }
            }
        },
        {
            "Sid": "2",
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::my_bucket"
            ],
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "456/*"
                    ],
                    "aws:PrincipalTag/groups": [
                        "*456*"
                    ]
                }
            }
        },
        {
            "Sid": "3",
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::my_bucket"
            ],
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "789/*"
                    ],
                    "aws:PrincipalTag/groups": [
                        "*789*"
                    ]
                }
            }
        }
    ]
}

Finishing my example - when the user1 attains their credentials (you can test that by creating the user, setting its attributes and then doing aws cognito-idp initiate-auth to get ID token , then aws cognito-identity get-id to get the identity and finally aws cognito-identity get-credentials-for-identity to get the access key id/secret access key/session token), they can perform aws s3 ls s3://my_bucket/123 and aws s3 ls s3://my_bucket/789 .

Unfortunately, this is still severely limited by:

  • IAM Policy characters limit (10240)
  • Cognito custom attributes length limit (2048)

and with such granular IAM Policy you can have at most 15-20 "groups".

You may somehow counter that by creating multiple Policies and multiple ("fake") Cognito User Pool Groups, have each user be attached to every group and then iterate over the groups them using cognito:roles attribute inside ID token and assume these roles using GetCredentialForIdentity that allows you to set CustomRoleArn . It works (I've checked), but is really hacky.

However , there is a trick that might extend Cognito capabilities to some of you and get rid of that limit. The requirement is that the structure of directories is predictable and you don't need access to subdirectories. We will use the fact that ${s3:prefix} is also a variable.

If so, your policy may look as follows:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::my_bucket"
            ],
            "Condition": {
                "StringLike": {
                    "aws:PrincipalTag/groups": [
                        "*${s3:prefix}*"
                    ]
                }
            }
        },
        {
            "Sid": "2",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::my_bucket/${s3:prefix}/*"
            ],
            "Condition": {
                "StringLike": {
                    "aws:PrincipalTag/groups": [
                        "*${s3:prefix}*"
                    ]
                }
            }
        }
    ]
}

And then in custom:attribute_groups you have to specify all prefixes (ie all directories) that given user has access to. Note that they will not be allowed to access subdirectories of these directories, as then the ${s3:prefix} will be different.

For example, an user with:

custom:attribute_groups = directoryA####directoryA/subdirectoryAA####directoryC/subdirectoryCA/subsubdirectoryCAA

would be allowed to access files in:

  • my_bucket/directoryA
  • my_bucket/directoryA/subdirectoryAA
  • my_bucket/directoryC/subdirectoryCA/subsubdirectoryCAA

And no other (sub)directories ! directoryA/subdirectoryAB is unreachable, even though directoryA is.

Of course, each attribute can hold maximum 2048 characters. But you can create up to 50 custom attributes in Cognito and extend your policy accordingly. I think that should be sufficient for most conventional use cases.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM