AWS S3 Transfer Manager ${cognito-identity.amazonaws.com:sub} Policy Variable Access Denied
Asked Answered
P

2

9

I am trying to download a file from AWS S3 to my iOS mobile app from a folder that is specific to the user, using Transfer Manager, like so:

@IBAction func download() {
    let transferManager = AWSS3TransferManager.default()!
    let downloadingFileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("disney1.jpg")
    let downloadRequest = AWSS3TransferManagerDownloadRequest()!
    downloadRequest.bucket = "sidestreamx"
    // user's UUID/disney1
    downloadRequest.key = "631d121f-b294-4318-b3cd-36b3b74ebdff/disney1"
    downloadRequest.downloadingFileURL = downloadingFileURL

    transferManager.download(downloadRequest).continue(with: AWSExecutor.mainThread(), with: {
        (task: AWSTask<AnyObject>) -> Any? in
        if let error = task.error as? NSError {
            // handle error
            return nil
        }
        self.imageView.image = UIImage(contentsOfFile: downloadingFileURL.path)
        return nil
    })
}

My IAM role permission policy is the following, gotten from this AWS doc:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "GetBucketListIfRequestIsForUser",
            "Action": [
                "s3:ListBucket"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::sidestreamx"
            ],
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "${cognito-identity.amazonaws.com:sub}/*"
                    ]
                }
            }
        },
        {
            "Sid": "S3GetObjects",
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::sidestreamx/${cognito-identity.amazonaws.com:sub}/*"
            ]
        }
    ]
}

Response I get is

<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code>
   <Message>Access Denied</Message>    
   <RequestId>E1F205B58EF4A670</RequestId>
   <HostId>dUWI8PfVZL3mJmykjhXRqvFd1yt/CqDFNlwgwD3kmLk2vrMBP6JvVgezMYSROt3KyE3dx0+3eDE=</HostId>
</Error>

The user is authenticated via AWS Cognito User Pool & Cognito Federated Identities. I've debugged and extracted the JWT token, and seen that the sub = "631d121f-b294-4318-b3cd-36b3b74ebdff". I've even used Charles to see the Request/Response.

It does work if I replace ${cognito-identity.amazonaws.com:sub} in the last statement S3GetObjects with 631d121f-b294-4318-b3cd-36b3b74ebdff to get arn:aws:s3:::sidestreamx/631d121f-b294-4318-b3cd-36b3b74ebdff/*. The first statement can continue having the policy variable and it'll still work. It'll work if I remove the first statement altogether! It's when I add the policy variable to the last statement where it starts to breakdown.

I've checked out this Stack Overflow question and this one, to no avail. So yea, I don't know. I've been at this for almost over 9 man hours, so any help would sincerely be appreciatd.

Pontic answered 5/3, 2017 at 6:52 Comment(0)
P
26

Problem solved. Turns out, ${cognito-identity.amazonaws.com:sub} doesn't really refer to the sub in the JWT token. It refers to IdentityID from the credentialsProvider:

    (AWSServiceManager.default().defaultServiceConfiguration.credentialsProvider
        as! AWSCognitoCredentialsProvider).getIdentityId()
        .continue({task -> Any? in
        print("Credentials ID is \(task.result!)")
        return nil
    })

I manually made a folder in my bucket with the name equal to task.result! (which is in the format of us-east-1:XXXXXXXXXXXXXXXXXX fyi), and it worked.

Pontic answered 5/3, 2017 at 7:59 Comment(3)
Sorry for the confusion. Yes, the policy variables refer to the Cognito Federated Identity's identity id, not to a user pools field, despite the admittedly confusing terminology overlap.Intolerance
Thank you! That took half a day to realise the 'sub' didn't match to the 'sub' in my cognito user's attributes. And was the opposite of one post in the GitHub issues.Allina
two years later, almost exactly. Took me the best part of the day hitting my head on the wall and your post finally solved the issue!Granada
S
1

While @azizj's answer is correct:

${cognito-identity.amazonaws.com:sub} doesn't really refer to the sub in the JWT token

In my case, I really did need to reference the sub from the JWT token in my IAM policy. Turns out this is doable! Go to your Identity pool -> User access -> select the Identity provider and edit the Attributes for access control. You can use the default claim mappings or provide custom tag keys. I went with the defaults, and I can now reference the sub claim of the token by using the username principal tag in my IAM policy:

{
    "Effect": "Allow",
    "Action": "s3:ListBucket",
    "Resource": [
        "arn:aws:s3:::mybucket"
    ],
    "Condition": {
        "StringLike": {
            "s3:prefix": [
                "${aws:PrincipalTag/username}/*"
            ]
        }
    }
},

Obviously, you can map the claim to a different principal tag key and reference it accordingly in the policy.

Importantly, in order for this to work you also need to update the trust policy for the IAM role and add "sts:TagSession" as an allowed action. If you don't do this, you will get an error:

InvalidIdentityPoolConfigurationException: Invalid identity pool configuration. Check assigned IAM roles for this pool.

The AWS documentation covers all of this, but it took me a while to find the right information, so posting in case it helps someone else.

Simplehearted answered 25/7, 2024 at 21:52 Comment(1)
Whoa, amazing! I'm guessing everybody comes here because they want to reference the JWT sub in their policies. Thank you!Kagoshima

© 2022 - 2025 — McMap. All rights reserved.