How to subscibe to SNS topic with lambda ARN? - amazon-web-services

I created a lambda function using the aws_cdk.aws_lambda.Function constructor. Following that I aim to subscribe to a SNS topic I created with boto3. However, one of the arguments needed is the lambda function ARN which I try to get with dynamodb_lambda.function_arn but the problem is that the the attributes returns a unresolved token more specifically ${Token[TOKEN.240]}
Here is a portion of the code to further clarify what I'm doing.
dynamodb_lambda = lambda_.Function(
self, #scope
"foobar", #id
runtime = lambda_.Runtime.PYTHON_3_7,
handler = "lambda_handlers.dynamodb_lambda_handler", #filename.methodname at path
code = lambda_.Code.from_asset(path),
role = iam_.Role( #need this for cloudwatch access
self, #scope
"foobar", #id
assumed_by = iam_.ServicePrincipal('lambda.amazonaws.com'),
managed_policies = [
iam_.ManagedPolicy.from_aws_managed_policy_name('DynamoDBFullAccess')
]
)
)
client_sns = boto3.client("sns")
response = client_sns.create_topic(
Name = c.SNS_TOPIC_NAME,
Tags = [ # for easier filtering and searching
{
'Key': 'CohortStudent',
'Value': 'anon'
}
])
client_sns.subscribe(
TopicArn = response['TopicArn'],
Protocol = 'lambda', #usually "email" or "sms", see link above for possible values
Endpoint = dynamodb_lambda.function_arn
)
The last parameter is where I'm facing trouble. The lambda function isn't created yet so the ARN is a token, but the subscribe function doesn't accept that.
botocore.errorfactory.InvalidParameterException: An error occurred (InvalidParameter) when calling the Subscribe operation: Invalid parameter: Lambda endpoint ARN
Please help me understand how to figure this out. Any help is appreciated.

As luk2302 has commented, using boto3 calls to subscribe to a SNS Topic created using aws_cdk is wrong. A simple fix would be to use subscribe to the lambda using aws_cdk.

Related

How to make a PubSub triggered Cloud Function with message ordering using terraform?

I am trying to create a Cloud Function that is triggered from a PubSub subscription, but I need to have the message ordering enabled. I know to use the event_trigger block in the google_cloudfunctions_function block, when creating a function linked to a subscription. However this does not like the enable_message_ordering as described under PubSub. When using the subscription push config, I don't know how I can get link the endpoint to the function.
So is there a way I can link the function to a subscription with message ordering enabled?
Can I just use the internal URL to the function as the push config URL?
You can't use background functions triggered by PubSub and message ordering (or filtering).
You have do deploy a HTTP functions (take care, the signature of the fonction change, and the the format of the PubSub message also change slightly).
Then create a PubSub PUSH subscriptions, use the Cloud Functions URL. The best is also to add a Service Account on PubSub to allow only it to call your Functions.
For completeness I wanted to add the terraform that I used to do this. In case others are looking.
# This is the HTTP function that processes the events from PubSub, note it is set as a HTTP trigger
resource "google_cloudfunctions_function" "processEvent" {
name = "processEvent"
runtime = var.RUNTIME
environment_variables = {
GCP_PROJECT_ID = var.GCP_PROJECT_ID
LOG_LEVEL = var.LOG_LEVEL
}
available_memory_mb = var.AVAILABLE_MEMORY
timeout = var.TIMEOUT
source_archive_bucket = var.SOURCE_ARCHIVE_BUCKET
source_archive_object = google_storage_bucket_object.processor-archive.name
trigger_http = true
entry_point = "processEvent"
}
# define the topic
resource "google_pubsub_topic" "event-topic" {
name = "event-topic"
}
# We need to create the subscription specifically as we need to enable message ordering
resource "google_pubsub_subscription" "processEvent_subscription" {
name = "processEvent_subscription"
topic = google_pubsub_topic.event-topic.name
ack_deadline_seconds = 20
push_config {
push_endpoint = "https://${var.REGION}-${var.GCP_PROJECT_ID}.cloudfunctions.net/${google_cloudfunctions_function.processEvent.name}"
oidc_token {
# a new IAM service account is need to allow the subscription to trigger the function
service_account_email = "cloudfunctioninvoker#${var.GCP_PROJECT_ID}.iam.gserviceaccount.com"
}
}
enable_message_ordering = true
}

Can I create Slack subscriptions to an AWS SNS topic?

I'm trying to create a SNS topic in AWS and subscribe a lambda function to it that will send notifications to Slack apps/users.
I did read this article -
https://aws.amazon.com/premiumsupport/knowledge-center/sns-lambda-webhooks-chime-slack-teams/
that describes how to do it using this lambda code:
#!/usr/bin/python3.6
import urllib3
import json
http = urllib3.PoolManager()
def lambda_handler(event, context):
url = "https://hooks.slack.com/services/xxxxxxx"
msg = {
"channel": "#CHANNEL_NAME",
"username": "WEBHOOK_USERNAME",
"text": event['Records'][0]['Sns']['Message'],
"icon_emoji": ""
}
encoded_msg = json.dumps(msg).encode('utf-8')
resp = http.request('POST',url, body=encoded_msg)
print({
"message": event['Records'][0]['Sns']['Message'],
"status_code": resp.status,
"response": resp.data
})
but the problem is, that in that implementation I have to create a lambda function for every user.
I want to subscribe multiple Slack apps/users to one SNS topic.
Is there a way of doing that without creating a lambda function for each one?
You really DON'T need Lambda. Just SNS and SLACK are enough.
I found a way to integrate AWS SNS with slack WITHOUT AWS Lambda or AWS chatbot. With this approach you can confirm the subscription easily.
Follow the video which show all the step clearly.
https://www.youtube.com/watch?v=CszzQcPAqNM
Steps to follow:
Create slack channel or use existing channel
Create a work flow with selecting Webhook
Create a variable name as "SubscribeURL". The name
is very important
Add the above variable in the message body of the
workflow Publish the workflow and get the url
Add the above Url as subscription of the SNS You will see the subscription URL in the
slack channel
Follow the URl and complete the subscription
Come back to the work flow and change the "SubscribeURL" variable to "Message"
The publish the
message in SNS. you will see the message in the slack channel.
Hi i would say you should go for a for loop and make a list of all the users. Either manually state them in the lambda or get them with api call from slack e.g. this one here: https://api.slack.com/methods/users.list
#!/usr/bin/python3.6
import urllib3
import json
http = urllib3.PoolManager()
def lambda_handler(event, context):
userlist = ["name1", "name2"]
for user in userlist:
url = "https://hooks.slack.com/services/xxxxxxx"
msg = {
"channel": "#" + user, # not sure if the hash has to be here
"username": "WEBHOOK_USERNAME",
"text": event['Records'][0]['Sns']['Message'],
"icon_emoji": ""
}
encoded_msg = json.dumps(msg).encode('utf-8')
resp = http.request('POST',url, body=encoded_msg)
print({
"message": event['Records'][0]['Sns']['Message'],
"status_code": resp.status,
"response": resp.data
})
Another solution you can do is set up email for the slack users, see link:
https://slack.com/help/articles/206819278-Send-emails-to-Slack
When you can just add the emails as subscribers to the sns topic. You can fileter the msg that the receiver gets with Subscription filter policy.

AWS HTTP API Gateway Getting Double Messages from Browser

I have an AWS API Gateway setup to pass values from HTTP calls to a lambda.
When I use postman, everything works great. When I use the browser, however, I get 2 calls. One of which is the same as the postman call, the other is missing all of the arguments and causes an error.
resource "aws_apigatewayv2_api" "retry_api" {
name = "${var.environment}_${var.cdp_domain}_retry_api"
protocol_type = "HTTP"
description = "To pass commands into the retry lambda."
target = module.retry-support.etl_lambda_arn
}
resource "aws_lambda_permission" "allow_retry_api" {
statement_id = "AllowAPIgatewayInvokation"
action = "lambda:InvokeFunction"
function_name = module.retry-support.etl_lambda_arn
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.retry_api.execution_arn}/*/*"
}
I'm giving the parameter as such:
?type_of_retry=retry_event
And on the first event it comes in correctly:
'rawQueryString': 'type_of_retry=retry_event'
'queryStringParameters': {'type_of_retry': 'retry_event'}
On the second event the rawQueryString is an empty list and there is no queryStringParameters key at all.
If anyone knows how to eliminate that 2nd event from being generated that would be helpful. Thank you.
Turns out that 2nd message was a favicon request.

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

Send an SMS via AWS SNS using boto3 in an AWS Lambda function?

I would like to send an SMS message from an AWS Lambda function using the boto3 publish method to notify the user of issues via SMS. My lambda function is written in Python and I am using the boto3 module. My lambda function has full rights to SNS. I have this code,
sns = boto3.client('sns')
sns.publish(
PhoneNumber = '+11234567890',
Message = 'Simple text message'
)
According to the boto3 documentation, the publish method accepts the following parameters,
response = client.publish(
TopicArn='string',
TargetArn='string',
PhoneNumber='string',
Message='string',
Subject='string',
MessageStructure='string',
MessageAttributes={
'string': {
'DataType': 'string',
'StringValue': 'string',
'BinaryValue': b'bytes'
}
}
)
It requires a "Message" parameter and one of the following three parameters as described in the docs:
TopicArn (string) -- The topic you want to publish to.
If you don't specify a value for the TopicArn parameter, you must
specify a value for the PhoneNumber or TargetArn parameters.
TargetArn (string) -- Either TopicArn or EndpointArn, but not both.
If you don't specify a value for the TargetArn parameter, you must
specify a value for the PhoneNumber or TopicArn parameters.
PhoneNumber (string) -- The phone number to which you want to deliver
an SMS message. Use E.164 format.
If you don't specify a value for the PhoneNumber parameter, you must
specify a value for the TargetArn or TopicArn parameters.
When my code is executed a parameter validation error is returned. It states,
Unknown parameter in input: "PhoneNumber", must be one of: TopicArn,
TargetArn, >Message, Subject, MessageStructure, MessageAttributes".
So the documentation seems to indicate that PhoneNumber is a valid parameter, but when used, an error occurs and the feedback from the error indicates that PhoneNumber is not a possible parameter. I suspect I am missing something obvious and simple, but could use some help.
I know there are other avenues to send SMS messages such as email gateways and other vendor supplied solutions like Twilio, but I would like to pursue the SNS based route and understand where I have gone wrong.
Actually your example looks right. Here is what I tried
import boto3
sns = boto3.client('sns')
number = '+17702233322'
sns.publish(PhoneNumber = number, Message='example text message' )
Worked like a charm. I recommend using awscli configured with your root account credentials first and take the code for a test drive. Once its working either create a new user with just the rights you need, or apply it to an Instance role.
You need to create a policy that allows SNS:Publish on resource:* (allow texting to everyone) or resource: '+17702233322' (allow text to a specific number).