Adventures in AWS Serverless: Enabling Users to Manage their Lab Environments

Having visibility and control over your penetration testing lab environment is a key component in its effectiveness as a training and research tool. To provide this to our customers, we recently rolled out a lab dashboard application with deep hooks into the underlying AWS infrastructure of our lab environments. The dashboard allows customers to perform a number of valuable tasks:

  • View lab and individual system states
  • Power on/off systems
  • Snapshot lab and system states
  • Revert to lab and system snapshots
  • Create and revoke VPN certificates

Viewing an individual lab environment in the dashboard application.

We decided to build the application using Amazon’s serverless application model. This eased the integration of the application with backend AWS resources and let us have granular control over what actions our users could perform on their lab instances. In this post, we’ll share our experience with the various AWS services used in our dashboard application and how they work together to provide powerful functionality to our users.

High Level Architecture

AWS offers a lot of different services, and there was a pretty steep learning curve to get our serverless architecture off the ground. Thankfully, Amazon also provides excellent documentation which helped us make our design decisions. We ended up using a total of nine services to build out the dashboard application.

  1. S3
  2. CloudFront
  3. Cognito
  4. API Gateway
  5. Lambda
  6. DynamoDB
  7. Step Functions
  8. Systems Manager
  9. Elastic Cloud Compute (EC2)**

**EC2 forms the backbone of our lab environments, and EC2 resources are the target of most of the dashboard application functionality.

At a high level these services work together like this:

S3 and CloudFront serve the static website content ➔ Cognito facilitates user authentication and provides an authenticated token to the user ➔ API Gateway accepts authenticated calls from users to perform dashboard actions ➔ Lambda receives the calls from API Gateway and interacts with data in DynamoDB, makes changes to EC2 resources, or configures EC2 resources via Systems Manager ➔ Complicated tasks are orchestrated by Step Functions.

Now that you have an overall picture of the application architecture, let’s walk through the application flow service by service to get a better understanding of what function each service is fulfilling and how they all work together.

S3 & CloudFront

These services handle the storage and distribution of the static content to our users. S3 (Simple Storage Service) handles the actual storage of the website content. CloudFront, Amazon’s content distribution network (CDN), caches the content stored in S3 and efficiently distributes it to users visiting the site. This helps the site load quickly, and also allows us to use an SSL certificate.

After a user visits the site, they need to authenticate to perform any actions on their lab environment(s).

Cognito & API Gateway

Cognito and API Gateway work in tandem to handle user authentication to the dashboard, and to direct authenticated calls to the backend AWS services performing the bulk of the work.

Cognito’s main function is to keep track of our application users and passwords in a User Pool. Users authenticate directly to Cognito through client-side JavaScript calls to receive an authentication token to use for authenticated application calls. This allows us to verify user authentication without having to worry about maintaining databases of hashed user passwords; a win! Cognito also allows us to easily integrate extra security features like multifactor authentication and password complexity requirements. As a security company, this is essential for us and made the decision to use Cognito for our user authentication an easy one.

After receiving an authentication token, users can perform authenticated application functionality by making requests to the API Gateway. Each API Gateway resource validates a user’s authorization bearer token before forwarding any client-side input to our Lambda functions. So, we can safely assume that any Lambda function invocations originating from the API gateway are coming from authenticated users that should have access to our application’s functionality. Then, our Lambda functions just need to check whether the already authenticated user is authorized to perform actions on the resources specified in their request. This decoupling of authentication and authorization is an important aspect of application security and failing to distinguish between the two is the root cause of many security vulnerabilities.

Cognito and API Gateway work well together to handle our user authentication. They keep things simple and allow us to perform more granular authorization checks in other parts of the application infrastructure.

Lambda, DynamoDB, Step Functions & Systems Manager

Lambda, DynamoDB, Step Functions and Systems Manager perform the bulk of the heavy lifting in our dashboard application. There are several different application flows, but they generally follow the same steps:

After receiving an authenticated application request from the API Gateway, Lambda first performs two actions for every request.

  1. Validate User Input – if the user has sent along improperly formatted data or junk, we can stop immediately and return an error.
  2. Perform Authorization Checks – user provided input often includes EC2 resources, and we need to check if the user is authorized to interact with the resources they’ve specified. Lambda makes calls to DynamoDB for user specific data to accomplish this.

After these two checks have been passed, Lambda continues along and performs the application functionality of the request. Simple tasks usually consist of a single Lambda function making updates to DynamoDB, polling EC2 for data on specific lab resources or using Systems Manager to make a configuration change to an EC2 resource. More complex tasks rely on other actions being completed before they can continue (volume creation, system state change, etc.). In these cases, we use Step Functions to orchestrate data flow across multiple Lambda functions to eventually reach our desired end state.

Hopefully you've enjoyed our adventure into AWS's serverless application model as much as we did! It's been a fulfilling process to be able to provide our users with meaningful control over their lab environments. If you think you might be interested in checking out the dashboard application, let us know! We're happy to set you up with access and a full demo lab environment to mess around with!