我正在研究概念验证 (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
值?任何在无服务器框架项目背景下实现这一目标的指导或最佳实践将不胜感激。
如果我参考您的 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 转发到您的后端。检查这里。