使用Fn::GetRef引用全局二级索引的DynamoDB Serverless

7

我正在定义一个与DynamoDB表相关的API/服务。我有一些索引(被定义为全局二级索引),以支持一些查询。我已经设计好了表格,包括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 未被授权执行 dynamodb:Query 操作的信息,并显示了全局二级索引的 ARN。
显然,需要定义策略以授予访问全局二级索引的权限。但是如何操作并不明显。我看到其他关于 DynamoDB 的 StackOverflow 问题抱怨文档过于零散,很难找到任何信息。我不得不同意。 "零散" 这个词用得太含蓄了。
我正在使用 Serverless Framework。下面是 provider 部分显示的策略/角色定义:
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}

在“资源”部分,我认为我应该列出声明权限的资源。第一个引用整个表格。我刚添加了最后两个,并引用索引。
编辑:当我运行“serverless deploy”时,会打印以下消息:
The CloudFormation template is invalid: Template error: instance of Fn::GetAtt references undefined resource #roomIndex

我尝试了几种不同的方式,但结果都是相同的错误。问题实质上是 - 在 serverless.yml 中,如何使用 Cloudfront 语法获取索引的 ARN。由于 ARN 在异常中显示,所以 ARN 是存在的。
DynamoDB 表定义:
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);
        }
    });
});
3个回答

16

我找到了比jake.lang发布的更好的答案。编辑:我没有看到他的第二条评论,在评论中他建议如下。

正如他所指出的那样,他的解答是错误的,因为ARN可能会因为合法的原因而发生变化。然而,解决方案出现在这里,因为全局辅助索引的ARN是将其附加到表的ARN后面使用"/INDEXNAME"。这意味着策略语句可以是:

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的一种“连接”操作。 它使用第一个参数将字符串数组连接起来。 因此,它是计算此策略语句中所需的ARN的一种相当复杂和过于复杂的方法。

有关文档,请参见:https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-join.html


11

你可以使用 !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/*"
...

1
这种方法对我来说效果最好。干净易读,同时具有对资源的动态引用。 - ChrisRich

1

以下是我在serverless中声明工作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的方式可能不是最好的选择,但在我的开发中已经很好地工作了。

在 serverless.yml 中硬编码 ARN 似乎不是正确的方法。ARN 可能因为合理的原因而发生更改,对吗?我尝试使用 { "Fn::GetAtt": ["MessagesDynamoDBTable/index/roomIndex", "Arn" ] } -- Fn::GetAtt 的好处是它可以动态检索值。但是我收到了这个错误:The CloudFormation template is invalid: Template error: instance of Fn::GetAtt references undefined resource MessagesDynamoDBTable/index/roomIndex - David Herron
你尝试过使用 - { "Fn::Join" : [ { "Fn::GetAtt" :["MessagesDynamoDBTable", "Arn" ] }, "index/roomIndex"] } 吗?你可以获取 MessagesDynamoDBTable 的 ARN,然后 GSI 的 ARN 就是该字符串加上 "index/roomIndex"。 - Jacob Lange

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接