introduce
While I was playing around with my pet project Kyiv Station Walk , I noticed that deleting test data manually was tedious and I needed to come up with a concept for an admin page. This requires some kind of authentication endpoint. Some super lightweight services that check login and password as a pair of superuser credentials.
Serverless is great for this simple nanoservice. This comes with some cost savings as I expect my admin pages for my less popular services to have a lower execution rate, so serverless gives me that almost for free. Also, I think this gives me some architectural benefits, as it allows me to separate my core domain from crosscutting concerns. For my task, I decided to use AWS Lambda. I also decided to use Go because of its minimalistic nature which is useful for Lambda instantiation.
set up
Our lambda function will be called externally via HTTP, so we put the HTTP gateway in front of it so it looks like this in the AWS console.
project structure
To separate our authentication logic from the FaaS internals, our project will have two files: auth.go is where the authentication logic lives, and main.go is where our logic integrates with AWS lambda.
The content of main.go looks like this:
For this code to work, we need "github.com/aws/aws-lambda-go/lambda"
package.json.
In order to use our endpoint from the outside, because we have to provide a specially formatted response to the API Gateway. For this reason, we also install github.com/aws/aws-lambda-go/events
the package.
Let's highlight an example of a successful response that you might have noticed in the snippet above:
The error response looks like this:
<span style="color:#000000"><span style="background-color:#fbedbb">events.APIGatewayProxyResponse{
StatusCode: status,
Body: http.StatusText(status),
}</span></span>
verify
For our purposes, we'll omit the use of persistent storage, as one pair of credentials will suffice. Still, we need to hash the stored passwords with a hash function, which will allow defenders to verify passwords in acceptable time, but attackers will need a lot of resources to guess passwords from hashes. Argon2 is recommended for such tasks. So to get started, we need "github.com/aws/aws-lambda-go/lambda"
packages.
<span style="color:#000000"><span style="background-color:#fbedbb">func main() {
lambda.Start(HandleRequest)
}</span></span>
Argon2 is implemented "golang.org/x/crypto/argon2"
so authentication is very simple.
Notice how we return the same message for wrong logins and wrong passwords to disclose as little information as possible. This allows us to prevent account enumeration attacks.
build it:
<span style="color:#000000"><span style="background-color:#fbedbb">go build -o main main.go
And zipping it
~\Go\Bin\build-lambda-zip.exe -o main.zip main</span></span>
use windows
If you are a Windows user, you will need to set the following environment variables before building:
Using environment variables
We can now see hardcoded credentials in the codebase. This is a bad practice as they fetch credentials automatically .
You can leverage environment variables with the help of package.json os
.
<span style="color:#000000"><span style="background-color:#fbedbb">login := os.Getenv(<span style="color:#800080">"</span><span style="color:#800080">LOGIN"</span>)
salt := os.Getenv(<span style="color:#800080">"</span><span style="color:#800080">SALT"</span>)</span></span>
Here's how you set them up in the AWS console.
JWT generation
Once the service verifies that the credentials are valid, it issues a token allowing its holder to act as a superuser. For this, we'll use JWT , which is the de facto standard format for access tokens.
We need the following packages:
<span style="color:#000000"><span style="background-color:#fbedbb">"github.com/dgrijalva/jwt-go"</span></span>
The JWT generation code looks like this:
Since an adversary intercepting such a token could be acting on behalf of a superuser, we do not want this token to be valid indefinitely, as this would grant the adversary unlimited privileges. So we set the token expiration time to one hour.
Test API Gateway
At this point, our API is ready to use. Here's a short snippet in the main service that only deletes a route if the user has sufficient permissions.
Minimize attack surface
At this point, our function has some bugs, so we have to do some extra work on the API Gateway.
endpoint limit
The default setting is too high for authorization functions that are not expected to be called very often. Let's change that.