I've got an AWS SAM/CloudFormation template written in JSON which contains:
A definition of an API Gateway API, with endpoints defined using the
OpenAPI DefinitionBody format
A definition of an API Gateway Authorizer, defined using SAM/CF format
I'm trying to find a way to reference the Authorizer (2) for each of the endpoints (1) I wish to use the Authorizer for.
Here's the code:
Authorizer definition:
"LambdaAuthorizer":{
"Type": "AWS::ApiGateway::Authorizer",
"Properties":{
"IdentitySource":"method.request.header.Authorization",
"Type":"TOKEN",
"RestApiId":{
"Ref": "ApiName"
},
"AuthorizerUri": {
"Fn::Join" : ["", ["arn:aws:apigateway:", {"Ref": "AWS::Region"}, ":lambda:path/2015-03-31/functions/", {"Fn::GetAtt": ["AuthLambda", "Arn"]}, "/invocations"]]
},
"IdentityValidationExpression": "^[a-zA-Z0-9]{3,32}$",
"AuthorizerResultTtlInSeconds": 300,
"AuthorizerCredentials": {
"Fn::GetAtt": ["LambdaAuthorizerRole", "Arn"]
},
"Name":"lambda-authorizer"
}
},
Endpoint definition
"API": {
"Type": "AWS::Serverless::Api",
"Properties": {
...
"DefinitionBody": {
...
"paths": {
"/endpoint": {
"post": {
"responses": {
"200": {
"description": "200 response"
}
},
"x-amazon-apigateway-integration": {
"uri": {
"Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AuthLambda.Arn}/invocations"
},
"responses": {
"default": {
"statusCode": "200",
"contentHandling": "CONVERT_TO_TEXT"
}
},
"passthroughBehavior": "when_no_match",
"httpMethod": "POST",
"contentHandling": "CONVERT_TO_TEXT",
"type": "aws_proxy"
},
"security" : [{
"NAME OF OPEN API SECURITY DEFINITION":[] // Can I reference my existing Authorizer?
}]
}
},
}
}
}
}
As you can see I've found the OpenAPI security property, which according to the AWS documentation is used as a reference to a securityDefinitions where the Authorizer can be defined.
But I've already got my Authorizer defined in CloudFormation JSON. Can't I reference that instead?
API Gateway does not allow you to directly reference an existing Authorizer ID in the OpenAPI paths. However, there is a workaround. You can provide a securitySchemes definition. The format of securitySchemes will vary between OpenAPI 2.0 (aka Swagger) and OpenAPI 3.0 (documentation here).
Make sure that the details of the securitySchemes exactly match the existing API Authorizer. If everything matches, then API Gateway will use the existing Authorizer. If there are differences, then it will create a new Authorizer or overwrite the existing one. Ensure the following:
the security scheme object name is the same as the Authorizer name. In this case it is "lambda-authorizer"
type is "apiKey" for lambda authorizers
name is the HTTP header you're using for authorization (typically the "Authorization" header)
x-amazon-apigateway-authtype is "custom" for lambda authorizers
x-amazon-apigateway-authorizer has the correct type ("request" or "token"), the correct uri, ttl, and identity source
I've tested this with a regional REST API, so it may behave differently for other API Gateway types. The example below is a simplified version of the AWS PetStore example API.
{
"openapi" : "3.0.1",
"info" : {
"description" : "Your first API with Amazon API Gateway. This is a sample API that integrates via HTTP with our demo Pet Store endpoints",
"version" : "1",
"title" : "PetStore"
},
...
"paths" : {
"/pets" : {
"get" : {
...
"security" : [ {
"lambda-authorizer" : [ ]
} ],
"x-amazon-apigateway-integration" : {
...
}
}
}
},
"components" : {
"schemas" : {
...
},
"securitySchemes" : {
"lambda-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/${lambda_authorizer_arn}/invocations",
"authorizerResultTtlInSeconds" : 300,
"identitySource" : "method.request.header.Authorization"
}
}
}
}
}
In this example, you'd have to replace ${region} with your API Gateway region and ${lambdaauthorizer_arn} with the full ARN of your lambda function. The full string will look like this: arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:lambda-authorizer-function/invocations
TL;DR: If you want to use an existing Authorizer, you have to fully define it in the OpenAPI file.
Related
I am trying to put template in API Gate Way with next command:
aws apigateway put-rest-api --rest-api-id $APIGW --mode merge --body 'fileb://./api-gw-template.json'
Template is:
{
"swagger" : "2.0",
"basePath" : "/aws",
"schemes" : [ "https" ],
"paths" : {
"/{proxy+}" : {
"x-amazon-apigateway-any-method" : {
"parameters" : [ {
"name" : "proxy",
"in" : "path",
"required" : true,
"type" : "string"
} ],
"responses" : { },
"x-amazon-apigateway-integration" : {
"httpMethod" : "POST",
"uri" : "{{URI}}",
"responses" : {
"default" : {
"statusCode" : "200"
}
},
"passthroughBehavior" : "when_no_match",
"cacheNamespace" : "2wn7w2",
"cacheKeyParameters" : [ "method.request.path.proxy" ],
"contentHandling" : "CONVERT_TO_TEXT",
"type" : "aws_proxy"
}
}
}
},
"x-amazon-apigateway-binary-media-types" : [ "font/woff", "image/x-icon", "*/*" ]
}
This template creates a proxy resource with lambda integration, everything is fine here.
But in my lambda function, I don't see a new trigger for that resource.
But if I create the same resource manually, the trigger in the lambda is also created.
And if I export these two api gateway as swagger json template they are the same.
Also deploy api after import template doesn't help.
Here is output after import templat:
{
"id": "********",
"name": "Imported on 2022-09-14T12:09:17Z",
"createdDate": 1663149628,
"warnings": [
"Required \"info\" property is missing from the document root."
],
"binaryMediaTypes": [
"font/woff",
"image/x-icon",
"*/*"
],
"apiKeySource": "HEADER",
"endpointConfiguration": {
"types": [
"REGIONAL"
]
},
"policy": "{\\\"Version\\\":\\\"2012-10-17\\\",\\\"Statement\\\":[{\\\"Effect\\\":\\\"Allow\\\",\\\"Principal\\\":\\\"*\\\",\\\"Action\\\":\\\"execute-api:Invoke\\\",\\\"Resource\\\":\\\"arn:aws:execute-api:eu-central-1:*************:***********\\/*\\/*\\/*\\\"}]}",
"disableExecuteApiEndpoint": false
}
aws apigateway put-integration command also don't create trigger in lambda
Can someone help me? Where is my mistake?
I found the solution myself. The lambda function needs permission to be able to be called by the API gateway integration.
Solution in my case (if you are using a resource without a proxy - specify the resource name instead of the last asterisk in the "--source-arn" key):
aws lambda add-permission --function-name $lambda \
--statement-id apigateway-test-111111 --action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:$REGION_NAME:$ACCOUNT:$APIGW/*/*/*"
When manually creating a resource, this permission is automatically created
I have a .NET core 3.1 project with a GET endpoint. Locally the route works fine - "/api" GET request returns JSON string.
After publishing to my AWS Lambda function, and invoking the lambda via Postman, I get a 404 Not Found response to the "/api" GET request. Full URL: "https://(lambda domain)/default/MyLambda2/api"
In my project's Startup.cs ConfigureServices and Configure methods, I added "LambdaLogger.Log" statements. My log lines show up in CloudWatch (so I am definitely reaching the app). It's just after that, the route fails.
In ConfigureServices method, I have
services.AddControllers();
In Configure method, I have
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
And in the Controller file
namespace LambdaTest.Controllers
{
[Route("api")]
public class ValuesController : ControllerBase
{
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
}
I have an API Gateway trigger defined for the lambda, with
API endpoint: "https://(lambda domain)/default/MyLambda2/{proxy+}"
API type: REST
Authorization: NONE
Method: ANY
In Resources tab, I have...
/MyLambda2
ANY
/{proxy+}
ANY
OPTIONS
... and I have all Resources deployed to "default" stage (via Actions > Deploy API).
Here is the default auto-generated serverless.template file...
{
"AWSTemplateFormatVersion": "2010-09-09",
"Transform": "AWS::Serverless-2016-10-31",
"Description": "An AWS Serverless Application that uses the ASP.NET Core framework running in Amazon Lambda.",
"Parameters": {},
"Conditions": {},
"Resources": {
"AspNetCoreFunction": {
"Type": "AWS::Serverless::Function",
"Properties": {
"Handler": "LambdaTest::LambdaTest.LambdaEntryPoint::FunctionHandlerAsync",
"Runtime": "dotnetcore3.1",
"CodeUri": "",
"MemorySize": 256,
"Timeout": 30,
"Role": null,
"Policies": [
"AWSLambda_FullAccess"
],
"Events": {
"ProxyResource": {
"Type": "Api",
"Properties": {
"Path": "/{proxy+}",
"Method": "ANY"
}
},
"RootResource": {
"Type": "Api",
"Properties": {
"Path": "/",
"Method": "ANY"
}
}
}
}
}
},
"Outputs": {
"ApiURL": {
"Description": "API endpoint URL for Prod environment",
"Value": {
"Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
}
}
}
}
What could be causing the route to not get picked up in lambda?
Thanks
Found the issue. I needed my route on the controller to be
[Route("MyLambda2/api")]
I am creating an AWS API Gateway using terraform and openAPI specification with swagger. I need to add a request validator for validating the headers against a matching pattern [a-zA-z0-9]{10}. I was able to set up basic validator that checks if the header is empty or not but not able to validate with the pattern.
"x-amazon-apigateway-request-validators" : {
"full" : {
"validateRequestBody" : true,
"validateRequestParameters" : true,
"validateRequestHeaders" : true
},
"body-only" : {
"validateRequestBody" : true,
"validateRequestParameters" : false
}
},
"x-amazon-apigateway-request-validator" : "full",
"paths": {
"/validation": {
"get": {
"parameters": [
{
"in": "header",
"name": "x-request-id",
"required": true,
"type": "string",
"pattern" : "^[a-z0-9]{10}$"
},
{
"in": "query",
"name": "name",
"required": true,
"type": "string",
"pattern": "^[a-zA-Z]{5}$"
}
]
}
}
Please suggest if there is any way to achieve that
I can suggest a workaround for your requirement. You may use a "request based" Lambda authorizer and implement the validation logic within the Lambda function. If you need to validate just one header you may use 'token based' Lambda authorizer as well and specify a token validation regex.
Once the Lambda function determine whether the incoming headers are valid, it can grant access to the API.
You can check how to Configure a Lambda Authorizer Using the API Gateway Console.
instead of
"type": "string",
"pattern" : "^[a-z0-9]{10}$"
it should read
"schema": {
"type": "string",
"pattern" : "^[a-z0-9]{10}$"
}
see also here
I created the serverless Lambda application by using an AWS Toolkit for Visual Studio template (I used Tutorial: Build and Test a Serverless Application with AWS Lambda). I had selected 'Empty Serverless Project' and created simple lambda function linked to API Gateway.
The CloudFormation template looks like:
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Transform" : "AWS::Serverless-2016-10-31",
"Description" : "An AWS Serverless Application.",
"Resources" : {
"Get" : {
"Type" : "AWS::Serverless::Function",
"Properties": {
"Handler": "AWSServerless::AWSServerless.Functions::Get",
"Runtime": "dotnetcore2.0",
"CodeUri": "",
"MemorySize": 256,
"Timeout": 30,
"Role": null,
"Policies": [ "AWSLambdaBasicExecutionRole" ],
"Events": {
"PutResource": {
"Type": "Api",
"Properties": {
"Path": "/",
"Method": "GET"
}
}
}
}
}
},
"Outputs" : {
"ApiURL" : {
"Description" : "API endpoint URL for Prod environment",
"Value" : { "Fn::Sub" : "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" }
}
}
}
Now I need to secure my API Gateway with Access and Secret Keys. I have investigated a bit and if I am correct it should look like next:
"security":[{"sigv4":[]}]
But it still isn't clear to me where should I apply it? Possible that I am wrong and it could be done in another way. So my question is:
How to secure API Gateway with Access and Secret Keys in CloudFormation?
You can use API key or Authorizers
The following examples create a custom authorizer that is an AWS Lambda function.
"Authorizer": {
"Type": "AWS::ApiGateway::Authorizer",
"Properties": {
"AuthorizerCredentials": { "Fn::GetAtt": ["LambdaInvocationRole", "Arn"] },
"AuthorizerResultTtlInSeconds": "300",
"AuthorizerUri" : {"Fn::Join" : ["", [
"arn:aws:apigateway:",
{"Ref" : "AWS::Region"},
":lambda:path/2015-03-31/functions/",
{"Fn::GetAtt" : ["LambdaAuthorizer", "Arn"]}, "/invocations"
]]},
"Type": "TOKEN",
"IdentitySource": "method.request.header.Auth",
"Name": "DefaultAuthorizer",
"RestApiId": {
"Ref": "RestApi"
}
}
}
(Update)
SO thread on How to use authorizers in the template
Reference an Authorizer definition in an API Gateway path
I am trying to create a template so that when i call api/divide/inputvalue, The api sends back response from DynamoDB which corresponds to inputvalue mapping.
Its pretty straight forward since i am fetching value directly from db without any business logic hence I don't need any lambda. But all the examples that I google or all tutorials they are using lambdas and i am now lost that how can i make it working without lambda
This is what I have so far. There is bug in this template right now since I haven't provided Uri in ApiGateway::Method. Which is what I am currently stuck at.
{
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"Deployment": {
"Type": "AWS::ApiGateway::Deployment",
"Properties": {
"RestApiId": { "Ref": "restApiName" },
"Description": "First Deployment",
"StageName": "StagingStage"
},
"DependsOn" : ["restApiMethod"]
},
"restApiMethod": {
"Type": "AWS::ApiGateway::Method",
"Properties": {
"AuthorizationType": "NONE",
"HttpMethod": "GET",
"ResourceId": {"Ref": "apiRestResource"},
"RestApiId": {"Ref": "restApiName"},
"Integration": {
"Type": "AWS",
"IntegrationHttpMethod": "GET",
"IntegrationResponses": [{"StatusCode": 200}],
"Uri": { "Fn::Sub":"arn.aws.apigateway:${AWS::Region}:dynamodb:action/${restApiName.Arn}"}
},
"MethodResponses": [{"StatusCode": 200}]
},
"DependsOn": ["apiRestResource"]
},
"apiRestResource": {
"Type": "AWS::ApiGateway::Resource",
"Properties": {
"RestApiId": {"Ref": "restApiName"},
"ParentId": {
"Fn::GetAtt": ["restApiName","RootResourceId"]
},
"PathPart": "divide"
},
"DependsOn": ["restApiName"]
},
"restApiName": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"Name": "CalculationApi"
}
}
}
}
According to the documentation, the Uri property is structured as follows for AWS service-proxy integration types:
If you specify AWS for the Type property, specify an AWS service that follows the form: arn:aws:apigateway:region:subdomain.service|service:path|action/service_api. For example, a Lambda function URI follows the form: arn:aws:apigateway:region:lambda:path/path. The path is usually in the form /2015-03-31/functions/LambdaFunctionARN/invocations. For more information, see the uri property of the Integration resource in the Amazon API Gateway REST API Reference.
The uri API Gateway property reference provides more details:
For AWS integrations, the URI should be of the form arn:aws:apigateway:{region}:{subdomain.service|service}:{path|action}/{service_api}. Region, subdomain and service are used to determine the right endpoint. For AWS services that use the Action= query string parameter, service_api should be a valid action for the desired service. For RESTful AWS service APIs, path is used to indicate that the remaining substring in the URI should be treated as the path to the resource, including the initial /.
For an AWS service proxy to the dynamodb service calling the Query Action, the Uri should be something like this (using the YAML short-form of Fn::Sub to insert a Ref for the current AWS region):
!Sub "arn:aws:apigateway:${AWS::Region}:dynamodb:action/Query"
As for your broader use-case of using API Gateway to access DynamoDB without using Lambda functions, refer to Andrew Baird's tutorial blog post, "Using Amazon API Gateway as a Proxy for DynamoDB", and translate the specified Management Console steps to corresponding CloudFormation template resources.