I'm struggling to get Cognito authorization working for fine-grained DynamoDB access control. This seems to be something that lots of people have problems with but there don't seem to be any solutions that I can see. I am using the C++ AWS SDK although I don;t think that's relevant.
Let's say I have a table "MyUsers" which has a Primary key consisting of both Partition Key and Sort Key. The Partition key is a unique value for each user (e.g. "AB-CD-EF-GH") - let's call it the "UserID". I want these users to log on using Cognito, then use Cognito to provide temporary credentials to each user to give access to all the rows in the table that have their UserID as the Partition key (GetItem, PutItem, Query, etc), but not be able to access any rows that start with a different user's partition key.
This seems to be what is being described here: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_dynamodb_items.html
So I have set up a Cognito User Pool and Identity Pool. Adding a "User" with a UserID of "AA-AA-AA-AA" consists of two parts:
- Add a row to the MyUsers table, with PK = AA-AA-AA-AA (and SK = AA-AA-AA-AA) (There will be other rows in the table where the UserID is AA-AA-AA-AA but the SK represents something different. For now though I am only testing this with one row per user).
- Create a Cognito user with a user_name of AA-AA-AA-AA (The user can log on with this UserID or the preferred_name alias, together with a password).
This all works and Cognito correctly generates the relevant emails with verification codes, adds the user to the user pool, etc.
My Identity Pool is set up with the User Pool as an identity provider, and a Cognito_Role (which is what will give the logged-on Cognito user the permissions to read the table). This role appears under the Identity Pool as an authenticated role, "service-role/Cognito_Role". The role has a Trust Relationship as follows:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Federated": "cognito-identity.amazonaws.com" },
"Action": [ "sts:AssumeRoleWithWebIdentity" ],
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "[REGION]:[IDENTITYPOOL_GUID]"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}
The role has a single policy attached, which is set up as follows:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:Query"
],
"Resource": [
"arn:aws:dynamodb:[REGION]:[ACCOUNT]:table/MyUsers"
],
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": [ "${cognito-identity.amazonaws.com:sub}" ]
}
}
}
]
}
What this is SUPPOSED to do is allow Query / GetItem access on the MyUsers table ONLY if the UserID of the currently logged on Cognito user matches the Partition Key in the MyUsers table.
So for user AA-AA-AA-AA to query a table, the following process should happen:
- The user logs on using UserID and password (or preferred_name and password). This uses Cognito to log on using InitiateAuth. I note the IdToken from the response.
- When the user needs to access the database, I retrieve temporary credentials using GetId (using the IdToken retrieved above) GetTemporaryCredentials (again using the IdToken and the IdentityId returned by GetId) This correctly returns me a temporary Access Key, Secret Key and Session token.
I then create a DynamoDB client using the Access Key, Secret Key and Session token. I use this Client to perform a Query on the MyUsers table, using the UserID (AA-AA-AA-AA) as the PK and SK. But the Query request ALWAYS gives me an error response of:
FAILED: User: arn:aws:sts::[ACCOUNT]:assumed-role/Cognito_Role/CognitoIdentityCredentials is not authorized to perform: dynamodb:Query on resource: arn:aws:dynamodb:[REGION]:[ACCOUNT]:table/MyUsers because no identity-based policy allows the dynamodb:Query action
As part of my testing, I have tried removing the Condition section of the role's permissions policy completely. This correctly allows me to Query the row on the database (but obviously would not limit me to a specific Cognito user).
I also tried changing the policy Condition for the role to
"dynamodb:LeadingKeys": "AA-AA-AA-AA"
and this DOES allow access to this specific row for Query, but not for GetItem (I can live without GetItem access if necessary, although it would be good to get this working too if it's allowed).
However any attempt to try and use the "currently logged on Cognito user" has failed. I know that "sub" is an automatically-generated ID for each user, so I have set up the "attributes for access control" for the User Pool to "use default mappings", which maps username (i.e. my UserID, I hope) to a claim of 'sub'.
I tried various other things, in order to try and get this to work:
I tried replacing the "sub" in the policy Condition for the role to a specific Cognito user attribute, for example
"dynamodb:LeadingKeys": [ "${cognito-identity.amazonaws.com:username}" ]
"dynamodb:LeadingKeys": [ "${cognito-identity.amazonaws.com:user_name}" ]
This gives exactly the same error response as above.
I have also added a custom attribute called "user_id" to the Cognito user, and when I create the user I copy the user ID into this attribute.
Then I tried the following:
"dynamodb:LeadingKeys": [ "${cognito-identity.amazonaws.com:custom:user_id}" ]
I also tried adding "sts:TagSession"
to the Trust relationship policy of the Role, and changing the role permissions policy to:
"dynamodb:LeadingKeys": "${aws:PrincipalTag/user_name}"
"dynamodb:LeadingKeys": "${aws:PrincipalTag/username}"
"dynamodb:LeadingKeys": "${aws:PrincipalTag/custom:user_id}"
But for every single thing that I have tried, I get exactly the same error message
FAILED: User: arn:aws:sts::[ACCOUNT]:assumed-role/Cognito_Role/CognitoIdentityCredentials is not authorized to perform: dynamodb:Query on resource: arn:aws:dynamodb:[REGION]:[ACCOUNT]:table/MyUsers because no identity-based policy allows the dynamodb:Query action
The only thing I have been able to find in my extensive searching for a solution is one mention that the 'sub' part of ${cognito-identity.amazonaws.com:sub}
is not actually the Cognito UserID, but is actually the automatically-generated "Identity Pool" Id, and nothing to do with the User Pool. But if this is the case, then it seems that what I want to do (which seems like a not uncommon requirement, surely?) will mean using this Identity Pool Id as the PK for the MyUsers table. So all access to the table via my own UserID (AA-AA-AA-AA) will require adding an extra step to always retrieve an Identity Pool ID for the Cognito user AA-AA-AA-AA, and also use the Identity Pool as my partition Key (when there are reasons that I would like to use my own generated value (AA-AA-AA-AA) as the PK for the MyUsers table). Is there an easier way to achieve what I want? Or is there just no ability at all to link a Cognito User Pool username and a table Partition Key?
Any suggestions of things I might have missed along the way, or parts of this that I may have misunderstood (I am quite new to AWS) would be gratefully received! Thank you.