Amplify-js: Restricting access to s3 folder by congito sub

Created on 11 Dec 2018  Â·  8Comments  Â·  Source: aws-amplify/amplify-js

* Which Category is your question related to? *
Auth / Storage

* What AWS Services are you utilizing? *
S3 / Cognito

* Provide additional details e.g. code snippets *
I am trying to have users of my app upload images to a folder in an s3 bucket which then triggers a lambda function that adds the image as the profile picture in my dynamodb.

I am having an issue when restricting the access so that users can only mutate their own images.

I used the policy described here https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_s3_cognito-bucket.html

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": ["s3:ListBucket"],
            "Resource": ["arn:aws:s3:::<BUCKET-NAME>"],
            "Condition": {
                "StringLike": {
                    "s3:prefix": ["app/"]
                }
            }
        },
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::<BUCKET-NAME>/app/${cognito-identity.amazonaws.com:sub}",
                "arn:aws:s3:::<BUCKET-NAME>/app/${cognito-identity.amazonaws.com:sub}/*"
            ]
        }
    ]
}

in my amplify config I changed the default customPrefixes to reflect the folder.

customPrefix: {
   private: ‘app/’,
   protected: ‘app/’,
   public: ‘app/’,
},

But if I try to upload

Amplify.Storage.put(`${congito.sub}/test.txt`', 'Hello')
    .then (result => console.log(result))
    .catch(err => console.log(err));

I am getting an unauthorized error.

However, if I am adjusting the policy by removing ${cognito-identity.amazonaws.com:sub} to:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": ["s3:ListBucket"],
            "Resource": ["arn:aws:s3:::<BUCKET-NAME>"],
            "Condition": {
                "StringLike": {
                    "s3:prefix": ["app/"]
                }
            }
        },
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::<BUCKET-NAME>/app",
                "arn:aws:s3:::<BUCKET-NAME>/app/*"
            ]
        }
    ]
}

it works fine. In my lambda trigger I see then that the userIdentity does not contain a cognito sub though, only a principalId.

{
    "Records": [
        {
            "eventVersion": "2.1",
            "eventSource": "aws:s3",
            "awsRegion": "eu-west-1",
            "eventTime": "2018-12-11T15:10:56.056Z",
            "eventName": "ObjectCreated:Put",
            "userIdentity": {
                "principalId": "AWS:AROAXXXXXXXXXSJ7LGAIC:CognitoIdentityCredentials"
            },
            "requestParameters": {
                "sourceIPAddress": XXXXXX
            },
            "responseElements": {
                "x-amz-request-id": XXXX,
                "x-amz-id-2": XXXX,
            },
            "s3": {
                "s3SchemaVersion": "1.0",
                "configurationId": "01b36196-568a-4ce7-97ab-a428e8c581eb",
                "bucket": {
                    "name": "dh--sfa-api--image--images",
                    "ownerIdentity": {
                        "principalId": XXXXX
                    },
                    "arn": "arn:aws:s3:::dh--sfa-api--image--images"
                },
                "object": {
                    "key": "app/07e32422-5a14-4c9b-a286-dc440a72687f/test.txt",
                    "size": 5,
                    "eTag": "8b1a9953c4611296a827abf8c47804d7",
                    "sequencer": "005C0FD38006BD30DC"
                }
            }
        }
    ]
}

What could I be doing wrong here ?

Storage question

Most helpful comment

I found the issue in the end. Apparently it seems to be a common misconception that the ${cognito-identity.amazonaws.com:sub} is the ${congito.sub} while they are actually different.

When using the level: protected it automactically adds the ${cognito-identity.amazonaws.com:sub} which looks like eu-west-1:8f8ac3c6-65bd-4844-a4b6-5446c651684b where the uuid is not the ${congito.sub}.

So querying https://XXXXXX.s3.eu-west-1.amazonaws.com/app/${cognito:sub}/example1.png returns a 404, correct would have been https://XXXXXX.s3.eu-west-1.amazonaws.com/app/${cognito-identity.amazonaws.com:sub}/example1.png

All 8 comments

@philiiiiiipp - What happens when you try setting the restriction level within the .put call?

Yes I have, public, private and protected, but all with the same response to the preflight call.

Request Method: OPTIONS
Status Code: 403 Forbidden

What I also noticed is that it adds app/region/usersub to the path if I am using the protected level.

https://xxxx.s3.eu-west-1.amazonaws.com/app/eu-west-1%3A8f8ac3c6-65bd-4844-a4b6-5446c651684b/246dd1e6-44dc-43e0-bf61-bec0f4f526ef/example.png

this is my CognitoAuthRole, which should have all the necessary permissions and even beyond.

        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::*"
        },
        {
            "Sid": "VisualEditor2",
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::*/*"
        },

Ok that is not entirely true, it seems as if allowing everything in my CogntioAuthUser works fine, it just took some time to update.

Still, the moment I restrict it

{
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::XXXXXX--images/app/${cognito-identity.amazonaws.com:sub}/*"
        },

I am getting a 403

Request URL: https://XXXXXX.s3.eu-west-1.amazonaws.com/app/3908ef60-0476-41c6-a186-06b5c217cdcc/example1.png
Request Method: PUT
Status Code: 403 Forbidden

Being less restrictive works fine

        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::XXXXXXX/app/*"
        },

I found the issue in the end. Apparently it seems to be a common misconception that the ${cognito-identity.amazonaws.com:sub} is the ${congito.sub} while they are actually different.

When using the level: protected it automactically adds the ${cognito-identity.amazonaws.com:sub} which looks like eu-west-1:8f8ac3c6-65bd-4844-a4b6-5446c651684b where the uuid is not the ${congito.sub}.

So querying https://XXXXXX.s3.eu-west-1.amazonaws.com/app/${cognito:sub}/example1.png returns a 404, correct would have been https://XXXXXX.s3.eu-west-1.amazonaws.com/app/${cognito-identity.amazonaws.com:sub}/example1.png

For anyone wondering how to get the ${cognito-identity.amazonaws.com:sub}

Auth.currentCredentials().then( cred => console.log(cred.identityId) )

@philiiiiiipp why you didn't try this policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::{enter bucket name}/public/*",
                "arn:aws:s3:::{enter bucket name}/protected/${cognito-identity.amazonaws.com:sub}/*",
                "arn:aws:s3:::{enter bucket name}/private/${cognito-identity.amazonaws.com:sub}/*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::{enter bucket name}/uploads/*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::{enter bucket name}/protected/*"
            ],
            "Effect": "Allow"
        },
        {
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "public/",
                        "public/*",
                        "protected/",
                        "protected/*",
                        "private/${cognito-identity.amazonaws.com:sub}/",
                        "private/${cognito-identity.amazonaws.com:sub}/*"
                    ]
                }
            },
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::{enter bucket name}"
            ],
            "Effect": "Allow"
        }
    ]
}

If you use amplify-cli this policies are created for you when you enable Storage on your project.

I already had an s3 bucket setup, but I suppose the bigger reason was that I wanted to know exactly how it works :-).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

epicfaace picture epicfaace  Â·  3Comments

cosmosof picture cosmosof  Â·  3Comments

rygo6 picture rygo6  Â·  3Comments

ldgarcia picture ldgarcia  Â·  3Comments

guanzo picture guanzo  Â·  3Comments