A combination of AWS Lambda and Amazon API Gateway is a widely-used architecture for serverless microservices and API based solutions. They enable developers to focus on their applications, instead of spending time provisioning and managing servers.
API Gateway is a feature rich offering that includes with support for different API types (HTTP, REST, WebSocket), multiple authentication schemes, API versioning, canary deployments and much more! However, if your requirements are simpler and all you need is an HTTP(S) endpoint for your Lambda function (for example, to serve as a webhook), you can use Lambda Function URLs! When you create a function URL, Lambda automatically generates a unique HTTP(S) endpoint that is dedicated for your Lambda function.
This blog post demonstrates how to use Lambda function URL with a practical example. You will build a Go Lambda function to serve as a serverless webhook backend for Slack.
It's a step-by-step guide that covers:
- Overview of the application
- Configure and deploy the function (along with some gotchas you need to watch out for!)
- How to configure Slack to enable the end to end integration
- Test the app and have fun!
By the end of this blog, you would have configured, integrated and deployed a useful (and hopefully fun?) app using Lambda function URL. In the process, you will get an overview of this feature that you can utilise when building your own solutions!
The code is available on GitHub
How it works
The sample app presented in this blog is a trimmed down version of Giphy for Slack. The (original) Giphy Slack app returns a bunch of GIFs for a search term and the user can pick one of them. To keep things simple, I've tweaked things a bit such that the serverless backend simply returns a (single) random image for a search keyword using the Giphy Random API.
Since the solution will be integrated as a Slash Command in Slack, the end user (you!) will invoke it from a Slack workspace using /awsome <your search term>
(where awsome
is nothing but the name of the slash command). This in turn invokes the Lambda function URL (the configuration is covered later in the blog), which takes care of the rest.
For example, invoking it from your Slack workspace using /awsome serverless
will return a random GIF (you will try this later!)
Here is an overview of what the Lambda function does:
- Slack slash command invocation results in a
base64
encoded string payload being sent to the Lambda function URL - so the first step is to decode it. - The function is only supposed to invoked by Slack and we need to make sure we confirm that. Slack makes this possible by allowing apps to verify requests using a signing secret - the function simply implements a Go version of the signature matching recipe presented here
- If the signature match is successful (we return an error to the client if it fails), the Slack request is parsed to extract the search
text
that user sent. - Then, the Giphy Random API is invoked with the search term. If we get a successful response, we parse it and send it back to Slack in it's desired format
Finally, the user gets to see a GIF in their Slack workspace!
I will skip the code walk-through in order to focus on other aspects of the solution, but the function signature deserves a mention - it is similar to what you would've used in case of an API Gateway based solution:
func Funcy(r events.LambdaFunctionURLRequest) (events.LambdaFunctionURLResponse, error) {
...
}
We are using events.LambdaFunctionURLRequest as input and returning events.LambdaFunctionURLResponse. Behind the scenes, Lambda maps the request to an event object before passing it to the function. Finally, the function response is then mapped to an HTTP response that Lambda sends back to the client through the function URL.
You can read up on the details in the documentation
That's quite convenient right? You can use API Gateway conventions without actually having to setup and configure one!
With that background info, let's move on to the part where you deploy the function and try it out with Slack. But, before that make sure you have the following ready:
Pre-requisites
- Create an AWS account (if you do not already have one) and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
- Install and configure AWS CLI
- Install Go
- Install Git
- Create a Slack workspace if you don't have one.
- Create a GIHPY account (it's free!) and create an app. Each application you create will have its own API Key.
Please note down your GIPHY API key as you will be using it later
Clone the Github repo and move into the right directory:
git clone https://github.com/abhirockzz/awsome-slack-backend
cd awsome-slack-backend/function
The subsequent steps use AWS CLI - I've purposely used the AWS CLI in order to highlight specific aspects of the process. Please check this tutorial for CloudFormation and SAM
Build, zip and deploy the function!
export FUNC_NAME=awsome-slack-backend
export FUNC_GO_BINARY_NAME=awsome
export ZIP_NAME=function.zip
GOOS=linux go build -o $FUNC_GO_BINARY_NAME main.go
zip function.zip $FUNC_GO_BINARY_NAME
First, create an IAM Role for Lambda and attach the AWSLambdaBasicExecutionRole
policy:
export ROLE_NAME=demo-lambda-role
ROLE_ARN=$(aws iam create-role \
--role-name $ROLE_NAME \
--assume-role-policy-document '{"Version": "2012-10-17","Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}' \
--query 'Role.[Arn]' --output text)
aws iam attach-role-policy --role-name $ROLE_NAME --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Create the function:
aws lambda create-function \
--function-name $FUNC_NAME \
--runtime go1.x \
--zip-file fileb://$ZIP_NAME \
--handler $FUNC_GO_BINARY_NAME \
--role $ROLE_ARN
After the function gets created, go ahead and add the Function URL:
aws lambda create-function-url-config \
--function-name $FUNC_NAME \
--auth-type NONE
For the purposes of this sample app, we're using
NONE
as the authentication type. This means that the Lambda function URL will be publicly accessible - more on this shortly
If you navigate to the AWS console and open the function you just created, you should see the Function URL associated with it:
Let's invoke the function - copy the Function URL and paste it in a browser or use any other tool (e.g. curl
)
curl -i <FUNCTION_URL>
You should get a {"Message":"Forbidden"}
response with a HTTP 403 Forbidden
status code
Don't worry, this is expected - I wanted to make sure you encounter this issue and understand the root cause.
Even though we use NONE
as the authentication scheme, users must still have lambda:InvokeFunctionUrl
permissions in order to successfully invoke the function URL. The (slightly) tricky bit is that when you create a function URL (with auth type NONE
) via the console or AWS Serverless Application Model (AWS SAM), Lambda automatically creates the resource-based policy statement for you (details in the documentation). That's not the case if you're using the AWS CLI (as in this blog), AWS CloudFormation, or the Lambda API directly - you must add permissions yourself.
Let's do that:
aws lambda add-permission \
--function-name $FUNC_NAME \
--action lambda:InvokeFunctionUrl \
--statement-id FunctionURLAllowPublicAccess \
--principal "*" \
--function-url-auth-type NONE
To see the policy, navigate to your Function in the AWS console: Configuration > Permissions
Invoke the function again:
curl -i <FUNCTION_URL>
This time, you will get a different error with a HTTP 401 Unauthorized
status code. This is expected as well!
Let's finish the rest of the configuration to get things working.
Configure Slack
Please note that most of the instructions in this section have been adapted from the Slack documentation
Start by signing into your Slack Workspace and creating a new Slack App.
Once that's done, create a Slash Command - head to your app's settings page, and then click the Slash Commands feature in the navigation menu. You'll be presented with a button marked Create New Command, and when you click on it, you'll see a screen where you'll be asked to define your new Slash Command with the required information.
Enter the required information - Enter /awsome
for the Command and enter the Lambda Function URL in Request URL
Finally, install the app to your workspace - click the Basic Information feature in the navigation menu, choose Install your app to your workspace and click Install App to Workspace. This will install the app to your Slack workspace to test your app and generate the tokens you need to interact with the Slack API.
As soon as you finish installing the app, the App Credentials will show up on the same page. You need to grab your Slack Signing Secret from there
Make a note of your app Signing Secret as you'll be using it later
Update the function
Now that you've the Slack signing secret key, you need to make sure to configure it in the function as well. Also, don't forget the GIPHY API key since the function needs that to invoke GIPHY REST endpoint.
Let's update the function to include these as environment variables:
aws lambda update-function-configuration \
--function-name $FUNC_NAME \
--environment "Variables={SLACK_SIGNING_SECRET=<enter Slack signing secret>,GIPHY_API_KEY=<enter Giphy API key>}"
The sample app uses Lambda environment variables to store keys for Slack and GIPHY - this is just for demonstration purposes. You should use a solution such as AWS Secrets Manager to securely store and manage credentials.
You're all set!
Head over to your Slack workspace and invoke the command. For example, to get a random cat GIF, just type:
/awsome cat
I got this response. How about you? :-)
Feel free to play around with the app!
Clean up
Once you're done, delete the function along with the IAM policy and role.
aws lambda delete-function --function-name $FUNC_NAME
aws iam detach-role-policy --role-name $ROLE_NAME --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws iam delete-role --role-name $ROLE_NAME
That's all for now!
You configured and deployed a serverless backend for Slack and in the process, learnt about some of the aspects of Lambda Function URLs through the lens of this sample app. I would encourage you to explore other capabilities such as AWS_IAM authentication, CORS config, throttling limits, monitoring etc.
Happy coding!
Top comments (0)