如何使用无服务器框架进行细粒度访问控制,在 AWS Lambda 函数中使用 JWT 声明动态设置 dynamodb:LeadingKeys 条件

问题描述 投票:0回答:1

我正在研究概念验证 (POC),以使用

dynamodb:LeadingKeys
条件在 DynamoDB 中实施细粒度访问控制。我有一个 Node.js Lambda 函数,可以在 DynamoDB 上执行 CRUD 操作,该函数受 API Gateway 和 JWT 授权者的保护。

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/specifying-conditions.html

这是我的 NodeJS

index.js
Lambda 函数代码:

const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
const {
  DynamoDBDocumentClient,
  GetCommand,
  QueryCommand,
  PutCommand,
  DeleteCommand,
} = require("@aws-sdk/lib-dynamodb");
const express = require("express");
const serverless = require("serverless-http");
const crypto = require("crypto");

const app = express();
const USERS_TABLE = `${process.env.PROJECT_NAME}-${process.env.ENVIRONMENT}-user-data`;
const client = new DynamoDBClient();
const dynamoDbClient = DynamoDBDocumentClient.from(client);

app.use(express.json());

app.use((req, res, next) => {
  res.header("X-Custom-Header", "Custom header set");
  next();
});

// Get all notes for the authenticated user
app.get("/notes", async (req, res) => {
  const userId = req.apiGateway.event.requestContext.authorizer.jwt.claims.sub;
  try {
    const params = {
      TableName: USERS_TABLE,
      KeyConditionExpression: "userId = :userId",
      ExpressionAttributeValues: {
        ":userId": userId,
      },
    };
    const data = await dynamoDbClient.send(new QueryCommand(params));
    res.json({ data: data.Items });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Create a new note for the authenticated user
app.post("/notes", async (req, res) => {
  const userId = req.apiGateway.event.requestContext.authorizer.jwt.claims.sub;
  const noteId = crypto.randomUUID();
  const { data } = req.body;
  try {
    const params = {
      TableName: USERS_TABLE,
      Item: {
        userId,
        noteId,
        data,
        timestamp: new Date().toISOString(),
      },
    };
    await dynamoDbClient.send(new PutCommand(params));
    res.status(201).json({ data: { noteId, data, timestamp: params.Item.timestamp } });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

module.exports.handler = serverless(app);

这是我的

serverless.yml
配置:

service: dynamodb-test
frameworkVersion: "3"

plugins:
  - serverless-dotenv-plugin

custom:
  dotenv:
    path: .env
    required: true
    env:
      PROJECT_NAME: PROJECT_NAME
      ENVIRONMENT: ENVIRONMENT
    file: true

provider:
  name: aws
  runtime: nodejs20.x
  architecture: arm64
  profile: NONPRODPROFILE
  region: us-west-2
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - dynamodb:Query
            - dynamodb:Scan
            - dynamodb:GetItem
            - dynamodb:PutItem
            - dynamodb:UpdateItem
            - dynamodb:DeleteItem
          Resource:
            - arn:aws:dynamodb:us-west-2:014498888870:table/project-dev-user-data
          Condition:
            ForAnyValue:StringEqualsIgnoreCaseIfExists:
              dynamodb:LeadingKeys: "${event.requestContext.authorizer.jwt.claims.sub}"
        - Effect: Allow
          Action:
            - cloudwatch:*
          Resource: "*"
        - Effect: Allow
          Action:
            - logs:CreateLogGroup
            - logs:CreateLogStream
            - logs:PutLogEvents
          Resource:
            - "arn:aws:logs:*:*:*"
        - Effect: Allow
          Action:
            - lambda:InvokeFunction
          Resource: "*"
  httpApi:
    cors: true
    metrics: true
    payload: '2.0'
    authorizers:
      AwsNamedHttpApiEventAuthorizer:
        type: jwt
        identitySource: $request.header.Authorization
        issuerUrl: https://cognito-idp.us-west-2.amazonaws.com/us-west-2_ZZZZZZZ
        audience:
          - drhdtjgedyrsdtdshyyfdhf

functions:
  api:
    handler: index.handler
    events:
      - httpApi:
          path: /notes
          method: get
          authorizer: AwsNamedHttpApiEventAuthorizer
      - httpApi:
          path: /notes/{noteId}
          method: get
          authorizer: AwsNamedHttpApiEventAuthorizer
      - httpApi:
          path: /notes
          method: post
          authorizer: AwsNamedHttpApiEventAuthorizer
      - httpApi:
          path: /notes/{noteId}
          method: delete
          authorizer: AwsNamedHttpApiEventAuthorizer

我遇到的问题是 IAM 角色中存在以下情况:

Condition:
  ForAnyValue:StringEqualsIgnoreCaseIfExists:
    dynamodb:LeadingKeys: "${event.requestContext.authorizer.jwt.claims.sub}"

如果我注释掉这个条件,一切都会正常。如果我对 JWT 中的特定子值进行硬编码,则它对该用户来说效果很好。但是,我需要这个条件来动态地从 JWT 声明中提取子值,但似乎该值没有被正确替换。

如何动态地将

dynamodb:LeadingKeys
条件设置为 JWT 声明中的
sub
值?任何在无服务器框架项目背景下实现这一目标的指导或最佳实践将不胜感激。

node.js aws-lambda amazon-dynamodb aws-api-gateway serverless
1个回答
0
投票

如果我参考您的 serverless.yaml,使用的 Autorizer 是 Cognito 作为身份提供者,则用于在 Cognito 中检查用户身份的属性是

cognito-identity.amazonaws.com:sub
: 来自:https://docs.aws.amazon.com/cognito/latest/developerguide/iam-roles.html

cognito-identity.amazonaws.com:sub 通过 UUID 将角色限制为一个或多个用户。该UUID是用户在身份池中的身份ID。该值不是来自用户原始身份提供商的子值。 Amazon Cognito 在身份池令牌的子声明中指示此 UUID。

此子属性对于 Cognito 用户池是必需的,它的值是 UUID 128 位,并且对于用户池中的用户来说是唯一的: 来自:https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-terms.html#terms-uuid

通用唯一标识符(UUID) 应用于对象的 128 位标签。 Amazon Cognito UUID 对于每个用户池或身份池都是唯一的,但不符合特定的 UUID 格式。

如果您需要检查名称、电子邮件等其他值,您可以从以下位置检查标准属性:

https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html

所以策略应该看起来像这样,来自 AWS 帖子部分: DynamoDB 细粒度访问

{
"Version": "2012-10-17",
"Statement": [
    {
        "Effect": "Allow",
        "Action": [
            "dynamodb:GetItem",
            "dynamodb:BatchGetItem",
            "dynamodb:Query",
            "dynamodb:PutItem",
            "dynamodb:UpdateItem",
            "dynamodb:DeleteItem",
            "dynamodb:BatchWriteItem"
        ],
        "Resource": [
            "arn:aws:dynamodb:us-west-2:123456789012:table/MyTable"
        ],
        "Condition": {
            "ForAllValues:StringEquals": {
               "dynamodb:LeadingKeys":  ["${cognito-identity.amazonaws.com:sub}"]
            }
        }
    }
]
}

对于

event.requestContext.authorizer.jwt.claims.sub
,我认为如果身份验证成功并且不可用于动态生成和检查的 IAM 策略,则会从您的 apigateway 转发到您的后端。检查这里

© www.soinside.com 2019 - 2024. All rights reserved.