英文:
Using Cognito user for fine-grained access to DynamoDB table rows
问题
我正在努力使Cognito授权适用于细粒度的DynamoDB访问控制。这似乎是许多人都遇到问题的地方,但似乎没有我能看到的解决方案。我正在使用C++ AWS SDK,尽管我不认为这是相关的。
假设我有一个名为"MyUsers"的表,它具有由分区键和排序键组成的主键。分区键对于每个用户都是唯一的值(例如"AB-CD-EF-GH") - 让我们称之为"UserID"。我希望这些用户使用Cognito登录,然后使用Cognito为每个用户提供临时凭证,以便访问表中具有他们的UserID作为分区键的所有行(GetItem、PutItem、Query等),但不能访问以不同用户的分区键开头的任何行。
这似乎就是这里描述的内容:
https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_dynamodb_items.html;
所以我设置了一个Cognito用户池和身份池。添加一个UserID为"AA-AA-AA-AA"的"User"包括两个部分:
- 向MyUsers表中添加一行,PK = AA-AA-AA-AA(以及SK = AA-AA-AA-AA)
(表中将有其他具有UserID为AA-AA-AA-AA的行,但SK代表不同的内容。但目前,我仅测试每个用户的一行)。 - 创建一个Cognito用户,用户名为AA-AA-AA-AA
(用户可以使用此UserID或preferred_name别名,以及密码登录)。
这一切都有效,Cognito正确生成具有验证码的相关电子邮件,将用户添加到用户池等。
我的身份池已配置为将用户池设置为身份提供者,并创建了一个Cognito_Role(这将为已登录的Cognito用户授予权限以读取表)。
此角色出现在身份池下作为经过身份验证的角色,"service-role/Cognito_Role"。
该角色具有以下信任关系:
{
"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"
}
}
}
]
}
角色附加了一个策略,设置如下:
{
"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}" ]
}
}
}
]
}
这应该允许仅在MyUsers表上进行Query / GetItem访问,前提是当前已登录的Cognito用户的UserID与MyUsers表中的分区键匹配。
因此,对于用户AA-AA-AA-AA来说,查询表应该发生以下过程:
- 用户使用UserID和密码(或preferred_name和密码)登录
这使用Cognito进行登录,使用InitiateAuth。
我注意到响应中的IdToken。 - 当用户需要访问数据库时,我使用GetId(使用上面检索到的IdToken)来检索临时凭证
GetTemporaryCredentials(再次使用IdToken和GetId返回的IdentityId)
这会正确返回临时的访问密钥、秘密密钥和会话令牌。
然后,我使用访问密钥、秘密密钥和会话令牌创建一个DynamoDB客户端。
我使用此客户端在MyUsers表上执行Query,使用UserID(AA-AA-AA-AA)作为PK和SK。
但是,Query请求总是给我返回错误响应:
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
作为我的测试的一部分,我尝试完全删除角色权限策略的Condition部分。
这将允许我正确地在数据库中查询该行(但显然不会限制我只能访问特定的Cognito用户)。
我还尝试将角色的策略条件更改为
"dynamodb:LeadingKeys": "AA-AA-AA-AA"
这确实允许对此特定行进行查询,但不允许GetItem(如果需要的话,我可以不使用GetItem,尽管如果可以,最好使其正常工作)。
然而,尝试使用"当前登录的Cognito用户"始终失败。
我知道"sub"是每个用户自动生成的ID,所以我已经为用户池的"访问控制属性"设置了"default mappings",将用户名(也就是我的UserID,希望如此)映射到一个名为'sub'的声明。
我尝试了各种其他方法,以尝试使其工作:
我尝试将策略条件中的"sub"替换为特定的Cognito用户属性,例如
"dynamodb:LeadingKeys": [ "${cognito-identity.amazonaws.com:username}" ]
"dynamodb:LeadingKeys": [ "${cognito-identity.amazonaws.com:user_name}" ]
这与上面的错误响应完全相同。
我还为Cognito用户添加了一个名为"user_id"的自定义属性,当我创建用户时
英文:
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.
答案1
得分: 2
可以使用Cognito 用户池
的属性(标准属性和自定义属性)作为身份池
IAM角色中的条件,以便提供对AWS资源(例如DynamoDB)的精细访问控制。
在您的情况下,您需要在IAM角色策略中使用PrincipalTag(详细信息请参见此处):
{
"Version": "2012-10-17",
"Statement": [
{
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": [
"${aws:PrincipalTag/user_id}"
]
}
},
"Action": [
"dynamodb:GetItem",
"dynamodb:Query"
],
"Resource": "arn:aws:dynamodb:[REGION]:[ACCOUNT]:table/[TABLE]",
"Effect": "Allow"
}
]
}
为了使这个工作,您应该创建一个AWS::Cognito::IdentityPoolPrincipalTag资源,在其中提供您的用户池
属性(参见Claim
字段)与PrincipalTag的Tag Key
之间的映射:
在上面的示例中,您将aws:PrincipalTag/user_id
标签键映射到自定义的用户池属性(或声明)custom:user_id
,然后可以在IAM策略中使用它。
您还可以使用默认属性,请参见此处。因此,对于Cognito用户池,您可以使用sub
(用户池用户ID)和aud
(用户池应用程序客户端ID)属性(声明)来映射到您的标签键
(例如user_id
)。
至于您在AWS文档中可能找到的以下属性:
- ${www.amazon.com:user_id}
- ${graph.facebook.com:id}
- ${accounts.google.com:sub}
- ${cognito-identity.amazonaws.com:sub}
这些属性来自相应的身份提供者(Amazon、Facebook、Google、Cognito身份池)在身份池中,而不是用户池中。
例如,${cognito-identity.amazonaws.com:sub}
是Cognito Identity Pool
内的身份的sub
属性,而不是Cognito User Pool
的属性。它的值如下:
eu-central-1:edfeed68-3f49-4e69-bd02-27484b04a4b6
如果您转到AWS控制台中的身份池并单击身份浏览器,您将找到一系列身份,这些身份当然不同于用户池身份(用户):
请注意,Cognito Identity Pool
不需要用户池
- 它可以独立存在。Cognito User pool
仅充当另一个身份提供者(例如Google、Facebook、Amazon等)。
更新
要创建IdentityPoolPrincipalTag
映射并找出您可以创建映射的属性,请转到您的用户池的用户
选项卡:
现在单击任何可用用户:
您只能使用现有用户池属性(默认或自定义)!从您尝试的内容中 - username
、user_name
、user name
- 这些属性在用户属性中都不存在。
如上面的屏幕截图所示,您有一个默认的sub
属性(唯一)。或者,您可以创建自定义属性(请参见custom:g_uuid
属性),可以在AWS控制台中为每个用户手动创建,也可以使用Cognito Lambda触发器(例如使用Pre sign-up Lambda触发器)自动创建。
英文:
It is possible to use any Congnito User pool
attribute (both standard and custom attributes) as a condition in the Identity Pool
IAM Role in order to provide fine-grained access to your AWS resources (i.e. DynamoDB).
In your case you need to use the PrincipalTag (see details here) in your IAM Role policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": [
"${aws:PrincipalTag/user_id}"
]
}
},
"Action": [
"dynamodb:GetItem",
"dynamodb:Query"
],
"Resource": "arn:aws:dynamodb:[REGION]:[ACCOUNT]:table/[TABLE]",
"Effect": "Allow"
}
]
}
In order for this to work you should create an AWS::Cognito::IdentityPoolPrincipalTag resource where you provide a mapping between your User pool
attributes (see Claim
field) and the PrincipalTag's Tag Key
:
In the example above you map the aws:PrincipalTag/user_id
tag key to the custom User pool attribute (or claim) custom:user_id
, which you can then use in your IAM policy.
You can also use the default attributes, see details here. So, for Cognito User pool you can use sub
(User pool user id) and aud
(User pool App client id) attributes (claims) to map to your tag key
(i.e. user_id
).
As for the following attributes you may find in AWS documentation:
- ${www.amazon.com:user_id}
- ${graph.facebook.com:id}
- ${accounts.google.com:sub}
- ${cognito-identity.amazonaws.com:sub}
these are the attributes from the respective Identity providers (Amazon, Facebook, Google, Cognito Identity Pool) in the Identity pool, and not in the User Pool.
For instance, ${cognito-identity.amazonaws.com:sub}
refers to the sub
attribute for an identity inside the Cognito Identity Pool
, not Cognito User Pool
. Its value looks like this:
eu-central-1:edfeed68-3f49-4e69-bd02-27484b04a4b6
If you go over to your Identity Pool in AWS Console and click Identity browser - you will find a list of identities, which are of course different from the User Pool identities (users):
Keep in mind, that Cognito Identity Pool
doesn't need a User Pool
- it may, but in general it can exist on its own. Cognito User pool
acts as just another Identity Provider (e.g. Google, Facebook, Amazon, etc.).
UPDATE
In order to create a IdentityPoolPrincipalTag
mapping and find out which attribute you can create a mapping to, go to your User pool's Users
tab:
Now click on any available user:
You can only use existing User pool attributes (default or custom)! From what you were trying - username
, user_name
, user name
- none of those exists in the user attributes.
As you can see in the screenshot above, you have a default sub
attribute (unique). Or you can create a custom attribute (see custom:g_uuid
attribute), either manually in the AWS Console for each user or automatically using Cognito Lambda triggers (e.g. using Pre sign-up Lambda trigger).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论