如何修改CloudFormation模板处理S3代理API Gateway的403/404错误
问题:API Gateway代理S3时,请求不存在对象返回200而非404
我有一个CloudFormation模板,实现通过API Gateway直接获取S3对象。请求存在的对象时正常返回200和内容:
curl -i "https://xxx.execute-api.eu-west-1.amazonaws.com/prod/index.json"
返回结果:
HTTP/2 200 content-type: application/json ... {"hello": "world"}
但请求不存在的对象时,本该返回404 Not Found,却仍返回200 OK,同时返回S3的AccessDenied错误XML:
curl -i "https://xxx.execute-api.eu-west-1.amazonaws.com/prod/abcd.json"
返回结果:
HTTP/2 200 content-type: application/json content-length: 243 ... <?xml version="1.0" encoding="UTF-8"?> <Error> <Code>AccessDenied</Code> <Message>Access Denied</Message> <RequestId>CH2PRDQ2MVN0CXXX</RequestId> <HostId>cCV8dG+6CXA5pVrzTwLqiRqMjpuW8F+iuISQRUrKDP5PugEA6f4wauU0Egmb3b1GyvLjQZgjXXX=</HostId> </Error>
原CloudFormation模板如下:
Resources: S3Bucket: Type: AWS::S3::Bucket ApiGatewayRestApi: Type: AWS::ApiGateway::RestApi Properties: Name: S3ProxyAPI ApiGatewayResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: Ref: ApiGatewayRestApi ParentId: Fn::GetAtt: - ApiGatewayRestApi - RootResourceId PathPart: "{proxy+}" ApiGatewayMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE RestApiId: Ref: ApiGatewayRestApi ResourceId: Ref: ApiGatewayResource HttpMethod: GET RequestParameters: method.request.path.proxy: true MethodResponses: - StatusCode: 200 Integration: IntegrationHttpMethod: ANY Type: AWS Uri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:s3:path/${S3Bucket}/{proxy} Credentials: Fn::GetAtt: - ApiGatewayRole - Arn RequestParameters: integration.request.path.proxy: method.request.path.proxy PassthroughBehavior: WHEN_NO_MATCH IntegrationResponses: - StatusCode: 200 ApiGatewayDeployment: Type: AWS::ApiGateway::Deployment DependsOn: ApiGatewayMethod Properties: RestApiId: Ref: ApiGatewayRestApi StageName: prod ApiGatewayRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: apigateway.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: ApiGatewayS3ProxyPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject Resource: Fn::Sub: arn:aws:s3:::${S3Bucket}/* Outputs: ApiEndpoint: Value: Fn::Sub: https://${ApiGatewayRestApi}.execute-api.${AWS::Region}.amazonaws.com/prod/{proxy} BucketName: Value: Ref: S3Bucket
解决方案
需要做两处关键修改:
1. 扩展IAM权限,允许API Gateway执行s3:ListBucket操作
当前角色仅允许s3:GetObject,当对象不存在时,S3返回AccessDenied而非NotFound(因为无权限检查对象是否存在)。添加s3:ListBucket权限,让API Gateway能确认对象是否存在,此时S3会返回正确的NotFound错误。
修改ApiGatewayRole中的策略:
Policies: - PolicyName: ApiGatewayS3ProxyPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject Resource: Fn::Sub: arn:aws:s3:::${S3Bucket}/* - Effect: Allow Action: - s3:ListBucket Resource: Fn::Sub: arn:aws:s3:::${S3Bucket}
2. 在API Gateway中添加错误映射
需要在MethodResponses中添加404状态码,并在IntegrationResponses中配置将S3的NotFound错误映射到API Gateway的404响应,同时处理AccessDenied(如果仍出现)。
修改ApiGatewayMethod的MethodResponses:
MethodResponses: - StatusCode: 200 - StatusCode: 404
修改IntegrationResponses,添加错误映射:
IntegrationResponses: - StatusCode: 200 - StatusCode: 404 SelectionPattern: '^404$' ResponseParameters: method.response.header.Content-Type: "'application/json'" ResponseTemplates: application/json: '{"error": "Not Found"}' - StatusCode: 404 SelectionPattern: '^AccessDenied$' ResponseParameters: method.response.header.Content-Type: "'application/json'" ResponseTemplates: application/json: '{"error": "Not Found"}'
这里添加两个404映射:一个匹配S3返回的404状态码,另一个匹配AccessDenied(避免权限配置仍有问题时返回200),同时用响应模板把S3的XML错误转为JSON格式。
另外注意:将IntegrationHttpMethod从ANY改为GET,更符合当前仅处理GET请求的场景,避免不必要的权限风险。
修改后的完整CloudFormation模板
Resources: S3Bucket: Type: AWS::S3::Bucket ApiGatewayRestApi: Type: AWS::ApiGateway::RestApi Properties: Name: S3ProxyAPI ApiGatewayResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: Ref: ApiGatewayRestApi ParentId: Fn::GetAtt: - ApiGatewayRestApi - RootResourceId PathPart: "{proxy+}" ApiGatewayMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE RestApiId: Ref: ApiGatewayRestApi ResourceId: Ref: ApiGatewayResource HttpMethod: GET RequestParameters: method.request.path.proxy: true MethodResponses: - StatusCode: 200 - StatusCode: 404 Integration: IntegrationHttpMethod: GET Type: AWS Uri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:s3:path/${S3Bucket}/{proxy} Credentials: Fn::GetAtt: - ApiGatewayRole - Arn RequestParameters: integration.request.path.proxy: method.request.path.proxy PassthroughBehavior: WHEN_NO_MATCH IntegrationResponses: - StatusCode: 200 - StatusCode: 404 SelectionPattern: '^404$' ResponseParameters: method.response.header.Content-Type: "'application/json'" ResponseTemplates: application/json: '{"error": "Not Found"}' - StatusCode: 404 SelectionPattern: '^AccessDenied$' ResponseParameters: method.response.header.Content-Type: "'application/json'" ResponseTemplates: application/json: '{"error": "Not Found"}' ApiGatewayDeployment: Type: AWS::ApiGateway::Deployment DependsOn: ApiGatewayMethod Properties: RestApiId: Ref: ApiGatewayRestApi StageName: prod ApiGatewayRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: apigateway.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: ApiGatewayS3ProxyPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject Resource: Fn::Sub: arn:aws:s3:::${S3Bucket}/* - Effect: Allow Action: - s3:ListBucket Resource: Fn::Sub: arn:aws:s3:::${S3Bucket} Outputs: ApiEndpoint: Value: Fn::Sub: https://${ApiGatewayRestApi}.execute-api.${AWS::Region}.amazonaws.com/prod/{proxy} BucketName: Value: Ref: S3Bucket
内容的提问来源于stack exchange,提问作者Justin
相关产品推荐
相关产品推荐

