DynamoDB w/ Serverless,使用 Fn::GetRef 引用全局二级索引
DynamoDB w/ Serverless, using Fn::GetRef to reference global secondary index
我有一个 API/service 我正在用 DynamoDB table 定义。我有几个索引(定义为全局二级索引)来支持几个查询。我设计了 table,带有 GSI 定义,以及看起来正确的查询。但是,我在进行查询时遇到此异常:
{ AccessDeniedException: User: arn:aws:sts::OBSCURED:assumed-role/chatroom-application-dev-us-east-1-lambdaRole/chatroom-application-dev-getRoomMessages is not authorized to perform: dynamodb:Query on resource: arn:aws:dynamodb:us-east-1:OBSCURED:table/messages-table-dev/index/roomIndex
at Request.extractError (/var/task/node_modules/aws-sdk/lib/protocol/json.js:48:27)
at Request.callListeners (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:105:20)
at Request.emit (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:77:10)
at Request.emit (/var/task/node_modules/aws-sdk/lib/request.js:683:14)
at Request.transition (/var/task/node_modules/aws-sdk/lib/request.js:22:10)
at AcceptorStateMachine.runTo (/var/task/node_modules/aws-sdk/lib/state_machine.js:14:12)
at /var/task/node_modules/aws-sdk/lib/state_machine.js:26:10
at Request.<anonymous> (/var/task/node_modules/aws-sdk/lib/request.js:38:9)
at Request.<anonymous> (/var/task/node_modules/aws-sdk/lib/request.js:685:12)
at Request.callListeners (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:115:18)
message: 'User: arn:aws:sts::OBSCURED:assumed-role/chatroom-application-dev-us-east-1-lambdaRole/chatroom-application-dev-getRoomMessages is not authorized to perform: dynamodb:Query on resource: arn:aws:dynamodb:us-east-1:OBSCURED:table/messages-table-dev/index/roomIndex',
code: 'AccessDeniedException',
time: 2018-06-02T22:05:46.110Z,
requestId: 'OBSCURED',
statusCode: 400,
retryable: false,
retryDelay: 30.704899664776054 }
异常的顶部显示了我的 getRoomMessages 方法的 ARN is not authorized to perform: dynamodb:Query on resource:
并显示了全局二级索引的 ARN。
看来清楚了,我需要定义策略来授予访问全局二级索引的权限。但是完全不清楚如何。我已经看到其他有关 DynamoDB 的 Whosebug 问题抱怨文档零散,而且很难找到任何东西。我不得不同意。 "fragmented"这个词说得太委婉了。
我正在使用无服务器框架。 provider
部分显示了这个 policy/role 定义:
provider:
name: aws
runtime: nodejs8.10
stage: dev
region: us-east-1
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
- { "Fn::GetAtt": ["MessagesDynamoDBTable", "Arn" ] }
- { "Fn::GetAtt": ["#roomIndex", "Arn" ] }
- { "Fn::GetAtt": ["#userIndex", "Arn" ] }
environment:
MESSAGES_TABLE: ${self:custom.tableName}
在 Resource
部分,我想我应该列出声明了权限的资源。第一个引用 table 作为一个整体。最后两个我刚加的,参考了索引。
编辑:当我 运行 serverless deploy
打印以下消息时:
The CloudFormation template is invalid: Template error: instance of Fn::GetAtt references undefined resource #roomIndex
我对此尝试了几种变体,但都出现了同样的错误。这归结为 - 我如何在 serverless.yml
中使用 Cloudfront 语法获取索引 的 ARN。 ARN 确实存在,因为它显示在异常中。
DynamoDB table 定义:
resources:
Resources:
MessagesDynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: messageId
AttributeType: S
- AttributeName: room
AttributeType: S
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: messageId
KeyType: HASH
GlobalSecondaryIndexes:
- IndexName: roomIndex
KeySchema:
- AttributeName: room
KeyType: HASH
Projection:
ProjectionType: ALL
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
- IndexName: userIndex
KeySchema:
- AttributeName: userId
KeyType: HASH
Projection:
ProjectionType: ALL
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
TableName: ${self:custom.tableName}
与上述异常对应的正在使用的查询:
{
"TableName": "messages-table-dev",
"IndexName": "roomIndex",
"KeyConditionExpression": "#roomIndex = :room",
"ExpressionAttributeNames": {
"#roomIndex": "room"
},
"ExpressionAttributeValues": {
":room": {
"S": "everyone"
}
}
}
以及生成查询的 Lambda 函数代码片段:
app.get('/messages/room/:room', (req, res) => {
const params = {
TableName: MESSAGES_TABLE,
IndexName: "roomIndex",
KeyConditionExpression: '#roomIndex = :room',
ExpressionAttributeNames: { '#roomIndex': 'room' },
ExpressionAttributeValues: {
":room": { S: `${req.params.room}` }
},
};
console.log(`QUERY ROOM ${JSON.stringify(params)}`);
dynamoDb.query(params, (error, result) => {
if (error) {
console.log(error);
res.status(400).json({ error: 'Could not get messages' });
} else {
res.json(result.Items);
}
});
});
以下是我在无服务器中声明我的工作 GSI 权限的方式。我不确定,但也许问题是您需要为每个资源独立声明操作?
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:GetItem
- dynamodb:PutItem
Resource: "arn:REDACTED:table/TABLENAME”
- Effect: Allow
Action:
- dynamodb:Query
Resource: "arn:REDACTED:table/TABLENAME/index/INDEXNAME”
arn 的这种硬编码可能不是最好的做法,但到目前为止它对我的开发来说效果很好。
我找到了比 jake.lang 发布的更好的答案。编辑:我没有看到他提出以下建议的第二条评论。
正如他指出的那样,他是不正确的,因为 ARN 可以出于正当理由而更改。但是,出现了解决方案,因为全局二级索引的 ARN 是将“/INDEXNAME”附加到 table 的 ARN。这意味着政策声明可以是:
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
- { "Fn::GetAtt": ["MessagesDynamoDBTable", "Arn" ] }
- { "Fn::Join": [ "/", [
{ "Fn::GetAtt": ["MessagesDynamoDBTable", "Arn" ] }, "index", "roomIndex"
]]}
- { "Fn::Join": [ "/", [
{ "Fn::GetAtt": ["MessagesDynamoDBTable", "Arn" ] }, "index", "userIndex"
]]}
"Fn::Join" 位来自 CloudFormation,是一个 "join" 操作。它接受一个字符串数组,使用第一个参数连接它们。因此,计算此政策声明中所需的 ARN 是一种相当复杂且过于复杂的方法。
您可以使用!Sub "${MessagesDynamoDBTable.Arn}"
代替Fn::Join
,它更简单。此外,如果您想访问所有索引(通常是我的情况),那么 /index/*
就是您所需要的。
示例:
...
Policies:
- Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- dynamodb:Query
Resource:
- !Sub "${MessagesDynamoDBTable.Arn}"
- !Sub "${MessagesDynamoDBTable.Arn}/index/*"
...
我有一个 API/service 我正在用 DynamoDB table 定义。我有几个索引(定义为全局二级索引)来支持几个查询。我设计了 table,带有 GSI 定义,以及看起来正确的查询。但是,我在进行查询时遇到此异常:
{ AccessDeniedException: User: arn:aws:sts::OBSCURED:assumed-role/chatroom-application-dev-us-east-1-lambdaRole/chatroom-application-dev-getRoomMessages is not authorized to perform: dynamodb:Query on resource: arn:aws:dynamodb:us-east-1:OBSCURED:table/messages-table-dev/index/roomIndex
at Request.extractError (/var/task/node_modules/aws-sdk/lib/protocol/json.js:48:27)
at Request.callListeners (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:105:20)
at Request.emit (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:77:10)
at Request.emit (/var/task/node_modules/aws-sdk/lib/request.js:683:14)
at Request.transition (/var/task/node_modules/aws-sdk/lib/request.js:22:10)
at AcceptorStateMachine.runTo (/var/task/node_modules/aws-sdk/lib/state_machine.js:14:12)
at /var/task/node_modules/aws-sdk/lib/state_machine.js:26:10
at Request.<anonymous> (/var/task/node_modules/aws-sdk/lib/request.js:38:9)
at Request.<anonymous> (/var/task/node_modules/aws-sdk/lib/request.js:685:12)
at Request.callListeners (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:115:18)
message: 'User: arn:aws:sts::OBSCURED:assumed-role/chatroom-application-dev-us-east-1-lambdaRole/chatroom-application-dev-getRoomMessages is not authorized to perform: dynamodb:Query on resource: arn:aws:dynamodb:us-east-1:OBSCURED:table/messages-table-dev/index/roomIndex',
code: 'AccessDeniedException',
time: 2018-06-02T22:05:46.110Z,
requestId: 'OBSCURED',
statusCode: 400,
retryable: false,
retryDelay: 30.704899664776054 }
异常的顶部显示了我的 getRoomMessages 方法的 ARN is not authorized to perform: dynamodb:Query on resource:
并显示了全局二级索引的 ARN。
看来清楚了,我需要定义策略来授予访问全局二级索引的权限。但是完全不清楚如何。我已经看到其他有关 DynamoDB 的 Whosebug 问题抱怨文档零散,而且很难找到任何东西。我不得不同意。 "fragmented"这个词说得太委婉了。
我正在使用无服务器框架。 provider
部分显示了这个 policy/role 定义:
provider:
name: aws
runtime: nodejs8.10
stage: dev
region: us-east-1
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
- { "Fn::GetAtt": ["MessagesDynamoDBTable", "Arn" ] }
- { "Fn::GetAtt": ["#roomIndex", "Arn" ] }
- { "Fn::GetAtt": ["#userIndex", "Arn" ] }
environment:
MESSAGES_TABLE: ${self:custom.tableName}
在 Resource
部分,我想我应该列出声明了权限的资源。第一个引用 table 作为一个整体。最后两个我刚加的,参考了索引。
编辑:当我 运行 serverless deploy
打印以下消息时:
The CloudFormation template is invalid: Template error: instance of Fn::GetAtt references undefined resource #roomIndex
我对此尝试了几种变体,但都出现了同样的错误。这归结为 - 我如何在 serverless.yml
中使用 Cloudfront 语法获取索引 的 ARN。 ARN 确实存在,因为它显示在异常中。
DynamoDB table 定义:
resources:
Resources:
MessagesDynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: messageId
AttributeType: S
- AttributeName: room
AttributeType: S
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: messageId
KeyType: HASH
GlobalSecondaryIndexes:
- IndexName: roomIndex
KeySchema:
- AttributeName: room
KeyType: HASH
Projection:
ProjectionType: ALL
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
- IndexName: userIndex
KeySchema:
- AttributeName: userId
KeyType: HASH
Projection:
ProjectionType: ALL
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
TableName: ${self:custom.tableName}
与上述异常对应的正在使用的查询:
{
"TableName": "messages-table-dev",
"IndexName": "roomIndex",
"KeyConditionExpression": "#roomIndex = :room",
"ExpressionAttributeNames": {
"#roomIndex": "room"
},
"ExpressionAttributeValues": {
":room": {
"S": "everyone"
}
}
}
以及生成查询的 Lambda 函数代码片段:
app.get('/messages/room/:room', (req, res) => {
const params = {
TableName: MESSAGES_TABLE,
IndexName: "roomIndex",
KeyConditionExpression: '#roomIndex = :room',
ExpressionAttributeNames: { '#roomIndex': 'room' },
ExpressionAttributeValues: {
":room": { S: `${req.params.room}` }
},
};
console.log(`QUERY ROOM ${JSON.stringify(params)}`);
dynamoDb.query(params, (error, result) => {
if (error) {
console.log(error);
res.status(400).json({ error: 'Could not get messages' });
} else {
res.json(result.Items);
}
});
});
以下是我在无服务器中声明我的工作 GSI 权限的方式。我不确定,但也许问题是您需要为每个资源独立声明操作?
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:GetItem
- dynamodb:PutItem
Resource: "arn:REDACTED:table/TABLENAME”
- Effect: Allow
Action:
- dynamodb:Query
Resource: "arn:REDACTED:table/TABLENAME/index/INDEXNAME”
arn 的这种硬编码可能不是最好的做法,但到目前为止它对我的开发来说效果很好。
我找到了比 jake.lang 发布的更好的答案。编辑:我没有看到他提出以下建议的第二条评论。
正如他指出的那样,他是不正确的,因为 ARN 可以出于正当理由而更改。但是,出现了解决方案,因为全局二级索引的 ARN 是将“/INDEXNAME”附加到 table 的 ARN。这意味着政策声明可以是:
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
- { "Fn::GetAtt": ["MessagesDynamoDBTable", "Arn" ] }
- { "Fn::Join": [ "/", [
{ "Fn::GetAtt": ["MessagesDynamoDBTable", "Arn" ] }, "index", "roomIndex"
]]}
- { "Fn::Join": [ "/", [
{ "Fn::GetAtt": ["MessagesDynamoDBTable", "Arn" ] }, "index", "userIndex"
]]}
"Fn::Join" 位来自 CloudFormation,是一个 "join" 操作。它接受一个字符串数组,使用第一个参数连接它们。因此,计算此政策声明中所需的 ARN 是一种相当复杂且过于复杂的方法。
您可以使用!Sub "${MessagesDynamoDBTable.Arn}"
代替Fn::Join
,它更简单。此外,如果您想访问所有索引(通常是我的情况),那么 /index/*
就是您所需要的。
示例:
...
Policies:
- Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- dynamodb:Query
Resource:
- !Sub "${MessagesDynamoDBTable.Arn}"
- !Sub "${MessagesDynamoDBTable.Arn}/index/*"
...