Giving AWS Api Gateway Permission To Invoke Lambda Function using BOTO3 - amazon-web-services

I am attempting to use BOTO3 to create an Api Gateway method that invokes a lambda function. I have so far been unable to find how to grant the necessary permissions.
Curiously, setting the lambda method name manually through the AWS console sets up permissions automatically. I have been unable to replicate this in code.
This is the code I am using to set up the gateway:
# Create a rest api
self.rest_api = self.apigateway.create_rest_api(
name='AWS_CMS_Operations'
)
# Get the rest api's root id
root_id = self.apigateway.get_resources(
restApiId=self.rest_api['id']
)['items'][0]['id']
# Create an api resource
api_resource = self.apigateway.create_resource(
restApiId=self.rest_api['id'],
parentId=root_id,
pathPart='AWS_CMS_Manager'
)
# Add a post method to the rest api resource
api_method = self.apigateway.put_method(
restApiId=self.rest_api['id'],
resourceId=api_resource['id'],
httpMethod='POST',
authorizationType='NONE'
)
# Add an integration method to the api resource
self.apigateway.put_integration(
restApiId=self.rest_api['id'],
resourceId=api_resource['id'],
httpMethod='POST',
type='AWS',
integrationHttpMethod='POST',
uri=self.create_api_invocation_uri()
)
# Set the put method response for the api resource
self.apigateway.put_method_response(
restApiId=self.rest_api['id'],
resourceId=api_resource['id'],
httpMethod='POST',
statusCode='200',
responseModels={
'application/json': 'Empty'
}
)
# Set the put integration response for the api resource
self.apigateway.put_integration_response(
restApiId=self.rest_api['id'],
resourceId=api_resource['id'],
httpMethod='POST',
statusCode='200',
responseTemplates={
'application/json': ''
}
)
# Create a deployment of the rest api
self.apigateway.create_deployment(
restApiId=self.rest_api['id'],
stageName='prod'
)
# Give the api deployment permission to trigger the lambda function
self.lmda.add_permission(
FunctionName=self.lmda_function['FunctionName'],
StatementId='apigateway-production-aws-cms',
Action='lambda:InvokeFunction',
Principal='apigateway.amazonaws.com',
SourceArn=self.create_api_permission_uri(api_resource)
)
Everything works fine with the exception of the proper permission being set for the gateway to invoke lambda.

I was able to get this working in Boto3 using this code:
source_arn = f'arn:aws:execute-api:{REGION}:{account_id}:{api_id}/*/*/{api_path}'
lambda_client.add_permission(FunctionName=lambda_function_arn, StatementId=f'invoke_{api_id}',
Action='lambda:InvokeFunction', Principal='apigateway.amazonaws.com',
SourceArn=source_arn)
This doesn't use the conventions used in the original question, but there are three things I'd like to point that may help with anyone wrestling with this question:
The FunctionName parameter can take the function name, the function's ARN, or the partial ARN according to the Boto3 Documentation.
The StatementId must be unique in your account. To ensure uniqueness, I appended something to the API ID, since I'll only need one rule per API.
The SourceARN is the "execute-api" ARN for your api/staging location. I haven't seen that ARN used elsewhere.

From section 3.6 in this tutorial is a sample CLI command:
$ aws lambda add-permissionn \
--function-name <function-name> \
--statement-id apigateway-test-2 \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "<method-arn">
Should be straight forward enough to translate to Boto3.

Related

How to Reuse the api gate TokenAuthorizer

I want to reuse the TokenAuthorizer which I have created in another stack. If a do the below it gives an error that it already exists, and if I change the authorizerName it creates a new one.
Is there a way I can reuse this resource?
const authzHandler = lambda.Function.fromFunctionName(this, 'AuthHandlerLookup', 'auth-handler');
const authorizer = new apigateway.TokenAuthorizer(this, 'WebApiTokenAuthorizer', {
handler: authzHandler,
resultsCacheTtl: Duration.seconds(600),
authorizerName: 'test-Authorizer',
assumeRole: lambdaExecutionRole
});
test.addMethod('GET', new apigateway.LambdaIntegration(TestLambda , { proxy: true }),
{
authorizer
}
i am able to get the authorizer information in cli , but now sure how to do the same using cdk
aws apigateway get-authorizer --rest-api-id wrrt25mzme0m --authorizer-id vffawds

Enable Provisioned Concurrency for a Lambda Authorizer

Using Terraform, I have a confiuration for an AWS HTTP API Gateway like so:
resource "aws_apigatewayv2_authorizer" "authorizer" {
api_id = module.api_gateway.this_apigatewayv2_api_id
name = "authorizer"
authorizer_payload_format_version = "2.0"
enable_simple_responses = true
authorizer_result_ttl_in_seconds = var.authorizer_result_ttl_in_seconds
authorizer_type = "REQUEST"
identity_sources = ["$request.header.Authorization"]
# Problem is below:
authorizer_uri = module.auth-authorizer-lambda.this_lambda_function_invoke_arn
}
when I use this_lambda_function_invoke_arn, this works fine but a Concurrency-provisioned version of the Lambda is not called (so the Lambda can work for, like, 4s). Typically one can refer to such a version by this_lambda_function_qualified_arn, but using it would result in an error:
Error: error updating API Gateway v2 authorizer: BadRequestException: Invalid Authorizer URI:
arn:aws:lambda:eu-west-1:<account-id>:function:authorizer:5.
Authorizer URI should be a valid API Gateway ARN that represents a Lambda function invocation.
How can I configure API gateway to use the particular version of an Authorizer lambda?
It is not shown what auth-authorizer-lambda module is, but the use of this_lambda_function_invoke_arn is incorrect. The correct form of authorizer_uri is shown in the following example:
arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-2:{account_id}:function:{lambda_function_name}/invocations
So you have to construct and provide the authorizer_uri in the form as shown above. Obviously, it must be adjusted with your region, account id and function name.
If anyone else runs into this... you can configure API gateway to use the particular version of an Authorizer lambda by using the invoke_arn of the alias.
resource "aws_lambda_alias" "test_lambda_alias" {
name = "my_alias"
description = "a sample description"
function_name = aws_lambda_function.lambda_function_test.arn
function_version = "1"
}
resource "aws_apigatewayv2_authorizer" "authorizer" {
api_id = module.api_gateway.this_apigatewayv2_api_id
name = "authorizer"
authorizer_payload_format_version = "2.0"
enable_simple_responses = true
authorizer_result_ttl_in_seconds = var.authorizer_result_ttl_in_seconds
authorizer_type = "REQUEST"
identity_sources = ["$request.header.Authorization"]
# Problem is below:
authorizer_uri = aws_lambda_alias.test_lambda_alias.invoke_arn
}
Then, as always don't forget to deploy the change to a stage to take effect.
The final reference will be this format:
arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-2:{account_id}:function:{lambda_function_name}:{lambda_alias}/invocations

Set up S3 Bucket level Events using AWS CloudFormation

I am trying to get AWS CloudFormation to create a template that will allow me to attach an event to an existing S3 Bucket that will trigger a Lambda Function whenever a new file is put into a specific directory within the bucket. I am using the following YAML as a base for the CloudFormation template but cannot get it working.
---
AWSTemplateFormatVersion: '2010-09-09'
Resources:
SETRULE:
Type: AWS::S3::Bucket
Properties:
BucketName: bucket-name
NotificationConfiguration:
LambdaConfigurations:
- Event: s3:ObjectCreated:Put
Filter:
S3Key:
Rules:
- Name: prefix
Value: directory/in/bucket
Function: arn:aws:lambda:us-east-1:XXXXXXXXXX:function:lambda-function-trigger
Input: '{ CONFIGS_INPUT }'
I have tried rewriting this template a number of different ways to no success.
Since you have mentioned that those buckets already exists, this is not going to work. You can use CloudFormation in this way but only to create a new bucket, not to modify existing bucket if that bucket was not created via that template in the first place.
If you don't want to recreate your infrastructure, it might be easier to just use some script that will subscribe lambda function to each of the buckets. As long as you have a list of buckets and the lambda function, you are ready to go.
Here is a script in Python3. Assuming that we have:
2 buckets called test-bucket-jkg2 and test-bucket-x1gf
lambda function with arn: arn:aws:lambda:us-east-1:605189564693:function:my_func
There are 2 steps to make this work. First, you need to add function policy that will allow s3 service to execute that function. Second, you will loop through the buckets one by one, subscribing lambda function to each one of them.
import boto3
s3_client = boto3.client("s3")
lambda_client = boto3.client('lambda')
buckets = ["test-bucket-jkg2", "test-bucket-x1gf"]
lambda_function_arn = "arn:aws:lambda:us-east-1:605189564693:function:my_func"
# create a function policy that will permit s3 service to
# execute this lambda function
# note that you should specify SourceAccount and SourceArn to limit who (which account/bucket) can
# execute this function - you will need to loop through the buckets to achieve
# this, at least you should specify SourceAccount
try:
response = lambda_client.add_permission(
FunctionName=lambda_function_arn,
StatementId="allow s3 to execute this function",
Action='lambda:InvokeFunction',
Principal='s3.amazonaws.com'
# SourceAccount="your account",
# SourceArn="bucket's arn"
)
print(response)
except Exception as e:
print(e)
# loop through all buckets and subscribe lambda function
# to each one of them
for bucket in buckets:
print("putting config to bucket: ", bucket)
try:
response = s3_client.put_bucket_notification_configuration(
Bucket=bucket,
NotificationConfiguration={
'LambdaFunctionConfigurations': [
{
'LambdaFunctionArn': lambda_function_arn,
'Events': [
's3:ObjectCreated:*'
]
}
]
}
)
print(response)
except Exception as e:
print(e)
You could write a custom resource to do this, in fact that's what I've ended up doing at work for the same problem. At the simplest level, define a lambda that takes a put bucket notification configuration and then just calls the put bucket notification api with the data that was passed it.
If you want to be able to control different notifications across different cloudformation templates, then it's a bit more complex. Your custom resource lambda will need to read the existing notifications from S3 and then update these based on what data was passed to it from CF.

Latest Lambda Layer ARN

I have a lambda layer which I keep updating. This lambda layer has multiple versions. How can I find the lambda layer ARN with latest version using aws cli?
I am able to do this using the command listed below -
aws lambda list-layer-versions --layer-name <layer name> --region us-east-1 --query 'LayerVersions[0].LayerVersionArn'
Unfortunately, it's currently not possible (I have encountered the same issue).
You can keep the latest ARN in your own place (like DynamoDB) and update it whenever you publish a new version of the layer.
You can create a custom macro to get the latest lambda layer version and use that as a reference.
The following function gets the latest version from the Lambda Layer stack:
import json
import boto3
def latest_lambdalayer(event, context):
fragment = get_latestversion(event['fragment'])
return {
'requestId': event['requestId'],
'status': 'success',
'fragment': fragment
}
def get_latestversion(fragment):
cloudformation = boto3.resource('cloudformation')
stack = cloudformation.Stack('ticketapp-layer-dependencies')
for o in stack.outputs:
if o['OutputKey']=='TicketAppLambdaDependency':
return o['OutputValue']
#return "arn:aws:lambda:eu-central-1:899885580749:layer:ticketapp-dependencies-layer:16"
And you use this when defining the Lambda layer—here using same global template:
Globals:
Function:
Layers:
- !Transform { "Name" : "LatestLambdaLayer"}
Runtime: nodejs12.x
MemorySize: 128
Timeout: 101

Boto3 - Create S3 'object created' notification to trigger a lambda function

How do I use boto3 to simulate the Add Event Source action on the AWS GUI Console in the Event Sources tab.
I want to programatically create a trigger such that if an object is created in MyBucket, it will call MyLambda function(qualified with an alias).
The relevant api call that I see in the Boto3 documentation is create_event_source_mapping but it states explicitly that it is only for AWS Pull Model while I think that S3 belongs to the Push Model. Anyways, I tried using it but it didn't work.
Scenarios:
Passing a prefix filter would be nice too.
I was looking at the wrong side. This is configured on S3
s3 = boto3.resource('s3')
bucket_name = 'mybucket'
bucket_notification = s3.BucketNotification(bucket_name)
response = bucket_notification.put(
NotificationConfiguration={'LambdaFunctionConfigurations': [
{
'LambdaFunctionArn': 'arn:aws:lambda:us-east-1:033333333:function:mylambda:staging',
'Events': [
's3:ObjectCreated:*'
],
},
]})