API Gateway CreateAuthorizer fails with InternalFailure - amazon-web-services

When I try to create custom authorizerer for my AWS API Gateway using CloudFormation, it freezes trying all the time to execute CreateAuthorizer call, but fails. Here is the minimum CloudFormation template with which I can reproduce that behavior:
AWSTemplateFormatVersion: "2010-09-09"
Resources:
ApiGatewayV1:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "test"
ApiAuthorizerV1:
Type: "AWS::ApiGateway::Authorizer"
Properties:
RestApiId: !Ref "ApiGatewayV1"
Name: "test"
Type: "TOKEN"
AuthorizerUri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda::${AWS::AccountId}:function:${!stageVariables.AuthorizerLambdaName}/invocations"
IdentitySource: "method.request.header.Authorization"
AuthorizerResultTtlInSeconds: 3600
CloudTrail log:
"errorCode": "InternalFailure",
"errorMessage": "An unknown error occurred",
"requestParameters": {
"restApiId": "lweme6j3wk",
"createAuthorizerInput": {
"providerARNs": [],
"identitySource": "method.request.header.Authorization",
"authorizerResultTtlInSeconds": 3600,
"type": "TOKEN",
"name": "test",
"authorizerUri": "arn:aws:apigateway:eu-central-1:lambda:path/2015-03-31/functions/arn:aws:lambda::<ACCOUNT_ID>:function:${stageVariables.AuthorizerLambdaName}/invocations"
},
"template": false
},
"responseElements": null,
"requestID": "470e2efa-d3c1-11e7-b0cc-b7fd2383ef6b",
"eventID": "2ceccaa5-9b97-4b1e-93e5-3c4e6bca419d",

Ok, that was supper bizzare. When I explicitely specified region in the target lambda ARN it worked!
Replaced:
arn:aws:lambda::<ACCOUNT_ID>
With:
arn:aws:lambda:<REGION>:<ACCOUNT_ID>
(and yes, it works with ${AWS::Region}:${AWS::AccountId} placeholder, I used fixed values to check.

Related

How to ensure using the latest lambda layer version when deploying with CloudFormation and SAM?

we use CloudFormation and SAM to deploy our Lambda (Node.js) functions. All our Lambda functions has a layer set through Globals. When we make breaking changes in the layer code we get errors during deployment because new Lambda functions are rolled out to production with old layer and after a few seconds (~40 seconds in our case) it starts using the new layer. For example, let's say we add a new class to the layer and we import it in the function code then we get an error that says NewClass is not found for a few seconds during deployment (this happens because new function code still uses old layer which doesn't have NewClass).
Is it possible to ensure new lambda function is always rolled out with the latest layer version?
Example CloudFormation template:
Globals:
Function:
Runtime: nodejs14.x
Layers:
- !Ref CoreLayer
Resources:
CoreLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: core-layer
ContentUri: packages/coreLayer/dist
CompatibleRuntimes:
- nodejs14.x
Metadata:
BuildMethod: nodejs14.x
ExampleFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: example-function
CodeUri: packages/exampleFunction/dist
Example CloudFormation deployment events, as you can see new layer (CoreLayer123abc456) is created before updating the Lambda function so it should be available to use in the new function code but for some reasons Lambda is updated and deployed with old layer version for a few seconds:
Timestamp
Logical ID
Status
Status reason
2022-05-23 16:26:54
stack-name
UPDATE_COMPLETE
-
2022-05-23 16:26:54
CoreLayer789def456
DELETE_SKIPPED
-
2022-05-23 16:26:53
v3uat-farthing
UPDATE_COMPLETE_CLEANUP_IN_PROGRESS
-
2022-05-23 16:26:44
ExampleFunction
UPDATE_COMPLETE
-
2022-05-23 16:25:58
ExampleFunction
UPDATE_IN_PROGRESS
-
2022-05-23 16:25:53
CoreLayer123abc456
CREATE_COMPLETE
-
2022-05-23 16:25:53
CoreLayer123abc456
CREATE_IN_PROGRESS
Resource creation Initiated
2022-05-23 16:25:50
CoreLayer123abc456
CREATE_IN_PROGRESS -
2022-05-23 16:25:41
stack-name
UPDATE_IN_PROGRESS
User Initiated
Example changeset:
{
"resourceChange": {
"logicalResourceId": "ExampleFunction",
"action": "Modify",
"physicalResourceId": "example-function",
"resourceType": "AWS::Lambda::Function",
"replacement": "False",
"moduleInfo": null,
"details": [
{
"target": {
"name": "Environment",
"requiresRecreation": "Never",
"attribute": "Properties"
},
"causingEntity": "ApplicationVersion",
"evaluation": "Static",
"changeSource": "ParameterReference"
},
{
"target": {
"name": "Layers",
"requiresRecreation": "Never",
"attribute": "Properties"
},
"causingEntity": null,
"evaluation": "Dynamic",
"changeSource": "DirectModification"
},
{
"target": {
"name": "Environment",
"requiresRecreation": "Never",
"attribute": "Properties"
},
"causingEntity": null,
"evaluation": "Dynamic",
"changeSource": "DirectModification"
},
{
"target": {
"name": "Code",
"requiresRecreation": "Never",
"attribute": "Properties"
},
"causingEntity": null,
"evaluation": "Static",
"changeSource": "DirectModification"
},
{
"target": {
"name": "Layers",
"requiresRecreation": "Never",
"attribute": "Properties"
},
"causingEntity": "CoreLayer123abc456",
"evaluation": "Static",
"changeSource": "ResourceReference"
}
],
"changeSetId": null,
"scope": [
"Properties"
]
},
"hookInvocationCount": null,
"type": "Resource"
}
I didn't understand why it has 2 target.name: Layers items in details array. One of them has causingEntity: CoreLayer123abc456 which is expected due to newly created layer and the other has causingEntity: null, not sure why this is there.
Originally posted on AWS re:Post here
Edit:
After a couple of tests it turns out that the issue is caused by the order of the changes from changeset. Looks like changes are applied one by one. For example for the following changeset it updates the old function code while still using the old layer and then updates the function layer with the latest version because Layers change item comes after Code change item.
{
"resourceChange":{
"logicalResourceId":"ExampleFunction",
"action":"Modify",
"physicalResourceId":"example-function",
"resourceType":"AWS::Lambda::Function",
"replacement":"False",
"moduleInfo":null,
"details":[
{
"target":{
"name":"Layers",
"requiresRecreation":"Never",
"attribute":"Properties"
},
"causingEntity":null,
"evaluation":"Dynamic",
"changeSource":"DirectModification"
},
{
"target":{
"name":"Code",
"requiresRecreation":"Never",
"attribute":"Properties"
},
"causingEntity":null,
"evaluation":"Static",
"changeSource":"DirectModification"
},
{
"target":{
"name":"Layers",
"requiresRecreation":"Never",
"attribute":"Properties"
},
"causingEntity":"CoreLayer123abc456",
"evaluation":"Static",
"changeSource":"ResourceReference"
}
],
"changeSetId":null,
"scope":[
"Properties"
]
},
"hookInvocationCount":null,
"type":"Resource"
}
But in some deployments the order is the other way around, such as:
{
"resourceChange":{
...
"details":[
...
{
"target":{
"name":"Layers",
"requiresRecreation":"Never",
"attribute":"Properties"
},
"causingEntity":"CoreLayer123abc456",
"evaluation":"Static",
"changeSource":"ResourceReference"
},
{
"target":{
"name":"Code",
"requiresRecreation":"Never",
"attribute":"Properties"
},
"causingEntity":null,
"evaluation":"Static",
"changeSource":"DirectModification"
}
],
...
}
In this case it updates the old function with the latest layer version and then updates the function code with the updated one. So for a couple of seconds old code is invoked with the latest layer version.
So does it possible to apply all these changes in only one single step? Similar to Atomicity in databases
I solved this by using Lambda versions as suggested here. For each deployment I create a new Lambda version, so old and new Lambda use the correct core layer.
Reposting my comment from the original post for reference:
I added a lambda version:
ExampleFunctionVersion:
Type: AWS::Lambda::Version
DeletionPolicy: Delete
Properties:
FunctionName: !Ref ExampleFunction
and replaced all function references with !Ref ExampleFunctionVersion.
I also tried AutoPublishAlias: live, it worked too but I had to add an AWS::Lambda::Version resource to be able to set DeletionPolicy: Delete

Invalid service prefix for action 'sts.AssumeRole' [duplicate]

I've been all over the web searching for an answer to this.
Essentially, we're spinning up an API using Swagger, which is awesome and works great, but one thing doesn't work... When we make a call to an Endpoint, we get a 500 error (it's not a 500 error that we're providing either it's one from AWS). The error states "Execution failed due to configuration error: Invalid permissions on Lambda function" (https://youtu.be/H4LM_jw5zzs <- This is a video, from another user, of the error I'm getting).
I've gone down many ratholes, and have found an answer... It involves using the AWS CLI and looks a bit like this:
aws lambda add-permission \
--function-name FUNCTION_NAME \
--statement-id STATEMENT_ID \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:us-east-1:ACCOUNT_ID:API_ID/*/METHOD/ENDPOINT"
This is great and all, but we are using CloudFormation to spin up everything and we want this to be automated. Is there an easier way to go about this? Is there something in CloudFormation that will give us the resource policy that we need?
I'm hitting a bit of a wall with this, but I've been working on it for a few hours today and it's a bit of a blocker for our API release, so any help would be much appreciated. :)
There is a CloudFormation solution to this problem. See the following CloudFormation snippet:
"Permission": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"FunctionName": { "Fn::GetAtt": [ "Lambda", "Arn" ] },
"Action": "lambda:InvokeFunction",
"Principal": "apigateway.amazonaws.com",
"SourceArn": { "Fn::Join": [ "", [
"arn:aws:execute-api:",
{ "Ref": "AWS::Region" }, ":",
{ "Ref": "AWS::AccountId" }, ":",
{ "Ref": "API" },
"/*/*/*"
] ] }
}
}
This grants API Gateway permissions to launch your Lambda function. Variables in this snippet you need to change are Lambda (line 4) and API (line 11).
For the invoke permissions:
"APIInvokePermission": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"FunctionName": {
"Ref": "YOUR_LAMBDA_FUNCTION_RESOURCE_NAME"
},
"Action": "lambda:InvokeFunction",
"Principal": "apigateway.amazonaws.com",
"SourceArn": {
"Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${YOUR_REST_API_RESOURCE_NAME}/*/*/*"
}
}
},
Thanks https://twitter.com/edjgeek for helping me get this straight in my head.
This GIST shows how to use AWS::Serverless:Function with Events to automatically generate the needed AWS::Lambda::Permission to allow APIGateway (for a given route) to invoke your Lambda:
https://gist.github.com/rainabba/68df1567cbd0c4930d428c8953dc2316
Both of the following approaches assume an api such as (many fields omitted for readability):
MyApi:
Type: 'AWS::Serverless::Api'
Properties:
DefinitionBody:
Fn::Transform:
Name: AWS::Include
Parameters:
Location: openapi.yaml
Using "SAM Events"
The most relevant bit (I've omitted many required fields):
MyLambdaFunction:
Type: 'AWS::Serverless::Function'
Properties:
Events:
MyRouteEventToProxy:
Type: Api
Properties:
Method: POST
Path: '/some-route/{pathParm}'
RestApiId: !Ref MyApi # ResourceName of AWS::Serverless::Api
Auth:
Authorizer: NONE
Using "openapi binding"
If you'd rather declare the binding in the openapi.yaml, then see the following project (no Lambda/Events required). This approach requires an explict role to allow invoke.
template.yaml relevant bits:
MyLambdaFunction:
Type: 'AWS::Serverless::Function'
Properties:
# Events: # No need for Events when binding from openapi.yaml
MyHttpApiRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: "apigateway.amazonaws.com"
Action:
- "sts:AssumeRole"
openapi.yaml relevant bits:
paths:
post:
x-amazon-apigateway-integration:
httpMethod: POST
uri:
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaFunction.Arn}:live/invocations"
contentHandling: "CONVERT_TO_TEXT"
type: aws_proxy
credentials:
Fn::GetAtt: [MyHttpApiRole, Arn]
https://github.com/aws-samples/sessions-with-aws-sam/tree/master/http-api-direct-integration

Receiving Error Property validation failure: [Value of property {/TableInput/ViewOriginalText} does not match type {String}] AWS cloudformation

I am trying to create external view using the data from the tables but receiving error Property validation failure: [Value of property {/TableInput/ViewOriginalText} does not match type {String}] while creating views in AWS glue using cloud formation.
Any idea about resolution would be appreciated. Below is the yaml file code snippet that I am using.
Thanks in Advance
---
AWSTemplateFormatVersion: 2010-09-09
Description: "Glue Athena database and table configuration"
Parameters:
MarketingAndSalesDatabaseName:
Type: String
MinLength: "4"
Default: "marketingandsales_qs"
Description: "Name of the AWS Glue database to contain this CloudFormation template's tables."
SalesPipelineTableName:
Type: String
MinLength: "4"
Default: "salespipeline_qs"
Description: "Name of the Sales Pipeline data table in AWS Glue."
Resources:
### AWS GLUE RESOURCES ###
MarketingAndSalesDatabase:
Type: "AWS::Glue::Database"
Properties:
DatabaseInput:
Description: "Marketing and Sales database (Amazon QuickSight Samples)."
Name: !Ref MarketingAndSalesDatabaseName
CatalogId: !Ref AWS::AccountId
SalesPipelineTable:
Type: "AWS::Glue::Table"
DependsOn: MarketingAndSalesDatabase
Properties:
TableInput:
Description: "Sales Pipeline table (Amazon QuickSight Sample)."
TableType: "VIRTUAL_VIEW"
Parameters: {
"CrawlerSchemaDeserializerVersion": "1.0",
"compressionType": "none",
"classification": "csv",
"recordCount": "16831",
"typeOfData": "file",
"CrawlerSchemaSerializerVersion": "1.0",
"columnsOrdered": "true",
"objectCount": "1",
"delimiter": ",",
"skip.header.line.count": "1",
"averageRecordSize": "119",
"sizeKey": "2002910",
"presto_view": "true"
}
StorageDescriptor:
StoredAsSubDirectories: False
Parameters: {
"CrawlerSchemaDeserializerVersion": "1.0",
"compressionType": "none",
"classification": "csv",
"recordCount": "16831",
"typeOfData": "file",
"CrawlerSchemaSerializerVersion": "1.0",
"columnsOrdered": "true",
"objectCount": "1",
"delimiter": ",",
"skip.header.line.count": "1",
"averageRecordSize": "119",
"sizeKey": "2002910"
}
InputFormat: "org.apache.hadoop.mapred.TextInputFormat"
OutputFormat: "org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat"
Columns:
- Type: string
Name: salesperson
- Type: string
Name: lead_name
ViewOriginalText:
'Fn::Sub':
- '/* Presto View: ${View} */'
- View:
'Fn::Base64': !sub '{"catalog": "awsdatacatalog",
"schema":"default",
"columns": [ { "name": "salesperson", "type": "varchar" }, { "name": "lead_name", "type": "varchar" }],
"originalSql": "SELECT salesperson AS salesperson, lead_name AS lead_name FROM marketing_qs_1"
}'
Retention: 0
Name: !Ref SalesPipelineTableName
DatabaseName: !Ref MarketingAndSalesDatabaseName
CatalogId: !Ref AWS::AccountId
These properties should be underneath StorageDescriptor instead of ViewOriginalText:
Compressed: False
Location: !Sub "s3://${DataBucketName}/sales/"
Recommend trying the CloudFormation Linter in VSCode to see some of these errors inline while authoring templates:
[cfn-lint] E3002: Property is an object instead of String at Resources/SalesPipelineTable/Properties/TableInput/ViewOriginalText
Sub also isn't required inside that Base64:
[cfn-lint] W1020: Fn::Sub isn't needed because there are no variables at Resources/SalesPipelineTable/Properties/TableInput/ViewOriginalText/Fn::Sub/1/View/Fn::Base64/Fn::Sub

Deploy StepFunctions with CloudFormation, from external definition file

I'm trying to deploy stepfunctions with CloudFormation, and I'd like to reference the actual stepfunction definition from an external file in S3.
Here's how the template looks like:
StepFunction1:
Type: "AWS::StepFunctions::StateMachine"
Properties:
StateMachineName: !Ref StepFunction1SampleName
RoleArn: !GetAtt StepFunctionExecutionRole.Arn
DefinitionString:
Fn::Transform:
Name: AWS::Include
Parameters:
Location:
Fn::Sub: 's3://${ArtifactsBucketName}/StepFunctions/StepFunction1/definition.json'
However, this doesn't seem to be supported, as we are getting error
Property validation failure: [Value of property {/DefinitionString} does not match type {String}]
I am doing something similar for APIs, referencing the actual API definition from an external swagger file, and that seems to be working fine.
Example:
SearchAPI:
Type: "AWS::Serverless::Api"
Properties:
Name: myAPI
StageName: latest
DefinitionBody:
Fn::Transform:
Name: AWS::Include
Parameters:
Location:
Fn::Sub: 's3://${ArtifactsBucketName}/ApiGateway/myAPI/swagger.yaml'
How can I make this work?
The trick is to escape the StepFunction DefinitionString property, and include the actual property, DefinitionString, in the external CloudFormation referenced file. Escaping only the stepfunction definition string would fail, CloudFormation complaining that the referenced Transform/Include template, is not a valid yaml/json.
Here's how it looks like:
Template:
StepFunction1:
Type: "AWS::StepFunctions::StateMachine"
Properties:
StateMachineName: !Ref StepFunction1SampleName
RoleArn: !GetAtt StepFunctionExecutionRole.Arn
Fn::Transform:
Name: AWS::Include
Parameters:
Location:
Fn::Sub: 's3://${ArtifactsBucketName}/StepFunctions/StepFunction1/definition.json'
External stepfunction definition file:
{
"DefinitionString" : {"Fn::Sub" : "{\r\n \"Comment\": \"A Retry example of the Amazon States Language using an AWS Lambda Function\",\r\n \"StartAt\": \"HelloWorld\",\r\n \"States\": {\r\n \"HelloWorld\": {\r\n \"Type\": \"Task\",\r\n \"Resource\": \"arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${HelloWorldLambdaFunctionName}\", \r\n \"End\": true\r\n }\r\n }\r\n}"}
}
Now, although this solves the problem, it's a bit more difficult to maintain the StepFunction definition, in this form, in source control.
So I've thought about using a CloudFormation custom resource backed by a lambda function. The lambda function would deal with the actual StepFunction DefinitionString escaping part.
Here's how it looks like:
Template:
StepFunctionParser:
Type: Custom::AMIInfo
Properties:
ServiceToken: myLambdaArn
DefinitionString:
Fn::Transform:
Name: AWS::Include
Parameters:
Location:
Fn::Sub: 's3://${ArtifactsBucketName}/StepFunctions/StepFunctionX/definition.json'
StepFunctionX:
Type: "AWS::StepFunctions::StateMachine"
Properties:
StateMachineName: StepFunction1SampleNameX
RoleArn: !GetAtt StepFunctionExecutionRole.Arn
DefinitionString: !GetAtt StepFunctionParser.DefinitionString
External StepFunction definition file:
{
"Comment": "A Retry example of the Amazon States Language using an AWS Lambda Function",
"StartAt": "HelloWorld",
"States": {
"HelloWorld": {
"Type": "Task",
"Resource": {"Fn::Sub" : "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${HelloWorldLambdaFunctionName}" },
"End": true
}
}
}
Here's the documentation for creating AWS Lambda-backed Custom Resources.
There's still a problem with this.
Transform/Include converts external template boolean properties into string properties.
Therefore, DefinitionString
"DefinitionString": {
"States": {
"HelloWorld": {
"Type": "Task",
"Resource": "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${HelloWorldLambdaFunctionName}",
**"End": true**
}
},
"Comment": "A Retry example of the Amazon States Language using an AWS Lambda Function",
"StartAt": "HelloWorld"
}
becomes
"DefinitionString": {
"States": {
"HelloWorld": {
"Type": "Task",
"Resource": _realLambdaFunctionArn_,
**"End": "true"**
}
},
"Comment": "A Retry example of the Amazon States Language using an AWS Lambda Function",
"StartAt": "HelloWorld"
}
CloudFormation then complains about the StepFunction definition not being valid:
Invalid State Machine Definition: 'SCHEMA_VALIDATION_FAILED: Expected value of type Boolean at /States/HelloWorld/End'
Is this a CloudFormation Transform/Include issue? Can someone from AWS give a statement on this?
In the mean time, one solution could be to use yaml instead of json, to store the stepfunction definition externally. No string escaping:
DefinitionString:
Fn::Sub: |
{
"Comment": "A Retry example of the Amazon States Language using an AWS Lambda Function",
"StartAt": "HelloWorld",
"States": {
"HelloWorld": {
"Type": "Task",
"Resource": "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${HelloWorldLambdaFunctionName}",
"End": true
}
}
}
Here you can find another approach to this problem: https://github.com/aws-samples/lambda-refarch-imagerecognition
And here is a brief summary:
As part of your CloudFormation template file (let's say it's named template.yml) you define the state machine as follows:
LambdaA:
...
LambdaB:
...
LambdaC:
...
MyStateMachine:
Type: AWS::StepFunctions::StateMachine
Properties:
StateMachineName: !Sub 'MyMachine-${SomeParamIfNeeded}'
DefinitionString:
!Sub
- |-
##{{STATEMACHINE_DEF}}
- {
lambdaArnA: !GetAtt [ LambdaA, Arn ],
lambdaArnB: !GetAtt [ LambdaB, Arn ],
lambdaArnC: !GetAtt [ LambdaC, Arn ]
}
RoleArn: !GetAtt [ StatesExecutionRole, Arn ]
Then you will need replacement script, here you can find one: inject_state_machine_cfn.py
Then you can create your state machine definition as a separate file, say state_machine.json with a content:
{
"Comment": "This is an example of Parallel State Machine type",
"StartAt": "Parallel",
"States": {
"Parallel": {
"Type": "Parallel",
"Next": "Final State",
"Branches": [
{
"StartAt": "Run Lambda A",
"States": {
"Run Lambda A": {
"Type": "Task",
"Resource": "${lambdaArnA}",
"ResultPath": "$.results",
"End": true
}
}
},
{
"StartAt": "Run Lambda B",
"States": {
"Run Login Functionality Test": {
"Type": "Task",
"Resource": "${lambdaArnB}",
"ResultPath": "$.results",
"End": true
}
}
}
]
},
"Final State": {
"Type": "Task",
"Resource": "${lambdaArnC}",
"End": true
}
}
}
Once you have all these elements you can invoke the transformation function:
python inject_state_machine_cfn.py -s state_machine.json -c template.yml -o template.complete.yml

How can I grant permission to API Gateway to invoke lambda functions through CloudFormation?

I've been all over the web searching for an answer to this.
Essentially, we're spinning up an API using Swagger, which is awesome and works great, but one thing doesn't work... When we make a call to an Endpoint, we get a 500 error (it's not a 500 error that we're providing either it's one from AWS). The error states "Execution failed due to configuration error: Invalid permissions on Lambda function" (https://youtu.be/H4LM_jw5zzs <- This is a video, from another user, of the error I'm getting).
I've gone down many ratholes, and have found an answer... It involves using the AWS CLI and looks a bit like this:
aws lambda add-permission \
--function-name FUNCTION_NAME \
--statement-id STATEMENT_ID \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:us-east-1:ACCOUNT_ID:API_ID/*/METHOD/ENDPOINT"
This is great and all, but we are using CloudFormation to spin up everything and we want this to be automated. Is there an easier way to go about this? Is there something in CloudFormation that will give us the resource policy that we need?
I'm hitting a bit of a wall with this, but I've been working on it for a few hours today and it's a bit of a blocker for our API release, so any help would be much appreciated. :)
There is a CloudFormation solution to this problem. See the following CloudFormation snippet:
"Permission": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"FunctionName": { "Fn::GetAtt": [ "Lambda", "Arn" ] },
"Action": "lambda:InvokeFunction",
"Principal": "apigateway.amazonaws.com",
"SourceArn": { "Fn::Join": [ "", [
"arn:aws:execute-api:",
{ "Ref": "AWS::Region" }, ":",
{ "Ref": "AWS::AccountId" }, ":",
{ "Ref": "API" },
"/*/*/*"
] ] }
}
}
This grants API Gateway permissions to launch your Lambda function. Variables in this snippet you need to change are Lambda (line 4) and API (line 11).
For the invoke permissions:
"APIInvokePermission": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"FunctionName": {
"Ref": "YOUR_LAMBDA_FUNCTION_RESOURCE_NAME"
},
"Action": "lambda:InvokeFunction",
"Principal": "apigateway.amazonaws.com",
"SourceArn": {
"Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${YOUR_REST_API_RESOURCE_NAME}/*/*/*"
}
}
},
Thanks https://twitter.com/edjgeek for helping me get this straight in my head.
This GIST shows how to use AWS::Serverless:Function with Events to automatically generate the needed AWS::Lambda::Permission to allow APIGateway (for a given route) to invoke your Lambda:
https://gist.github.com/rainabba/68df1567cbd0c4930d428c8953dc2316
Both of the following approaches assume an api such as (many fields omitted for readability):
MyApi:
Type: 'AWS::Serverless::Api'
Properties:
DefinitionBody:
Fn::Transform:
Name: AWS::Include
Parameters:
Location: openapi.yaml
Using "SAM Events"
The most relevant bit (I've omitted many required fields):
MyLambdaFunction:
Type: 'AWS::Serverless::Function'
Properties:
Events:
MyRouteEventToProxy:
Type: Api
Properties:
Method: POST
Path: '/some-route/{pathParm}'
RestApiId: !Ref MyApi # ResourceName of AWS::Serverless::Api
Auth:
Authorizer: NONE
Using "openapi binding"
If you'd rather declare the binding in the openapi.yaml, then see the following project (no Lambda/Events required). This approach requires an explict role to allow invoke.
template.yaml relevant bits:
MyLambdaFunction:
Type: 'AWS::Serverless::Function'
Properties:
# Events: # No need for Events when binding from openapi.yaml
MyHttpApiRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: "apigateway.amazonaws.com"
Action:
- "sts:AssumeRole"
openapi.yaml relevant bits:
paths:
post:
x-amazon-apigateway-integration:
httpMethod: POST
uri:
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaFunction.Arn}:live/invocations"
contentHandling: "CONVERT_TO_TEXT"
type: aws_proxy
credentials:
Fn::GetAtt: [MyHttpApiRole, Arn]
https://github.com/aws-samples/sessions-with-aws-sam/tree/master/http-api-direct-integration