Swagger definition for an AWS Api-Gateway Lambda Proxy endpoint - amazon-web-services

FYI - I've checked similar issues related to this, but none solves my problem.
I'm trying to create the Swagger definition for a number of APIs under AWS Api-Gateway. I'm able to successfully do this for other(POST, GET) endpoints from an auto-generated YAML configuration I downloaded from the API Stage.
But I encountered issues when I tried to do same for an Api-Gateway endpoint with Lambda Proxy Integration: Error from Swagger editor.swagger.io
Below is my YAML definition for the failing endpoint:
swagger: "2.0"
info:
version: "2018-04-18T17-09-07Z"
title: "XXX API"
host: "api.xxx.io"
schemes:
- "https"
parameters:
stage:
name: stage
in: path
type: string
enum: [ staging, production]
required: true
paths:
/env/{stage}/{proxy+}:
x-amazon-apigateway-any-method:
produces:
- "application/json"
parameters:
- $ref: '#/parameters/stage'
- name: "proxy"
in: "path"
required: true
type: "string"
responses: {}
x-amazon-apigateway-integration:
uri: "arn:aws:apigateway:eu-central-1:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-central-1:xxxxxxxxx:function:environment/invocations"
responses:
default:
statusCode: "200"
passthroughBehavior: "when_no_match"
httpMethod: "POST"
cacheNamespace: "4vbcjm"
cacheKeyParameters:
- "method.request.path.proxy"
contentHandling: "CONVERT_TO_TEXT"
type: "aws_proxy"
this is inline with AWS Documentation: enter link description here
Please, what am I missing?

At a glance I believe you have an error in your parameters block. If you include a $ref it discards anything in that block that follows it, so your proxy name is getting dropped. I have a similar setup with api-gateway proxying all calls to a lambda and this is my parameters block:
parameters:
- name: "proxy"
in: "path"
required: true
type: "string"
Additionally you may want an authorizer if you're at all worried about DDoS or serving up secure data. That's done by adding a security array as a sibling to parameters, and a securityDefinitions block as a sibling to paths
security:
- authorizer: []
securityDefinitions:
authorizer:
type : "apiKey"
name : "Authorization"
in : "header"
x-amazon-apigateway-authtype : "custom"
x-amazon-apigateway-authorizer : {
type : "request",
authorizerUri : "arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${region}:${account_id}:function:${authorizer_function_name}/invocations",
authorizerResultTtlInSeconds : 58,
identitySource: "method.request.header.authorization",
}
*note I'm publishing swagger as a terraform template, hence the ${} substitution.

Related

How to configure CORS for AWS API Gateway using OpenAPI?

I have an OpenAPI spec for an api which I am deploying via CDK. The spec looks like:
openapi: 3.0.1
info:
title: My API
description: My REST API with CORS enabled
version: 0.1.0
x-amazon-apigateway-cors:
allowOrigins:
- "*"
allowCredentials: true
exposeHeaders:
- "x-apigateway-header"
- "x-amz-date"
- "content-type"
maxAge: 3600
allowMethods:
- "*"
allowHeaders":
- "x-apigateway-header"
- "x-amz-date"
- "content-type"
- "Authorization"
components:
securitySchemes:
lambda:
type: "apiKey"
name: "Authorization"
in: "header"
x-amazon-apigateway-authtype: "custom"
x-amazon-apigateway-authorizer:
authorizerUri: "{{my-lambda-authorizer}}"
authorizerResultTtlInSeconds: 300
type: "token"
paths:
/user/{id}:
get:
summary: Get info of specified user.
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/User'
security:
- lambda: []
x-amazon-apigateway-integration:
uri: "{{my-lambda}}"
passthroughBehavior: "when_no_match"
httpMethod: "POST"
type: "aws_proxy"
When I try to access this via fetch(), I get an error Failed to load resource: Origin http://localhost:8000 is not allowed by Access-Control-Allow-Origin.
fetch('https://api.example.com/user/1')
.then(response => response.json())
.then((user: User) => {
// do something
})
.catch((err) => {
console.log("Error: " + err);
});
The API is accessible at api.example.com and I am running the website locally using Gatsby at localhost:8000.
The AWS docs seem to state that CORS is enabled when I put x-amazon-apigateway-cors at the root of the spec, but CORS doesn't seem to be enabled (or working) when I try to access the API. How do I enable CORS for my API without needing to configure it in the console?
Amazon API Gateway offers two types of APIs: REST APIs and HTTP APIs. REST APIs were the kind of APIs originally introduced with Amazon API Gateway, while HTTP APIs got announced at the end of 2019.
Based on the description of your OpenAPI specification I assume you're trying to deploy a REST API. The x-amazon-apigateway-cors OpenAPI extension however only works for HTTP APIs.
You have two choices now: You either switch to use a HTTP API or you configure CORS manually. As long as you don't need features only supported by REST APIs, I suggest you switch to use a HTTP API, as that's the more modern kind of API Amazon API Gateway offers.
If you want to use a REST API, enabling CORS requires more manual configuration. That's documented by AWS in Enabling CORS for a REST API resource. Essentially you have to ensure your integration returns proper CORS headers. For a NodeJS AWS Lambda function that could look like:
exports.handler = async (event) => {
const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Headers" : "Content-Type",
"Access-Control-Allow-Origin": "https://www.example.com",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET"
},
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
For CORS pre-flight requests to work you'd also have to ensure that OPTIONS-requests also return the correct headers. The easiest way to achieve that is to add a mock-integration for the OPTIONS-request to your OpenAPI specification. That would look like:
options:
responses:
'200':
description: Default response
headers:
Access-Control-Allow-Headers:
schema:
type: string
Access-Control-Allow-Methods:
schema:
type: string
Access-Control-Allow-Origin:
schema:
type: string
x-amazon-apigateway-integration:
type: mock
requestTemplates:
application/json: |
{"statusCode" : 200}
responses:
default:
statusCode: 200
responseParameters:
method.response.header.Access-Control-Allow-Headers: "'*'"
method.response.header.Access-Control-Allow-Methods: "'OPTIONS,POST'"
method.response.header.Access-Control-Allow-Origin: "'https://example.com/'"
Please also mind that modern browsers don't support localhost as origin for CORS, so you might need to work around that as well.

passing path parameter in google endpoints to backend not working

My setup contains google-endpoints with google-cloud-functions as my backend.
Google endpoints is defined with the following swagger v2 yaml:
swagger: "2.0"
info:
description: "yada..."
version: "0.0.1"
title: "yadada.."
termsOfService: "http://swagger.io/terms/"
contact:
name: "blah"
email: "email#mail.com"
url: "https://example.com"
host: "(generated service url by google when endpoints is deployed, i.e. 'api-gateway-xyz123123-ew.a.run.app')"
tags:
- name: "Documents"
description: "blah"
schemes:
- "https"
paths:
/api/documents:
post:
tags:
- "Documents"
summary: "Add a new document"
description: ""
security:
- firebase: []
operationId: "addDocument"
x-google-backend:
address: "(cloud functions http url)/documents"
consumes:
- "application/json"
produces:
- "application/json"
parameters:
- in: "body"
name: "body"
description: "Document supplied"
required: true
schema:
$ref: "#/definitions/Document"
responses:
201:
description: "The document was successfully created."
schema:
$ref: "#/definitions/Document"
400:
description: "Invalid input. See response for details"
schema:
items:
$ref: "#/definitions/Error"
/api/documents/{document_id}:
get:
tags:
- "Documents"
summary: "Get a document with the given ID"
description: ""
security:
- firebase: []
operationId: "getDocument"
x-google-backend:
address: "(cloud function http url)/documents/"
path_translation: APPEND_PATH_TO_ADDRESS
produces:
- "application/json"
parameters:
- in: "path"
name: "document_id"
description: "ID of the document to modify"
required: true
type: "string"
responses:
200:
description: "success."
schema:
type: "array"
items:
$ref: "#/definitions/Document"
404:
description: "Document not found"
schema:
items:
$ref: "#/definitions/Error"
securityDefinitions:
firebase:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "https://securetoken.google.com/%%GOOGLE_PROJECT_ID%%"
x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken#system.gserviceaccount.com"
x-google-audiences: "%%GOOGLE_PROJECT_ID%%"
definitions:
(a lot of type definitions)
This works with the POST endpoint without any problems.
The problem is with the GET REST endpoint where the path variable is not passed correctly to the backend.
As in https://cloud.google.com/endpoints/docs/openapi/openapi-extensions I tried to add the x-google-backend parameter as in the swagger api above. (path_translation: APPEND_PATH_TO_ADDRESS).
However this does not work.
I get an Unauthorized Error (403) as the cloud function is not hit by the endpoints frontend.
Currently I use an ugly workaround without the path_translation parameter which translates the google endpoints path variable to a query parameter in the cloud function backend with the same name. I.e. in the backend the url /documents?document_id=xyz is called.
(What I try to achieve is to pass the call with the backend url /documents/{document_id})
Does anyone know how to configure path based parameters correctly so that they are passed correctly to the cloud function backend?
Thank you in advance.
Regards,
Sebastian
TL;DR:
I assume that your 403 error isn't the correct error. It should be a 404, but because the endpoint is unknown, I guess that 403 is answered.
Cloud Endpoint is frustrating about this behavior. With the path_translation: APPEND_PATH_TO_ADDRESS, you think that your final called address will be /documents/{document_id}, but NO. The full openAPI path is append to your backend address, in your case: /documents/api/documents/{document_id}
That's why the endpoint doesn't exist and you should have a 404 (and not a 403).
For more details, you can have a look to this page.
Note: I'm in relation with Google team on this topic, and it will take time before having an update on this behavior.

AWS API Gateway and Lambda integration with resources on different Cloudformation stacks

I want to integrate a lambda function to an API gateway so that when I do a POST to the path defined on the API gateway the Lambda gets executed and returns a value.
Now, in my current project we currently have 2 Cloudformation templates:
- One that is generic and contains the definition of recourses common to all development environments
- And another non-generic one that deploys different resources depending on the environment passed as a paramater (dev, prod, etc).
In the non generic I have defined the lambda function, as well as the Deployment and Stage.
In the generic CF template I have defined the APIGateway as this:
Resources:
RecargakiApiGateway:
Type: AWS::ApiGateway::RestApi
Properties:
Body:
swagger: "2.0"
info:
version: "VersionTimestamp"
title: "Some API"
host: "some.host"
schemes:
- "https"
paths:
/payment:
post:
produces:
- "application/json"
responses:
"200":
description: "200 response"
schema:
$ref: "#/definitions/Empty"
x-amazon-apigateway-integration:
credentials:
Fn::ImportValue:
"lambda-credentials-outputvalue-in-another-generic-stack"
uri: !Sub
- "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda::${AWS::Region}:${AWS::AccountId}:function:${lambdaName}/invocations"
- lambdaName: "${stageVariables.lambda_name}"
responses:
default:
statusCode: "200"
passthroughBehavior: "when_no_match"
timeoutInMillis: 5000
httpMethod: "POST"
contentHandling: "CONVERT_TO_TEXT"
type: "aws_proxy"
definitions:
Empty:
type: "object"
title: "Empty Schema"
Description: "Desc"
Name: "Name"
EndpointConfiguration:
Types:
- REGIONAL
Now according to the docs on AWS proxy integration it is possible to construct the lambda uri like this:
arn:aws:apigateway:<region>:lambda:path/2015-03-31/functions/arn:aws:lambda::<account_id>:function:${stageVariables.<function_variable_name>}/invocations
I tried to construct the uri according to that format but I get this error:
Unable to put integration on 'POST' for resource at path '/payment': Invalid function ARN or invalid uri (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException
In all the examples I've seen, people assume the Lambda function to be integrated is located in the same CF template/stack as the API Gateway resource, but I have no idea how to reference the function's ARN and/or name in the Swagger definition of the API Gateway when the function is actually located in a different CF stack.
I've also tried referencing a stage variable that would contain the full URI like:
uri: "${stageVariables.functionURI}" # the URI would be constructed in the non generic stack in the format stated by the docs
But it fails with:
Unable to put integration on 'POST' for resource at path '/payment': Invalid ARN specified in the request (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException
Any help is greatly appreciated.

How to re-use my x-amazon-apigateway-integration definition throughout Swagger YAML document?

I am currently using Swagger to define an API with many end-points, and each one of those end-points all have the same definition for the 'x-amazon-apigateway-integration' key. I would like to define this somewhere in the document, and re-use that definition through-out.
Either I am not understanding how the definition should be defined, I am not placing it in the correct location or a mix of the two. I have tried defining this definition within 'definitions', and as some alias under it's own key. The definition (with key information removed) is:
x-amazon-apigateway-integration:
responses:
default:
statusCode: '200'
passthroughBehavior: when_no_match
httpMethod: POST
uri: >-
arn:aws:apigateway:<region>:lambda:path/2015-03-31/functions/<lambda arn>/invocations
credentials: '<role arn>'
type: aws
requestTemplates: "application/json": "<object definition>"
I have tried defining this as an alias under it's own key (not definitions, but the same base scope):
amazon:
Amazon: &Amazon
- responses:
default:
statusCode: '200'
- passthroughBehavior: when_no_match
- httpMethod: POST
- uri: >-
arn:aws:apigateway:<region>:lambda:path/2015-03-31/functions/<lambda arn>/invocations
- credentials: '<role arn>'
- type: aws
- requestTemplates:
"application/json": "<object definition>"
To use, I have the following:
x-amazon-apigateway-integration:
*Amazon
The error received on API Gateway import is 'Unable to parse API definition because of a malformed integration at path /'
I have also tried defining this under 'definitions', and using 'ref' to access it:
definitions:
Amazon:
type: object
x-amazon-apigateway-integration:
responses:
default:
statusCode: '200'
passthroughBehavior: when_no_match
httpMethod: POST
uri: >-
arn:aws:apigateway:<region>:lambda:path/2015-03-31/functions/<lambda arn>/invocations
credentials: '<role arn>'
type: aws
requestTemplates:
"application/json": "<object definition>"
To use, I have the following:
x-amazon-apigateway-integration:
$ref: '#/definitions/Amazon'
On import to API Gateway I receive the following error(s):
Your API was not imported due to errors in the Swagger file.
Unable to create model for 'Amazon': Invalid model specified: Validation Result: warnings : [], errors : [Invalid model schema specified. Unsupported keyword(s): ["x-amazon-apigateway-integration"]]
Additionally, these warnings were found:
Unknown integration type 'null' for 'POST /'. Ignoring.
Thank you in advance for your help.
Using YAML anchors seems like a good idea. The correct syntax is as follows.
Add the following on the root level of your OpenAPI file:
x-definitions: # <--- "x-" before "definitions" prevents it from being
# attempted to be parsed as an OpenAPI Schema object.
Amazon:
type: object
x-amazon-apigateway-integration: &Amazon # <--- "&Amazon" is the anchor
responses:
default:
statusCode: '200'
passthroughBehavior: when_no_match
httpMethod: POST
uri: >-
arn:aws:apigateway:<region>:lambda:path/2015-03-31/functions/<lambda arn>/invocations
credentials: '<role arn>'
type: aws
requestTemplates:
"application/json": "<object definition>"
Then you can refer to the anchor like this:
x-amazon-apigateway-integration: *Amazon
However, it might be that AWS parser does not support YAML anchors (&..., *...). In that case you can try pre-processing your definition using a parser that can resolve YAML anchors and then feed the resolved file to AWS.
As of 24th April 2020, it appears as though AWS API Gateway does not support referencing x-amazon-apigateway-integration components in OpenAPI v3 files, as trying to import the example from https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-extensions-integrations.html fails with
Your API was not imported due to errors in the Swagger file.
Unknown integration type 'null' for 'GET /'. Ignoring.
Unknown integration type 'null' for 'GET /pets'. Ignoring.
Unknown integration type 'null' for 'GET /checkout'. Ignoring.
It looks as though pre-processing the file is currently the only option to avoid repeated integration definitions

Use timestamp in AWS API Gateway in the Path Override of the Integration Request

I've been trying to create a method in API Gateway that drops the body of the incoming request into a file/object in an S3 bucket. But I want the method to create a new file each time (so a file with a new name) instead of overwriting the previous one. But I've been struggling to find a way to do it. Anyone has any suggestions/ideas? Something like a timestamp, or a sequence number (or both) to use as a variable in the Path override so that it would become the name of the s3 file. I looked at suggestions to use the X-Amzn-Trace-Id but it doesn't seem to be available in Path override. Anything else I could try? Maybe something in Swagger? I want to achieve it using API Gateway (avoid using a lambda as an extra step) to keep our architecture from getting too complex. Thanks in advance!
You can set the X-Amzn-Trace-Id as the method parameter, then map the parameter to the integration parameter on the path to S3 as the object name .
Example:
---
swagger: "2.0"
info:
version: "2017-12-04T23:03:26Z"
title: "API"
host: "xxxx.execute-api.us-east-1.amazonaws.com"
basePath: "/dev"
schemes:
- "https"
paths:
/:
post:
produces:
- "application/json"
parameters:
- name: "X-Amzn-Trace-Id"
in: "header"
required: false
type: "string"
responses:
200:
description: "200 response"
schema:
$ref: "#/definitions/Empty"
x-amazon-apigateway-integration:
credentials: "arn:aws:iam::178779171625:role/api-gate-way"
responses:
default:
statusCode: "200"
requestParameters:
integration.request.path.traceid: "method.request.header.X-Amzn-Trace-Id"
uri: "arn:aws:apigateway:us-east-1:bucketName.s3:path/{traceid}"
passthroughBehavior: "when_no_match"
httpMethod: "PUT"
type: "aws"
definitions:
Empty:
type: "object"
title: "Empty Schema"