【问题标题】:DynamoDB w/ Serverless, using Fn::GetRef to reference global secondary indexDynamoDB w/ Serverless,使用 Fn::GetRef 来引用全局二级索引
【发布时间】:2018-11-12 16:19:07
【问题描述】:

我有一个使用 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 方法 is not authorized to perform: dynamodb:Query on resource: 的 ARN,并显示了全局二级索引的 ARN。

似乎很清楚,我需要定义策略来授予访问全局二级索引的权限。但目前还不清楚如何。我已经看到有关 DynamoDB 的其他 StackOverflow 问题抱怨文档碎片化以及找到任何东西有多么困难。我不得不同意。 “碎片化”这个词太客气了。

我正在使用无服务器框架。 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}

Resource 部分,我相信我应该列出声明了权限的资源。第一个引用整个表。我刚刚添加的最后两个,并引用了索引。

编辑:当我运行 serverless deploy 时,会打印以下消息:

The CloudFormation template is invalid: Template error: instance of Fn::GetAtt references undefined resource #roomIndex

我对此尝试了几种变体,只是得到了同样的错误。这归结为 - 我如何在 serverless.yml 中使用 Cloudfront 语法获取索引的 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 函数代码 sn-p:

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);
        }
    });
});

【问题讨论】:

    标签: amazon-dynamodb serverless-framework dynamodb-queries aws-serverless


    【解决方案1】:

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

    正如他所指出的,他的说法不正确,因为 ARN 可以出于正当理由而更改。但是,出现了解决方案,因为全局二级索引的 ARN 是将“/INDEXNAME”附加到表的 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,是一个“加入”操作。它需要一个字符串数组,使用第一个参数将它们连接起来。因此,计算本政策声明中所需的 ARN 是一种相当复杂且过于复杂的方法。

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        【解决方案3】:

        以下是我在无服务器中声明我的工作 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
        • 你试过- { "Fn::Join" : [ { "Fn::GetAtt" :["MessagesDynamoDBTable", "Arn" ] }, "index/roomIndex"] } 吗?您可以获得 MessagesDynamoDBTable ARN 吗?那么 GSI ARN 就是那个字符串 + "index/roomIndex"
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-04-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-10-28
        • 1970-01-01
        相关资源
        最近更新 更多