azizj
azizj

Reputation: 3787

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

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.

Upvotes: 9

Views: 4822

Answers (2)

Jake Stoeffler
Jake Stoeffler

Reputation: 2905

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.

Upvotes: 1

azizj
azizj

Reputation: 3787

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.

Upvotes: 26

Related Questions