Lambdas with GoLang — a technical guide

Sam Bryant
Cloudnative.ly
Published in
5 min readNov 12, 2018

--

In one of my previous blogs I talked about migrating an app to Lambda. One of the challenges I had with my migration was finding good example and guides for Lambdas written in GoLang. The “Getting Started” type guides were great but it didn’t take long to exceed their limits of usefulness. Whenever I was looking for more advanced examples I found myself having to read a Javascript tutorial and work it out. Hopefully you find this helpful.

All of the code examples in this blog can also be found on GitHub.

You can clone the code by running:

go get github.com/srbry/go-serverless-example
cd $GOPATH/github.com/srbry/go-serverless-example

Getting started

The serverless framework

I found the easiest way to deploy my lambdas was using the serverless framework. An example for a hello world lambda on AWS:

The main sections of note are our provider.runtime of go1.x our provider.name of aws telling the framework that it will be deploying functions to AWS Lambda. We have set provider.memorySize to 128 as this is the smallest memory footprint serverless allows and our Hello World service won’t need even close to that.

We then tell serverless what files to include and exclude within our package block. With GoLang Lambdas we deploy compiled Linux binaries so we want to make sure we exclude all of the source code etc from being uploaded and wasting resources.

We can then define our functions, for GoLang our handler is defined as a path to a binary file. Each function gets triggered by events, as we want an HTTP function we can add an HTTP event. This will create an AWS API gateway to listen on the paths/ methods we want and trigger our functions.

Hello world

I try to set out my functions within the following format.

functions

└───hello-world
│ │ main.go

How does the most basic lambda in Go look?

We can also wrap this in a unit test. I use ginkgo for testing. You will need to create a test suite using ginkgo bootstrap (or copying the one from the GitHub repo).

Your tests will look something like:

You can run them with either go test or ginkgo.

Before we can deploy anything we need to make sure we have compiled our function.

env GOOS=linux go build -ldflags="-s -w" -o bin/hello-world functions/hello-world/main.go

I have also included an example Makefile that will compile all functions.

To deploy your environment make sure you have sourced some AWS Secrets and run:

sls deploy

The sls deploy command should output a URL that you can access your service on. It will look something like https://<id>.execute-api.eu-west-1.amazonaws.com/dev. It is worth noting that the /dev is appended to your URL, this is your stage which will be dev by default and is required.

To test our function is working, call it.

curl https://<id>.execute-api.eu-west-1.amazonaws.com/dev/

We can also use the sls CLI teardown our functions:

sls remove

Authorizers

Now that we can deploy some basic functions using serverless we could do with securing them using authorizers. We have two options for authorizers, AWS IAM or custom. We are going to focus on making custom authorizers.

For our first authorizer, we are going to expect auth bearer token of hellovia the HTTP header of Authorization . Obviously, this is a little oversimplified but it should show where you could add your custom code in to verify a token with OAUTH or something similar.

You can probably tell that our authorizer is not massively different to the function we wrote earlier. The main difference is that we are now using APIGatewayCustomAuthorizerRequest and APIGatewayCustomAuthorizerResponse instead of APIGatewayProxyRequest and APIGatewayProxyResponse. If we want to fail our Auth we have to return an error with an exact string value of Unauthorized, frustratingly as of this writing this is still a restriction with Lambda.

If we are allowing auth then we need to return a policy document that will allow access to our function.

We can unit test our authorizers in a similar way to our standard functions.

And again, run these tests with either go test or ginkgo.

What changes do we need to make to our serverless config?

We just add a new function with our auth function as our handler and then add that authorizer to our http event.

We also add resultTtlInSeconds to turn off auth caching, this is on my default and will cache auth across multiple requests to the same function, if you have different users authenticating with your function I have seen the second user receive the authorization identity of the first hitting the function so its best to play it safe unless you know your functions don’t use anything user specific.

Build your functions.

Deploy with sls deploy

Now lets test our functions again

$curl https://<id>.execute-api.eu-west-1.amazonaws.com/dev/
{"message":"Unauthorized"}
$curl https://<id>.execute-api.eu-west-1.amazonaws.com/dev/ -H "Authorization: hi"
{"message":"Unauthorized"}
$curl https://<id>.execute-api.eu-west-1.amazonaws.com/dev/ -H "Authorization: bearer hi"
{"message":"Unauthorized"}
$curl https://<id>.execute-api.eu-west-1.amazonaws.com/dev/ -H "Authorization: bearer hello"
Hello, World!

Auth context?

What if we need some context to our authorization? Some user details for example? Say hello to Authorization context. We have to re-write our function slightly to read the properties.

Here is an example:

We had to read our requests RequestContext and then access the Authorizer object from that context. From here we can choose to add some validation around our context and also use our context to make our Hello message personalised to the authorized user.

As before we want to make sure we can still write some unit tests…

Now, let’s write our new Authorizer, in order to prove our passed context we will allow any user to authorize with their name as the bearer token.

This time we are setting a context as part of our generatePolicy function. Notice we have also swapped our Unauthorized case to only return failed auth if we have an empty bearerToken.

Some more tests!

Lets modify our serverless config and get this deployed!

I have shown a version here that has both of our authorizers so we can do some testing that compares the two, you could have just overwritten your original functions and redeployed without any config changes.

### Old function
$curl https://<id>.execute-api.eu-west-1.amazonaws.com/dev/
{"message":"Unauthorized"}
$curl https://<id>.execute-api.eu-west-1.amazonaws.com/dev/ -H "Authorization: hi"
{"message":"Unauthorized"}
$curl https://<id>.execute-api.eu-west-1.amazonaws.com/dev/ -H "Authorization: bearer hi"
{"message":"Unauthorized"}$curl https://<id>.execute-api.eu-west-1.amazonaws.com/dev/ -H "Authorization: bearer hello"
Hello, World!
### New function
$curl https://<id>.execute-api.eu-west-1.amazonaws.com/dev/me
{"message":"Unauthorized"}
$curl https://<id>.execute-api.eu-west-1.amazonaws.com/dev/me -H "Authorization: bearer Bob"
Hello, Bob!
$curl https://<id>.execute-api.eu-west-1.amazonaws.com/dev/me -H "Authorization: bearer Geoff"
Hello, Geoff!
$curl https://<id>.execute-api.eu-west-1.amazonaws.com/dev/me -H "Authorization: bearer Bob"
Hello, Bob!

If you found this helpful and want more blogs like this please comment and let us know!

--

--