just like in the title. I can deploy stuff on AWS using only Cloud Formation. Now I try to secure my API Gateway with API Keys and looks like I need a Usage Plan for it. It doesn't seem to be covered by the documentation right here: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html
Have any of you had a similar problem and if yes, how did you solved it?
AWS has today released the ability to create AWS::ApiGateway::UsagePlan using cloud formation templates
Now AWS::ApiGateway::UsagePlanKey can be created using CloudFormation.
This snippet demonstrates how you might use a UsagePlan and UsagePlanKey, along with an APIKey in a CloudFormation Template
UsagePlan:
Type: 'AWS::ApiGateway::UsagePlan'
Properties:
ApiStages:
- ApiId: !Ref MyRestApi
Stage: !Ref Prod
Description: Customer ABCs usage plan
Quota:
Limit: 5000
Period: MONTH
Throttle:
BurstLimit: 200
RateLimit: 100
UsagePlanName: Plan_ABC
ApiKey:
Type: 'AWS::ApiGateway::ApiKey'
Properties:
Name: TestApiKey
Description: CloudFormation API Key V1
Enabled: 'true'
UsagePlanKey:
Type: 'AWS::ApiGateway::UsagePlanKey'
Properties:
KeyId: !Ref ApiKey
KeyType: API_KEY
UsagePlanId: !Ref UsagePlan
For anyone reading, this is now supported via the AWS::ApiGateway::UsagePlanKey (docs) resource type in CloudFormation. From that page:
The AWS::ApiGateway::UsagePlanKey resource associates an Amazon API Gateway API key with an API Gateway usage plan. This association determines which users the usage plan is applied to.
AWS has provided the CloudFormation template for the API Keys creation with the UsagePlan and the UsagePlan Keys as well, so probably the CFT for the same would be defined as:
ApiKey:
Type: 'AWS::ApiGateway::ApiKey'
DependsOn:
- ApiGatewayDeployment
Properties:
Name: !Sub "you keyName-Apikeys"
Description: Api Keys Description
Enabled: 'true'
StageKeys:
- RestApiId: !Ref ApiGatewayRestApi
StageName: !Sub "your stageName"
usagePlan:
Type: 'AWS::ApiGateway::UsagePlan'
DependsOn:
- ApiGatewayDeployment
Properties:
ApiStages:
- ApiId: !Ref ApiGatewayRestApi
Stage: !Sub "your stageName"
Description: your description of usage plan
Quota:
Limit: 50000
Period: DAY
Throttle:
BurstLimit: 200
RateLimit: 100
UsagePlanName: !Sub "define your name of UsagePlan"
usagePlanKey:
Type: 'AWS::ApiGateway::UsagePlanKey'
DependsOn:
- ApiGatewayDeployment
Properties:
KeyId: !Ref ApiKey
KeyType: API_KEY
UsagePlanId: !Ref usagePlan
I hope this will probably help you.
Related
I have created an Api usage plan and linked it to an api gateway. is it possible to create multiple usage plans and attach it to same api. if yes, will i need to create different api key for each usage plan?
UsagePlan:
Type: 'AWS::ApiGateway::UsagePlan'
Properties:
ApiStages:
- ApiId: !Ref MyRestApi
Stage: !Ref Prod
Description: Customer ABCs usage plan
Quota:
Limit: 5000
Period: MONTH
Throttle:
BurstLimit: 200
RateLimit: 100
UsagePlanName: Plan_ABC
ApiKey:
Type: 'AWS::ApiGateway::ApiKey'
Properties:
Name: TestApiKey
Description: CloudFormation API Key V1
Enabled: 'true'
UsagePlanKey:
Type: 'AWS::ApiGateway::UsagePlanKey'
Properties:
KeyId: !Ref ApiKey
KeyType: API_KEY
UsagePlanId: !Ref UsagePlan
I'm deploying a serverless application using the AWS SAM Cli and template, but the API Gateway resource is returning a 403 ForbiddenException error when trying to curl / postman it. Tried looking online, but haven't been able to find any answers that solved my issue and wondering if anyone here has experienced this before.
template.yaml:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Runtime: nodejs10.x
MemorySize: 256
Api:
Cors:
AllowMethods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"
AllowHeaders: "'Content-Type,X-Amz-Date,X-Amz-Security-Token,Authorization,X-Api-Key,X-Requested-With,Accept,Access-Control-Allow-Methods,Access-Control-Allow-Origin,Access-Control-Allow-Headers'"
AllowOrigin: "'*'"
Parameters:
ApiKey:
Type: String
Default: none
Conditions:
CreateApiKey: !Not [!Equals [!Ref ApiKey, 'none']]
Resources:
# DynamoDB table setup
DyanmoDBStoryTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: Stories
AttributeDefinitions:
- AttributeName: short_id
AttributeType: S
KeySchema:
- AttributeName: short_id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 0
WriteCapacityUnits: 0
BillingMode: PAY_PER_REQUEST
# Log group
DynamoSaveStoryLogGroup:
Type: AWS::Logs::LogGroup
DependsOn: [DynamoSaveStoryLambda]
Properties:
RetentionInDays: 30
LogGroupName: !Sub '/aws/lambda/${DynamoSaveStoryLambda}'
DynamoGetStoryLogGroup:
Type: AWS::Logs::LogGroup
DependsOn: [DynamoGetStoryLambda]
Properties:
RetentionInDays: 30
LogGroupName: !Sub '/aws/lambda/${DynamoGetStoryLambda}'
DynamoUpdateStoryLogGroup:
Type: AWS::Logs::LogGroup
DependsOn: [DynamoUpdateStoryLambda]
Properties:
RetentionInDays: 30
LogGroupName: !Sub '/aws/lambda/${DynamoUpdateStoryLambda}'
# Lambda Fn
DynamoSaveStoryLambda:
Type: AWS::Serverless::Function
Properties:
Policies:
- AmazonDynamoDBFullAccess
Handler: src/lambdas/save-story.handler
Timeout: 10
Events:
SaveStory:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /story
Method: post
DynamoGetStoryLambda:
Type: AWS::Serverless::Function
Properties:
Policies:
- AmazonDynamoDBFullAccess
Handler: src/lambdas/get-story.handler
Timeout: 10
Events:
SaveStory:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /story/{shortId}
Method: get
DynamoUpdateStoryLambda:
Type: AWS::Serverless::Function
Properties:
Policies:
- AmazonDynamoDBFullAccess
Handler: src/lambdas/update-story.handler
Timeout: 10
Events:
SaveStory:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /story/{shortId}
Method: post
# Custom API gateway setup API Keys & usage plans
ApiGateway:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Auth:
ApiKeyRequired: true
UsagePlan:
Type: AWS::ApiGateway::UsagePlan
DependsOn: [ApiGatewayProdStage]
Condition: CreateApiKey
Properties:
ApiStages:
- ApiId: !Ref ApiGateway
Stage: Prod
DynamoLambdasApiKey:
Type: AWS::ApiGateway::ApiKey
DependsOn: [UsagePlan]
Condition: CreateApiKey
Properties:
Value: !Ref ApiKey
Enabled: true
StageKeys:
- RestApiId: !Ref ApiGateway
StageName: Prod
UsagePlanKey:
Type: AWS::ApiGateway::UsagePlanKey
Condition: CreateApiKey
Properties:
KeyId: !Ref DynamoLambdasApiKey
KeyType: API_KEY
UsagePlanId: !Ref UsagePlan
Outputs:
StoryApi:
Description: Serverless api url generated by AWS Cloudformation upon stack deployment
Value: !Sub 'https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod'
ApiKey:
Description: Api key to authorize access in API Gateway
Value: !Ref ApiKey
SAM CLI Version: 0.47.0
Error:
Date →Sun, 26 Apr 2020 19:22:02 GMT
Content-Type →application/json
Content-Length →23
Connection →keep-alive
x-amzn-RequestId →01d6b9ec-dcf0-484c-be07-6b629437b305
x-amzn-ErrorType →ForbiddenException
x-amz-apigw-id →Lm_WOF9ZvHcF7nQ=
Testing it directly from the AWS Lambda console works correctly and cloudwatch logs are generated, but not when I curl/postman the request with the API url that is generated during deployment. I have tried the following:
Ensuring the x-api-key header is set correctly and verifying that the API Gateway in AWS console is set with the correct API Key
Configuring CORS in the API in globals of template. Confirming It creates the options endpoints in API Gateway console
Double checking the endpoints are correct
The error states that it is a cloudfront issue so I've confirmed that the S3 bucket has public access. There are no other cloudfront resources in the AWS console. I'm at a loss as to what is blocking the request.
Answer was simpler than I thought, but for anyone else that comes across this issue, the query parameter is case sensitive. The output url from the serverless application model deployment returns https://${serverlessAppId}.execute-api.${region}.amazonaws.com/${StageName}.
In my case, the StageName was Prod and I was making requests as prod
I am learning AWS SAM and having trouble finding information on how to create an API Key and usage plan through the SAM template.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
GetFamilyByIdFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs8.10
Handler: get-family-by-id.handler
CodeUri: get-family-by-id/
Timeout: 300
Events:
GetFamilyByIdApi:
Type: Api
Properties:
Path: "/family/{id}"
Method: get
I would like to create an API key and associate it with a usage plan for the 'GetFamilyByIdApi' specified above. Any help would be appreciated.
After a bit of digging I figured it out. This solution is when you want to define the value of the api key yourself as opposed to letting API Gateway generate a value. I assume a variation exists for that. Note that 'HvgnApi' refers to my Swagger definition (Type: AWS::Serverless::Api). Enjoy!
ApiKey:
Type: AWS::ApiGateway::ApiKey
Properties:
Name: !Join ["", [{"Ref": "AWS::StackName"}, "-apikey"]]
Description: "CloudFormation API Key V1"
Enabled: true
GenerateDistinctId: false
Value: abcdefg123456
StageKeys:
- RestApiId: !Ref HvgnApi
StageName: prod
ApiUsagePlan:
Type: "AWS::ApiGateway::UsagePlan"
Properties:
ApiStages:
- ApiId: !Ref HvgnApi
Stage: prod
Description: !Join [" ", [{"Ref": "AWS::StackName"}, "usage plan"]]
Quota:
Limit: 1000
Period: MONTH
UsagePlanName: !Join ["", [{"Ref": "AWS::StackName"}, "-usage-plan"]]
ApiUsagePlanKey:
Type: "AWS::ApiGateway::UsagePlanKey"
DependsOn:
- HvgnApi
Properties:
KeyId: !Ref ApiKey
KeyType: API_KEY
UsagePlanId: !Ref ApiUsagePlan
This is much easier to do with the latest version of SAM:
MyAPI:
Type: AWS::Serverless::Api
Properties:
StageName: dev
Auth:
ApiKeyRequired: true # for all methods
UsagePlan:
CreateUsagePlan: PER_API
Description: Usage plan for this API
I have the following Cloudformation template I am trying to deploy via SAM. This template correctly creates the DynamoDB table, an API Key, a Lambda function and the API Gateway, but I cannot figure out what I need to specify in the template to associate the API KEY with the API Gateway.
I have found plenty of snippets showing partial examples, but I am struggling to piece it all together.
Thank you in advance,
Denny
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Parameters:
TableName:
Type: String
Default: 'influencetabletest'
Description: (Required) The name of the new DynamoDB table Minimum 3 characters
MinLength: 3
MaxLength: 50
AllowedPattern: ^[A-Za-z-]+$
ConstraintDescription: 'Required parameter. Must be characters only. No numbers allowed.'
CorsOrigin:
Type: String
Default: '*'
Description: (Optional) Cross-origin resource sharing (CORS) Origin. You can specify a single origin, all "*" or leave empty and no CORS will be applied.
MaxLength: 250
Conditions:
IsCorsDefined: !Not [!Equals [!Ref CorsOrigin, '']]
Resources:
ApiKey:
Type: AWS::ApiGateway::ApiKey
DependsOn:
- ApiGetter
Properties:
Name: "TestApiKey"
Description: "CloudFormation API Key V1"
Enabled: "true"
ApiGetter:
Type: AWS::Serverless::Api
Properties:
StageName: prd
DefinitionBody:
swagger: 2.0
info:
title:
Ref: AWS::StackName
paths:
/getdynamicprice:
post:
responses: {}
x-amazon-apigateway-integration:
httpMethod: POST
type: aws_proxy
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaGetter.Arn}/invocations
LambdaGetter:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./index.js
Handler: index.handler
Runtime: nodejs8.10
Environment:
Variables:
TABLE_NAME: !Ref TableName
IS_CORS: IsCorsDefined
CORS_ORIGIN: !Ref CorsOrigin
PRIMARY_KEY: !Sub ${TableName}Id
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref TableName
Events:
Api:
Type: Api
Properties:
Path: /getdynamicprice
Method: POST
RestApiId: !Ref ApiGetter
DynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Ref TableName
AttributeDefinitions:
-
AttributeName: !Sub "${TableName}Id"
AttributeType: "S"
KeySchema:
-
AttributeName: !Sub "${TableName}Id"
KeyType: "HASH"
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
StreamSpecification:
StreamViewType: NEW_AND_OLD_IMAGES
Outputs:
ApiKeyID:
Value: !Ref ApiKey
ApiUrl:
Value: !Sub https://${ApiGetter}.execute-api.${AWS::Region}.amazonaws.com/prod/getdynamicprice
Description: The URL of the API Gateway you invoke to get your dynamic pricing result.
DynamoDBTableArn:
Value: !GetAtt DynamoDBTable.Arn
Description: The ARN of your DynamoDB Table
DynamoDBTableStreamArn:
Value: !GetAtt DynamoDBTable.StreamArn
Description: The ARN of your DynamoDB Table Stream
Edit (04/22/2020): there now seems to do all this using AWS SAM. Please see answer below
Here's a sample template where I have connected my API to a API key. But that's only been possible because I am using usage plans. I believe that is the primary purpose of an API key. API gateway usage plan
ApiKey:
Type: AWS::ApiGateway::ApiKey
Properties:
Name: !Join ["", [{"Ref": "AWS::StackName"}, "-apikey"]]
Description: "CloudFormation API Key V1"
Enabled: true
GenerateDistinctId: false
ApiUsagePlan:
Type: "AWS::ApiGateway::UsagePlan"
Properties:
ApiStages:
- ApiId: !Ref <API resource name>
Stage: !Ref <stage resource name>
Description: !Join [" ", [{"Ref": "AWS::StackName"}, "usage plan"]]
Quota:
Limit: 2000
Period: MONTH
Throttle:
BurstLimit: 10
RateLimit: 10
UsagePlanName: !Join ["", [{"Ref": "AWS::StackName"}, "-usage-plan"]]
ApiUsagePlanKey:
Type: "AWS::ApiGateway::UsagePlanKey"
Properties:
KeyId: !Ref <API key>
KeyType: API_KEY
UsagePlanId: !Ref ApiUsagePlan
There does not seem to be a way to do this without a usage plan.
I did try the suggestion from ASR but ended up with a simpler approach.
The AWS SAM (Serverless Application Model) contains prepackaged handling that doesn't necessitate the use of resources of the ApiGateway type.
To create an API Gateway with a stage that requires an authorization token in the header the following simplified code should do it for you :
Resources:
ApiGatewayEndpoint:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Auth:
ApiKeyRequired: true
UsagePlan:
CreateUsagePlan: PER_API
UsagePlanName: GatewayAuthorization [any name you see fit]
LambdaFunction:
Type: AWS::Serverless::Function
Properties:
Handler: lambda.handler
Runtime: python3.7
Timeout: 30
CodeUri: .
Events:
PostEvent:
Type: Api
Properties:
Path: /content
Method: POST
RequestParameters:
- method.request.header.Authorization:
Required: true
Caching: true
RestApiId:
Ref: ApiGatewayEndpoint [The logical name of your gateway endpoint above]
The elements:
Auth:
ApiKeyRequired: true
UsagePlan:
CreateUsagePlan: PER_API
is what does the trick.
Cloudformation handles the plumbing for you, ie. the Api Key, UsagePlan and UsagePlanKey is automatically created and binded.
Although the docs are definitely not best in class they do provide some additional information: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-resources-and-properties.html
I create CloudFormation template for my AWS API Gateway and Lambda function and I need to connect specific API Gateway stage to specific Lambda alias. I have two aliases - QA and Prod, and two API stages (QA & Prod too), in CloudFormation template it looks like:
AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Lambda function configuration
Resources:
EndpointLambda:
Type: "AWS::Lambda::Function"
Properties:
FunctionName: "endpoint-lambda"
Handler: "com.test.aws.RequestHandler::handleRequest"
Runtime: java8
Code:
S3Bucket: "lambda-functions"
S3Key: "test-endpoint-lambda-0.0.1.jar"
Description: Test Lambda function
MemorySize: 256
Timeout: 60
Environment:
Variables:
ES_HOST: test-es-host-url
ES_ON: true
ES_PORT: 443
ES_PROTOCOL: https
REDIS_URL: test-redis-host-url
QaLambdaAlias:
Type: "AWS::Lambda::Alias"
Properties:
FunctionName: !Ref EndpointLambda
FunctionVersion: 1
Name: "QA"
Description: "QA alias"
ProdLambdaAlias:
Type: "AWS::Lambda::Alias"
Properties:
FunctionName: !Ref EndpointLambda
FunctionVersion: 1
Name: "Prod"
Description: "Production alias"
RestApi:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "test-rest-api"
Description: "Test REST API"
RestApiResource:
Type: "AWS::ApiGateway::Resource"
Properties:
RestApiId: !Ref "RestApi"
ParentId: !GetAtt "RestApi.RootResourceId"
PathPart: "/test"
RestApiDeployment:
Type: "AWS::ApiGateway::Deployment"
Properties:
RestApiId: !Ref "RestApi"
QaRestApiStage:
Type: "AWS::ApiGateway::Stage"
Properties:
DeploymentId: !Ref "RestApiDeployment"
RestApiId: !Ref "RestApi"
StageName: "qa"
ProdRestApiStage:
Type: "AWS::ApiGateway::Stage"
Properties:
DeploymentId: !Ref "RestApiDeployment"
RestApiId: !Ref "RestApi"
StageName: "prod"
How can I describe in template that QA API stage should call QA alias of Lambda function, and Prod stage - Prod alias?
To begin with find out how to do it using the GUI. There some documentation about what you want to do here. Theres some extra permissions you'll need to add aswell if this is the first time you've set this up which are included here -
https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html
But for a quick answer what your looking for is $:{stageVariables.stage} what this does is links the alias of the lambda you want to trigger. In the GUI it'd look something like this:
What this will do is allow your lambda to trigger a certain alias. Once this is entered you'll be able to see a new option when using the Testing feature in the API gateway. So here you'd specify QA.
So, to reflect this in Cloudformation we need to do something similar -
RestApi:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "test-rest-api"
Description: "Test REST API"
paths:
/ExamplePath:
put:
#Here will go all the configuration setting you want
#Such as security, httpMethod, amd passthroughBehavior
#But what you need is
uri: 'arn:aws:apigateway:${AWS:Region}:lambda:path/2-15/03/31/functions/${LambdaARN}:${!stageVariables.stage}/invocations'
More info on this can be found here:
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-method-integration.html what you'll want to see is at the very bottom of the page.
Hope this helps!