Creating an API Gateway AWS Integration with S3 - amazon-web-services

I would like to set up API Gateway to retrieve files from a specific bucket on S3.
The GET method for the resource in the Cloudformation template looks like this:
GetMethod:
Type: "AWS::ApiGateway::Method"
Properties:
HttpMethod: GET
ApiKeyRequired: true
AuthorizationType: NONE
RequestParameters:
'method.request.path.listId': true
RestApiId:
Ref: ApiGatewayRestApi
ResourceId:
Ref: Lists
Integration:
Type: aws
Credentials:
Ref: S3ReadAccessRole
IntegrationHttpMethod: GET
PassthroughBehavior: WHEN_NO_MATCH
RequestParameters:
'integration.request.path.key': 'method.request.path.listId'
I am having trouble specifying the S3 URI to integrate with. I've done the following with no success:
The S3 URI: arn:aws:s3:::lists/groceries/{key}
The API Gateway based uri based on this article. arn:aws:apigateway:us-west-2:s3:lists/groceries/{key}
Please help me figure out the correct URI to allow objects on S3 to be retrieved using API Gateway directly.

Related

API Gateway Canned Response From GET Request

I'm looking for a simple way to provide a message in a GET response of an API Gateway. I have a template that has several lambda integrations on the backend that are configured for various purposes - including an authorizer that validates requests upon requesting. I am looking to implement an additional GET method who's only responsibility is to return a JSON formatted response. Rather than have another lambda do this simple response, I would like to generate the response in the CFT. I just can't figure out what exactly is needed. I tried out the following AWS::ApiGateway::Method with an AWS::ApiGateway::Model but I don't think I am doing it correctly. I defer to the wizards of stackOverflow!
GetMethodEndpoint:
Type: "AWS::ApiGateway::Method"
Properties:
AuthorizationType: "NONE"
OperationName: 'mock'
ResourceId: !Ref APIResource
RestApiId: !Ref apiGateway
HttpMethod: "GET"
MethodResponses:
- ResponseModels:
application/json: !Ref ApiGatewayModel
StatusCode: 200
Integration:
PassthroughBehavior: WHEN_NO_TEMPLATES
TimeoutInMillis: 29000
ConnectionType: 'INTERNET'
Type: "MOCK"
IntegrationResponses:
- StatusCode: 200
SelectionPattern: '2\d{2}'
ResponseTemplates:
application/json: "{\"message\":{\"support_info\": \"This is a canned response that I want returned when the GET request is made. It would be ideal for this to be nested json but...\}"
ApiGatewayModel:
Type: AWS::ApiGateway::Model
Properties:
ContentType: 'application/json'
RestApiId: !Ref apiGateway
Schema: {}
Defining API Gateway with Cloudformation is a nightmare.
Based on experience, it's easier to prepare API Gateway resources, methods, integrations, responses, transformations with GUI and then export API definition to OpenAPI format.
You can than provide OpenAPI file content as Body property of AWS::ApiGateway::RestApi resource, or as S3 object location with BodyS3Location property of AWS::ApiGateway::RestApi
I think Swager is also supported.

APIGateway Proxy resource doesn't get path parameter into Lambda

I need to create an API endpoint, which will trigger a Lambda function and return an image from an S3 bucket.
Example URL: https://abc.execute-api.eu-west-1.amazonaws.com/dev/xyz/00/01/23911414.jpeg
I created an APIGateway instance manually using the web console and it’s working fine.
And I created the same (I guess) using CloudFormation and it’s not working.
The Lambda gets triggered, but it doesn't get the path parameter in the event.
But I want the Lambda function to get /xyz/00/01/23911414.jpeg as the event['path'].
Here is a part of my CloudFormation Template:
RestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Description: Example API Gateway
EndpointConfiguration:
Types:
- REGIONAL
Name: imaginary-api
ProxyResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref RestApi
ParentId: !GetAtt
- RestApi
- RootResourceId
PathPart: '{proxy+}'
ProxyResourceANY:
Type: AWS::ApiGateway::Method
Properties:
RestApiId: !Ref RestApi
ResourceId: !Ref ProxyResource
HttpMethod: ANY
AuthorizationType: NONE
MethodResponses:
- StatusCode: 200
Integration:
Type: AWS
IntegrationHttpMethod: POST
Uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ImaginaryLambda.Arn}/invocations
Credentials: !GetAtt ApiGatewayIamRole.Arn
PassthroughBehavior: WHEN_NO_TEMPLATES
RequestTemplates:
"image/jpeg": ""
"image/jpg": ""
IntegrationResponses:
- StatusCode: 200
RestAPIDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn:
- ProxyResource
Properties:
RestApiId: !Ref RestApi
StageName: dev
ImaginaryInvoke:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt ImaginaryLambda.Arn
Principal: apigateway.amazonaws.com
SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:abc/dev
This is the first time I'm using APIGateway and I might have done something wrong here. Any help would be highly appreciated.
UPDATE:
Adding RequestParameters to the Method won't work either.
RequestParameters:
method.request.path.proxy: true
You have different possible integrations:
aws (requires data mapping)
aws_proxy
mock
http
http_proxy
The AWS integration type requires data mapping to get the required attributes (check https://docs.aws.amazon.com/apigateway/latest/developerguide/request-response-data-mappings.html).
You can use AWS_PROXY to get the complete event (check: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format) including the path attribute.
You can return binary file (be careful with content length) or return a s3 presigned url (thru a redirection for example).
Let me answer my own question.
I was able to fix this with the following values.
RestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Description: Example API Gateway
BinaryMediaTypes:
- "*/*" # <-- Important
EndpointConfiguration:
Types:
- REGIONAL
Name: imaginary-api
ProxyResourceANY:
Type: AWS::ApiGateway::Method
DependsOn:
ProxyResource
Properties:
RestApiId: !Ref RestApi
ResourceId: !Ref ProxyResource
HttpMethod: ANY
AuthorizationType: NONE
RequestParameters:
method.request.path.proxy: true # <-- Important
MethodResponses:
- StatusCode: 200
Integration:
Type: AWS_PROXY # <-- Important
IntegrationHttpMethod: POST
ContentHandling: CONVERT_TO_TEXT # <-- Important, depends on your code
PassthroughBehavior: WHEN_NO_MATCH
Uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ImaginaryLambda.Arn}/invocations
Credentials: !GetAtt ApiGatewayIamRole.Arn
Something strange I noticed is, even though the manually created one showed as AWS in the web console, it showed as AWS_PROXY when I exported the stage as a Swagger definition.
Comparing the two Swagger definitions helped me a lot comparing the YAMLs taken out from Export as Swagger + API Gateway Extensions option.

What is the difference between AWS and AWS_PROXY in CloudFormation::APIGateway?

What is the difference between AWS and AWS_PROXY in a CloudFormation template with a AWS::ApiGateway::Method - Integration:Type, with a Lambda backend? I was constantly getting 502 errors just now and realized that I needed to respond with a very specifically formatted JSON response. When I created API Gateways from the console I never bumped into this issue. It does work now but I want to know the underlying differences so I can learn.
Here's the part from the CF Template:
VisitorCounterAPIGatewayRootMethod:
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: GET
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY #THIS is my question. AWS or AWS_PROXY?
Uri: !Sub
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations
- lambdaArn: !GetAtt VisitorCountLambda.Arn
ResourceId: !GetAtt VisitorCounterAPIGateway.RootResourceId
RestApiId: !Ref VisitorCounterAPIGateway
And here's the response code from my Lambda function (Python3.7):
apiResponse = {
"isBase64Encoded": False,
"statusCode": 200,
"body": json.dumps({
"visitorCount": int(float(response["Attributes"]["amount"]))
})
}
Thank you.
AWS and AWS_PROXY are two types of integration in API Gateway.
AWS_PROXY is only used with lambda and it is the simplest way of working with lambda with API Gateway. It means:
For HTTP proxy integration, API Gateway passes the entire request and response between the frontend and an HTTP backend. For Lambda proxy integration, API Gateway sends the entire request as input to a backend Lambda function. API Gateway then transforms the Lambda function output to a frontend HTTP response.
AWS can be used with any AWS service, including lambda. It is more complex to setup and usually it is used when you want to integrate API gateway with, e.g., SQS or Kinesis or other AWS services:
This type of integration lets an API expose AWS service actions. In AWS integration, you must configure both the integration request and integration response and set up necessary data mappings from the method request to the integration request, and from the integration response to the method response.
So to answer your question:
Type: AWS_PROXY #THIS is my question. AWS or AWS_PROXY?
If you can, use AWS_PROXY. This is the most streamlined and easiest to work and setup form of integration with lambda. It only works with lambda anyway. Since entire response and reply are just pass-through the API gateway, you must ensure that your lambda returns correct headers and status code to the caller.
Use AWS with other services, such as SQS. You can still use lambda, but it requires more setup. But the benefit is that you have more control over what's get passed into your lambda, and how the response is passed to the caller. Usually you would use AWS with lambda if you have existing lambda and you can't change it. This way in AWS integration you can shape and mold the inputs and outputs from the caller to lambda and back, without changing the lambda.
Short example on AWS and AWS_PROXY integrations in CloudFormation, showing that when you use AWS integration, you can define extra parameters (myParam) that can be passed to your lambda:
AWS_PROXY
MyApiResourceMethod:
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: None
HttpMethod: GET
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
Uri: !Sub >-
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaArn1}/invocations
MethodResponses:
- ResponseModels: {"application/json": "Empty"}
StatusCode: 200
ResourceId: !Ref MyResource
RestApiId: !Ref MyRestApi
AWS
MyApiResourceMethod:
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: None
#AuthorizerId: String
HttpMethod: GET
Integration:
IntegrationHttpMethod: POST
- ResponseTemplates:
application/json: ""
StatusCode: 200
PassthroughBehavior: WHEN_NO_TEMPLATES
RequestTemplates:
application/json: |
{
"myParam": "$input.params('myParam')"
}
Type: AWS
Uri: !Sub >-
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaArn2}/invocations
MethodResponses:
- ResponseModels: {"application/json": "Empty"}
StatusCode: 200
ResourceId: !Ref MyResource
RestApiId: !Ref MyRestApi

API Gateway - Pass through proxy and AWS_IAM, not passing identity

I'm trying to create an API Gateway, which uses an AWS_IAM Authorizer, and using Amplify to sign in to my app using Federated Identities.
This all works fine, however I'm not getting an identity in my backend service.
What I want is to be able to access the identity of the user in my backend service. Eg a header with a user-id or something like that.
I've been looking at this example: https://github.com/matsev/cloudformation-api-gateway/blob/master/cloudformation.template to try to map the $context, however it seems it doesn't work with HTTP_PROXY?
RefreshProxy:
Type: AWS::ApiGateway::Resource
Properties:
ParentId:
Ref: SomeOtherHandler
PathPart: '{proxy+}'
RestApiId:
Ref: ApiGatewayRestApi
RefreshProxyMethod:
Type: AWS::ApiGateway::Method
Properties:
ResourceId:
Ref: RefreshProxy
RestApiId:
Ref: ApiGatewayRestApi
AuthorizationType: AWS_IAM
HttpMethod: POST
RequestParameters:
method.request.path.proxy: true
Integration:
IntegrationHttpMethod: POST
Type: HTTP_PROXY
Uri: url/{proxy}
IntegrationResponses:
- StatusCode: 200
RequestParameters:
integration.request.path.proxy: method.request.path.proxy
integration.request.header.Accept-Encoding: "'identity'"
PassthroughBehavior: WHEN_NO_MATCH
You need to add a header with the cognitoIdentityId from the context. So in the integration section you need:
integration.request.header.Identity: context.identity.cognitoIdentityId

AWS Api Gateway with Lambda integration only works via console

I have a CloudFormation template which sets up an API Gateway with a Lambda integration. When testing the integration via the AWS console it works.
When I attempt to use the HTTP method via Postman or Chrome, the response is null.
The full template can be seen here:
https://github.com/mattcanty/musicdown/blob/07b899d5924ab2fcc675cc521706b0c217e4e07e/cloudformation.yaml
I think something is not quite right here:
MusicdownsGet:
Type: AWS::ApiGateway::Method
Properties:
RestApiId: !Ref ApiGateway
ResourceId: !Ref MusicdownResource
HttpMethod: GET
RequestParameters:
method.request.querystring.filter: true
AuthorizationType: NONE
RequestModels:
application/json: !Ref MusicdownModel
Integration:
Type: AWS
IntegrationHttpMethod: POST
Uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Lambda.Arn}/invocations
IntegrationResponses:
- StatusCode: 200
RequestTemplates:
application/json: "{ \"filter\": \"$input.params('filter')\" }"
MethodResponses:
- StatusCode: 200