【发布时间】:2020-04-24 05:22:23
【问题描述】:
如何使用 CloudFormation 使用 AWS_PROXY 集成类型预置 AWS API 网关,以 CloudFront 为前端(以便我可以进行 HTTP 到 HTTPS 重定向)并由 AWS Lambda 函数支持?
下面是一个 CloudFormation 模板,展示了我尝试过的内容。它包括
- 一个简单的 Lambda 函数,它在
AWS_PROXY模式下为 Lambda 返回 expected output format。- 这已将
isBase64Encoded设置为 True。
- 这已将
- 一个
AWS::ApiGateway::RestApiCloudFormation 资源,其中包含一个BinaryMediaTypes属性,该属性的值是*~1*。- 在this AWS doc page 和the CloudFormation docs 中建议设置
*/*的二进制媒体类型,说明Slashes must be escaped with ~1. For example, image/png would be image~1png in the BinaryMediaTypes list
- 在this AWS doc page 和the CloudFormation docs 中建议设置
我已经阅读了这篇 AWS 论坛帖子 AWS_PROXY and binary responses,但还没有弄清楚我做错了什么。论坛帖子中包含有关AWS_PROXY 模式以及其他模式的帖子,因此有点令人困惑。
我相信,这个 AWS 文档页面 Support Binary Payloads in API Gateway 是在讨论 AWS_PROXY 以外的模式,因为它讨论了设置 IntegrationResponses 属性,这需要使用与 StatusCode 匹配的 StatusCode StatusCode。
这是一个展示问题的 CloudFormation 模板。您可以通过这些步骤重现它
- 为您帐户中现有 Route53 区域中的 DNS 域名提供 ACM 证书
- 将域名、区域名称(以“.”字符结尾)和 ACM ARN 作为参数提供给 CloudFormation 堆栈
- 使用下面的模板启动 CloudFormation 堆栈(因为它使用 CloudFront,这可能需要 30 分钟)
-
curlAPI 网关的 URL
如果这工作正常,你会得到一个二进制 png HTTP 响应,而不是你得到一个 base64 响应。
AWSTemplateFormatVersion: 2010-09-09
Description: Test binary responses with AWS_PROXY mode
Parameters:
CustomDomainName:
Type: String
Description: The custom domain name to use for the API
Default: ''
# AWS::ApiGateway::DomainName can not contain any uppercase characters
AllowedPattern: '^[^A-Z]*$'
ConstraintDescription: must not contain any uppercase characters
DomainNameZone:
Type: String
Description: The Route53 DNS zone containing the custom domain name
Default: ''
CertificateArn:
Type: String
Description: The ARN of the AWS ACM Certificate for your custom domain name
Default: ''
Resources:
TestFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: AllowLambdaLogging
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
TestFunction:
Type: AWS::Lambda::Function
Properties:
Description: Test Function
Code:
ZipFile: |
def lambda_handler(event, context):
body = 'iVBORw0KGgoAAAANSUhEUgAAABEAAAAKCAYAAABSfLWiAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAA0gAAANIBBp0MHQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHgSURBVCiRlY47aFNxGEfP97/p05SWYhXfEHMjNZobuChYk1iwUCKKiqSjj0XpIM46uDgUQdxqk0lUHJwsiEPtoEmtgxhMIx2StFJBhA4tOCTVPO7n0C5uesbDj8NPAEJO4oXCLqDHU3PbktYJhM/lwty07SRmEHlQKWRn7Uh8VlRvqDFpoEdgo7yQO+0DqP80V1ZW3v0KOcMxI95dMFOqnD8YGfoAckCUZMCNlWhKvxoGxaNWLuZGAQUQwNhOfEJFjhqPugo7u7RzZEN+50HvgO4R5KKKPkVlb9VXfbit5X+Cp2FBn5WLc/dNyBkeAkksFXJnWurdA6xi8U0VqIBc89R6q0hVPLmgtF7+yOdrlUI2ZdXb4hhzKRQ95frENL6qZ+2zo/FHqHQAA6RSlpZWp0WYWC5mF4NO4j3C1aWF+UXbiZ0VZKxFo4pitTcbywAE3JHeQDRhAxIOh9vZxITDw34A13Xbdrtu95Yn4Mb2HzoSjwSDyQ4A0SlOyjjz/Af6mE7q3AQGgW4D1DTDc01zWTP0/lPlG02ULxgmUfoEQCfx4+MWMI5SQvi0NVpDWcejC6EfsBGOA4cR0vh4RZNz8tfNzVgSYRTlGLADGADWge/AR4QZ+ngtY9Q1w3aus/YHPCW0c1bW92YAAAAASUVORK5CYII='
return {
'headers': {'Content-Type': 'image/png'},
'statusCode': 200,
'isBase64Encoded': True,
'body': body}
Handler: index.lambda_handler
Runtime: python3.7
Role: !GetAtt TestFunctionRole.Arn
Timeout: 900
TestFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
# Let's hope that the Lambda function doesn't execute before this LogGroup
# resource is created, creating the LogGroup with no expiration and
# preventing this resource from creating
LogGroupName: !Join [ '/', ['/aws/lambda', !Ref 'TestFunction' ] ]
RetentionInDays: 1
TestRoute53RecordSet:
Type: AWS::Route53::RecordSet
Properties:
AliasTarget:
DNSName: !GetAtt TestCloudFrontDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2 # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget-1.html
Comment: Bind the custom domain name to the Test CloudFront fronted API Gateway
HostedZoneName: !Ref DomainNameZone
Name: !Ref CustomDomainName
Type: A
TestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: Test
BinaryMediaTypes:
- '*~1*'
Description: Test API
FailOnWarnings: true
EndpointConfiguration:
Types:
- REGIONAL
TestApiGatewayDomainName:
# The ApiGateway requires a custom domain name, despite sitting behind
# CloudFront. This is because we want to pass all ( * ) HTTP headers
# through CloudFront and onto API Gateway. If we didn't set a custom domain
# name on the API Gateway, the "Host" header passed through from CloudFront
# to API Gateway would be for the custom domain, but API Gateway, which uses
# SNI, wouldn't know which TLS certificate to use in the handshake because
# API Gateway would have no record of that Host header. This would result in
# API Gateway being unable to setup a TLS connection with the inbound
# CloudFront connection attempt, API Gateway writing no logs about this
# fact, and CloudFront returning to the user an error of
# {"message":"Forbidden"}
# If we weren't passing the "Host" header from CloudFront to API Gateway
# this resource wouldn't be needed
Type: AWS::ApiGateway::DomainName
Properties:
# Uppercase letters are not supported in DomainName
DomainName: !Ref CustomDomainName
EndpointConfiguration:
Types:
- REGIONAL
RegionalCertificateArn: !Ref CertificateArn
SecurityPolicy: TLS_1_2
TestBasePathMapping:
Type: AWS::ApiGateway::BasePathMapping
Properties:
# BasePath: # Not specifying this so that we have no base path
DomainName: !Ref TestApiGatewayDomainName
RestApiId: !Ref TestApi
Stage: !Ref TestApiStage
TestLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:invokeFunction
FunctionName: !GetAtt TestFunction.Arn
Principal: apigateway.amazonaws.com
SourceArn: !Join [ '', [ 'arn:aws:execute-api:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref 'TestApi', '/*/*' ] ]
TestApiStage:
Type: AWS::ApiGateway::Stage
Properties:
DeploymentId: !Ref TestApiDeployment
MethodSettings:
- DataTraceEnabled: true
HttpMethod: '*'
ResourcePath: /*
RestApiId: !Ref TestApi
TestApiDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn:
- TestRequest
Properties:
RestApiId: !Ref TestApi
StageName: DummyStage
# Deployment with an Empty Embedded Stage
# The following instructional text is no longer present in the AWS
# documentation for AWS::ApiGateway::Deployment StageName and it's not
# clear if it still applies.
#
# "Note This property is required by API Gateway. We recommend that you
# specify a name using any value (see Examples) and that you don’t use
# this stage. We recommend not using this stage because it is tied to
# this deployment, which means you can’t delete one without deleting the
# other. For example, if you delete this deployment, API Gateway also
# deletes this stage, which you might want to keep. Instead, use the
# AWS::ApiGateway::Stage resource to create and associate a stage with
# this deployment."
TestResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref TestApi
ParentId: !GetAtt TestApi.RootResourceId
PathPart: '{proxy+}'
TestRequest:
DependsOn: TestLambdaPermission
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: GET
Integration:
Type: AWS_PROXY
# IntegrationHttpMethod is POST regardless of the HttpMethod for this resource
# https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# "For Lambda integrations, you must use the HTTP method of POST for the
# integration request, according to the specification of the Lambda service
# action for function invocations."
IntegrationHttpMethod: POST
Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
ResourceId: !Ref TestResource
RestApiId: !Ref TestApi
TestPOSTRequest:
DependsOn: TestLambdaPermission
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: POST
Integration:
Type: AWS_PROXY
# https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# "For Lambda integrations, you must use the HTTP method of POST for the
# integration request, according to the specification of the Lambda service
# action for function invocations."
IntegrationHttpMethod: POST
Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
ResourceId: !Ref TestResource
RestApiId: !Ref TestApi
TestRootRequest:
# This resource is necessary to get API Gateway to respond to requests for the '/' path
# Without it API Gateway will respond to requests for '/' with the error
# {"message":"Missing Authentication Token"}
# https://stackoverflow.com/q/46578615/168874
# https://stackoverflow.com/q/52909329/168874
DependsOn: TestLambdaPermission
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: GET
Integration:
Type: AWS_PROXY
# IntegrationHttpMethod is POST regardless of the HttpMethod for this resource
# https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# "For Lambda integrations, you must use the HTTP method of POST for the
# integration request, according to the specification of the Lambda service
# action for function invocations."
IntegrationHttpMethod: POST
Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
# ResourceId must use the RootResourceId attribute of the AWS::ApiGateway::RestApi
# https://stackoverflow.com/a/56121914/168874
ResourceId: !GetAtt TestApi.RootResourceId
RestApiId: !Ref TestApi
TestCloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Comment: !Join [ ':', [!Ref 'AWS::StackName', 'Test']]
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
- POST
- DELETE
- OPTIONS
- PUT
- PATCH
Compress: true
DefaultTTL: 0
MinTTL: 0
MaxTTL: 0
ForwardedValues:
Cookies:
Forward: all
QueryString: true
Headers:
- '*'
TargetOriginId: TestCloudFrontOriginId
ViewerProtocolPolicy: redirect-to-https
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-distributionconfig.html#cfn-cloudfront-distribution-distributionconfig-defaultrootobject
DefaultRootObject: '' # "If you don't want to specify a default root object when you create a distribution, include an empty DefaultRootObject element."
Enabled: true
Aliases:
- !Ref CustomDomainName
HttpVersion: http2
IPV6Enabled: true
#Logging:
# Logging
Origins:
- CustomOriginConfig:
OriginProtocolPolicy: https-only
OriginSSLProtocols:
- TLSv1.2
DomainName: !GetAtt TestApiGatewayDomainName.RegionalDomainName
Id: TestCloudFrontOriginId
# OriginPath: !Join [ '', [ '/', !Ref 'TestApiStage' ] ]
PriceClass: PriceClass_100 # US, Canada, Europe, Israel
ViewerCertificate:
AcmCertificateArn: !Ref CertificateArn
MinimumProtocolVersion: TLSv1.2_2018
SslSupportMethod: sni-only
【问题讨论】:
标签: amazon-web-services aws-lambda amazon-cloudformation aws-api-gateway amazon-cloudfront