使用Node.js的Lambda读取S3中CSV时遇Access Denied问题求助
问题排查:S3触发Lambda读取CSV时出现Access Denied错误
业务场景:CSV文件上传至AWS S3后,通过CloudTrail与EventBridge触发Lambda函数,Lambda需读取CSV并转换为JSON格式,但调用Lambda时返回Access Denied错误,无法读取S3中的文件。
相关代码与配置
App.js
const bucket= 'patterns-s3-eventbridge-src-bucket-new';//event['detail'].requestParameters.bucketName, const objectkey= 'test.csv' const params = { Bucket: bucket, Key: objectkey }; const results = []; exports.handler = function (event, ctx, callback) { try { const file = s3.getObject(params).createReadStream(); file .pipe(csv()) .on('data', function (data) { results.push(data); // --> here }) .on('end', () => { console.log(results); callback(null, results); }); } catch (err) { console.log(err); callback(Error(err)); } };
CloudFormation模板 temp.yaml
AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Description: S3-to-EventBridge Integration 1 - Simple Invoke Parameters: SourceBucketName: Type: String Default: patterns-s3-eventbridge-src-bucket-new LoggingBucketName: Type: String Default: patterns-s3-eventbridge-ct-logs-new Resources: # S3 Bucket - A source bucket and a logging bucket SourceBucket: Type: AWS::S3::Bucket Properties: BucketName: !Ref SourceBucketName LoggingBucket: Type: AWS::S3::Bucket Properties: BucketName: !Ref LoggingBucketName # Bucket policy enables CloudTrail to write to the logging bucket BucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref LoggingBucket PolicyDocument: Version: 2012-10-17 Statement: - Sid: AWSCloudTrailAclCheck Effect: Allow Principal: Service: cloudtrail.amazonaws.com Action: s3:GetBucketAcl Resource: !GetAtt LoggingBucket.Arn - Sid: AWSCloudTrailWrite Effect: Allow Principal: Service: cloudtrail.amazonaws.com Action: s3:PutObject Resource: !Sub ${LoggingBucket.Arn}/AWSLogs/${AWS::AccountId}/* Condition: StringEquals: s3:x-amz-acl: bucket-owner-full-control # The CloudTrail trail - uses the LoggingBucketName as the trail name myTrail: Type: AWS::CloudTrail::Trail DependsOn: BucketPolicy Properties: TrailName: !Ref LoggingBucketName S3BucketName: !Ref LoggingBucket IsLogging: true IsMultiRegionTrail: false EventSelectors: - IncludeManagementEvents: false DataResources: - Type: AWS::S3::Object Values: - !Sub ${SourceBucket.Arn}/ IncludeGlobalServiceEvents: false ### This section configures the consuming Lambda function ### # Lambda function EventConsumerFunction: Type: AWS::Serverless::Function Properties: CodeUri: eventConsumer/ Handler: app.handler Runtime: nodejs14.x # EventBridge rule - invokes EventConsumerFunction EventRule: Type: AWS::Events::Rule Properties: Description: EventRule State: ENABLED EventPattern: source: - aws.s3 detail: eventName: - PutObject requestParameters: bucketName: - !Ref SourceBucket Targets: - Arn: !GetAtt EventConsumerFunction.Arn Id: EventConsumerFunctionTarget PermissionForEventsToInvokeLambda: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref EventConsumerFunction Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt EventRule.Arn
错误日志
2022-11-20T11:10:04.221Z 3393a03e-c793-42a1-b4f2-eaee4365e6a8 ERROR Uncaught Exception { "errorType": "AccessDenied", "errorMessage": "Access Denied", "code": "AccessDenied", "message": "Access Denied", "region": null, "time": "2022-11-20T11:10:04.220Z", "requestId": "G9BZX6PSC8XZ5NVC", "extendedRequestId": "Wpet8ECVvRWEJu25NV8DYM7kYlKgGFCxhuCwg9tojnTd24LfFtBCzSdO953f3HBwmBWaVEDk4WQ=", "statusCode": 403, "retryable": false, "retryDelay": 72.92149207351702, "stack": [ "AccessDenied: Access Denied", " at Request.extractError (/var/task/node_modules/aws-sdk/lib/services/s3.js:711:35)", " at Request.callListeners (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:106:20)", " at Request.emit (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:78:10)", " at Request.emit (/var/task/node_modules/aws-sdk/lib/request.js:686: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:688:12)", " at Request.callListeners (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:116:18)" ] }
问题分析与修复方案
核心原因
Lambda函数的执行角色缺少读取目标S3桶对象的IAM权限,Lambda默认没有S3访问权限,这是触发403错误的直接原因。
修复步骤
- 给Lambda添加S3读取权限
在CloudFormation模板的EventConsumerFunction资源中,添加Policies配置,授予读取SourceBucket的权限:
EventConsumerFunction: Type: AWS::Serverless::Function Properties: CodeUri: eventConsumer/ Handler: app.handler Runtime: nodejs14.x # 添加以下权限配置 Policies: - S3ReadPolicy: BucketName: !Ref SourceBucketName
或者使用更精细化的自定义权限:
Policies: - Statement: - Effect: Allow Action: s3:GetObject Resource: !Sub ${SourceBucket.Arn}/*
- 优化App.js的硬编码问题
当前代码中桶名和对象键是固定值,建议从EventBridge传递的事件中动态获取,避免后续资源变更时出错:
const AWS = require('aws-sdk'); const s3 = new AWS.S3(); const csv = require('csv-parser'); // 确保已安装该依赖 const results = []; exports.handler = function (event, ctx, callback) { try { // 从EventBridge事件中动态获取桶名和对象键 const bucket = event.detail.requestParameters.bucketName; const objectkey = event.detail.requestParameters.key; const params = { Bucket: bucket, Key: objectkey }; const file = s3.getObject(params).createReadStream(); file .pipe(csv()) .on('data', function (data) { results.push(data); }) .on('end', () => { console.log(results); callback(null, results); }); } catch (err) { console.log(err); callback(Error(err)); } };
注意:需确保Lambda代码已安装aws-sdk和csv-parser依赖,部署时可包含node_modules或通过Lambda层添加依赖。
- 附加排查点
- 检查SourceBucket是否存在自定义桶策略,若有需确保允许Lambda执行角色的
s3:GetObject操作; - 确认CloudTrail已正常生成日志到LoggingBucket,可查看桶内是否有AWSLogs目录;
- 验证EventBridge规则的事件匹配逻辑,当前配置已正确匹配目标桶的
PutObject事件,无需修改。
内容的提问来源于stack exchange,提问作者Van
相关产品推荐
相关产品推荐

