Securing AWS Lambda REST Endpoints

Cris Holdorph,
Software Architect

In our previous blog we introduced using AWS Lambda functions for simple REST services. Today, we are going to show how to secure access to your REST endpoints, using Lambda and API Gateway. Securing your services is critical before you allow your REST endpoints to be available on the public Internet. Unsecured endpoints can cause significant problems, including uncontrolled costs, incorrect data, and exposing private data. This post will show one approach to securing your Lambda REST services.

The API Gateway provides a built-in feature called Authorizers. With this feature we can write a custom Lambda function that will allow or disallow a request to continue. By using an Authorizer function, we eliminate the responsibility of each Lambda REST service to do the same kind of validation. This is extremely helpful as most of the time our Lambda functions only want to do the business logic they are associated with. In the older Service Oriented Architecture (SOA) style application, you would probably describe the code we write in the Lambda REST services as the "service" tier. Therefore, removing as many 'web' responsibilities as we can will lead to cleaner code.

Authorizers look at an HTTP header value and provide a response as to whether a request should be allowed to proceed or not. If the Authorizer allows the request to proceed, some optional information about the authorization can be provided. For example, an authorizer might be able to look up user data and provide that user data as part of the return information from the Authorizer. The API Gateway does not require an Authorizer to provide this additional information, but, at a minimum, it must specify whether the request is allowed or not. To allow a request, both the Amazon resource identifier (ARN) and the permission of "allow" must be returned. If the Authorizer wants to refuse a request, this can be done two ways. Either by returning the permission of "deny" for the same ARN, or simplifying not returning the correct format for the authorization request at all.

Lets begin creating our Authorizer by looking at the Java code that will run in Lambda. The code covered in this article can be obtained on github at:

https://github.com/holdorph/authlambda

To check the code out using git on the command line you would type

git clone git@github.com:holdorph/authlambda.git

In this code we follow the same pattern as we did in our HelloWorld Lambda example. We have a main class which implements the AWS RequestHandler interface. As part of implementing that interface we specify what our request and response classes will be. Both of these Request and Response classes will be serialized to or from JSON. This is a very important detail as we will see, because the contract the Authorizer must meet is case sensitive. We must understand how AWS serializes Java classes to JSON in order to get the correct capitalization within the output JSON and in order to get the incoming JSON to correctly populate the Request object. The following Amazon article contains two sections that give examples of the input / output JSON for an authorizer.

http://docs.aws.amazon.com/apigateway/latest/developerguide/use-custom-authorizer.html

See sections "Input to an Amazon API Gateway Custom Authorizer" and "Output from an Amazon API Gateway Custom Authorizer".

With that JSON in mind you can look at the "AuthorizerRequest" (input) and "AuthorizerResponse" (output) classes. These classes make use of some other classes, in order to handle the nested JSON structures. In particular pay close attention to the fields in the JSON that begin with a capital letter.  Because the normal Java class to JSON pattern is to convert a Java bean with the method "getName" to the JSON field "name", there is no way to map JSON fields that begin with a capital letter using standard Java POJO 'get' and 'set' methods. Instead, in these cases, public fields had to be used without the corresponding POJO 'get' and 'set' methods (see AuthorizationStatement.java lines 6-10 for an example).

Understanding the input and output of the Authorizer is more than half the battle. After that, the logic of the actual RequestHandler is pretty straight forward. Look at the input data, decide if the request is allowed, set the Response to Allow or Deny and you're done. This sample authorizer looks at the input authorization token and simply decides to allow or deny the request based on how many characters are in the token. This is not very good security, but is simply provided as an example. In practice we would probably use a JSON Web Token (JWT) that could be verified by looking at the JWT signature.

We can build this Lambda function the same way we built the HelloWorld one in the previous article.

gradle shadowJar

Once we have a jar file, we can create the Lambda function in the same way as we did for the HelloWorld example. At this stage, other than the restrictions on the "input" and "output" there is nothing special about this Lambda function.

After the Lambda function is created and uploaded, we need to configure API Gateway to use our Authorizer. To do this we will go back to our HelloApi that we defined in the API Gatway in the previous article. Now select the "Authorizers" sub heading for this API.

Authorizer1

We want to choose our new custom authorizer Lambda function we created above, give the Authorizer a name, and then press the blue "Create" button.

Authorizer2

You will be prompted that you are going to be giving API Gateway permission to execute your Lambda Authorizer function. This popup is expected. You definitely want API Gateway to access your Lambda function, so click "OK" on this screen.

authorizer3 image

Now go back to the "Resources" section of your API definition and click on the "ANY" method that was created in the previous article. On this screen we will be clicking on the "Method Request" part of this method defition.

authorizer4 image

After clicking into the "Method Request," we want to click the pencil icon next to the Authorization entry in the "Settings" section.

authorizer5 image

Next we want to choose the "ExampleAuthorizer" we defined earlier from the drop down menu.

authorizer6 image

Finally, click the grey checkmark next to the Authorizer you have chosen.

authorizer7 image

We are now ready to redeploy our API defintion. Go up to "Actions" and select "Deploy API."

authorizer8 image

Choose the *Deployment stage* used previously, in my case that was "test," then press the blue "Deploy" button.

authorizer9 image

You are now ready to test your API. We can run the same style curl command we previously ran. This time the command should fail, because the REST call is protected by our Authorizer and we are not providing the proper headers needed to pass the Authorizer.

authorizer10 image

This command should result in the error:

{"message":"Unauthorized"}

Now if we include the proper header we should see the command succeed again:

authorizer11 image

Since we are including the additional "Authorization: 1234" header, the header value is greater than 2 characters and less than 7 characters (line 20 of Authorizer.java - https://github.com/holdorph/authlambda/blob/master/src/main/java/example/Authorizer.java#L20), we should expect the call to succeed (and it does).

{"hello":"Cris"}

Notice above, when the header was missing we received the message "Unauthorized." If the authorization header is included, but does not validate we get a different message. For example, the following command sends an authorization header value that is 10 characters long.

authorizer12 image

This command results in the request being answered with:

{"Message":"User is not authorized to access this resource"}

The AWS API Gateway Authorizer functionality is smart enough to check for the presence of the Authorization header you specified. If the header is not present, then your Lambda function doesn't even need to be called.

Amazon AWS API Gateway offers the ability for us to set a Lambda function as an authorizer for our REST services. Using an Authorizer is one way you can secure your "Serverless" REST microservices. Authorizers are not the only security you need to consider, but it is probably the first one you need to put in place when using AWS API Gateway.

Cris Holdorph

Cris Holdorph

Software Architect
Cris Holdorph has been a Unicon employee for fifteen years. Cris holds a degree in Computer Science from Michigan State University and is currently a Software Architect at Unicon, Inc. Unicon, Inc. is a leading supplier of Professional Services and Support for the uPortal, Sakai and CAS platforms. Cris has participated in training several university's on uPortal and Portlet development. Cris has also served as an adjunct faculty member at Arizona State University.
Top