Reputation: 3787
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
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
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