AWS Lambda: Basic Authentication support for OrangeHRM MySQL API
Use AWS Lambda Function pair to allow for Basic Authentication

AWS Lambda: Basic Authentication support for OrangeHRM MySQL API

In this article, we will explore an experimental strategy to implement Basic Authentication support for a Lambda Function, by using a pair of Functions with AWS Signature Version 4 and AWS IAM Policy. While Basic Authentication is not as secure as HMAC or AWS Signature V4, it is more secure than having a public Lambda Function URL with Auth type set to NONE as our main Function.

The main reason why we are exploring this option: Many integration products including Identity & Access Management products do not support HMAC or AWS Signature for all Connector types. So instead of using a publicly available main Lambda Function, we can explore a 2-Function strategy that is a bit more secure.

We still use Auth type set to NONE for our gatekeeper Function, but our main Function uses Auth type set to AWS_IAM. We could say that we are leveraging layered security, to mitigate some of the risk.

This article is a follow up article to this one: How to :: MySQL to REST API with AWS Lambda and nodejs

To illustrate the strategy based on 2 Functions, we will be using OrangeHRM Open Source and a Function that provides a SQL Query REST API as described in the previous article linked above. For the MySQL function, you need a mysql2 layer.

Article content
OrangeHRM Turnkey AWS AMI
Article content
We need to modify the config for MariaDB
Article content
We need to add the private IP for the Instance as a bind-address to allow private remote connections.
Article content
Creating a MySQL service account.

For more information on how to configure the MySQL Function, refer to the linked article above.

Article content
The MySQL Function must be configured for the same VPC subnet as our MySQL database.

Before we create our new Basic Authentication Function, we need 2 NodeJS module layers: One for axios and one to support AWS Signature V4. You can refer to this article for the steps: AWS Lambda: How to create a nodejs layer for axios

Article content
Use npm to install signature-v4 and sha256 modules in layer.
Article content
Use zip to package the layer.

Now we need to create a new Lambda Function with NodeJS and add the layers.

Article content
Basic Authentication Lambda Function with both layers added.

This is the NodeJS code for our Basic Authentication Function:

import axios from 'axios';
import { SignatureV4 } from '@smithy/signature-v4';
import { Sha256 } from '@aws-crypto/sha256-js';

export const handler = async (event) => {
console.log('####### event = '+JSON.stringify(event));

  const API_URL = 'https:/'+event.rawPath;
  const apiUrl = new URL(API_URL);
  const method = event.requestContext.http.method;

// Extract values from Authorization Header
  const base64token = event.headers.authorization.substring(6);
  const base64payload = Buffer.from(base64token, 'base64').toString('ascii'); 
  console.log('base64token = '+base64token+'.  base64payload = '+base64payload);
  const base64obj = JSON.parse(base64payload);
  const AWS_ACCESS_KEY_ID = base64obj.AWS_ACCESS_KEY_ID;
  const AWS_SECRET_ACCESS_KEY = base64obj.AWS_SECRET_ACCESS_KEY;
  const AWS_SERVICE = base64obj.AWS_SERVICE;
  const AWS_REGION = base64obj.AWS_REGION;

const sigv4 = new SignatureV4({
  service: AWS_SERVICE,
  region: AWS_REGION,
  credentials: {
    accessKeyId: AWS_ACCESS_KEY_ID,
    secretAccessKey: AWS_SECRET_ACCESS_KEY
  },
  sha256: Sha256,
});

if(event.body){
  const signed = await sigv4.sign({
    method: method,
    hostname: apiUrl.host,
    path: apiUrl.pathname,
    protocol: apiUrl.protocol,
    body: event.body,
    headers: {
      'Content-Type': event.headers['content-type'],
      'Content-Length': event.headers['content-length'],
      'host': apiUrl.hostname,
      'mysql_host': event.headers.mysql_host,
      'mysql_user': event.headers.mysql_user,
      'mysql_password': event.headers.mysql_password,
      'mysql_database': event.headers.mysql_database,
    },
  });
  console.log('signed = '+JSON.stringify(signed));

  try {
    const { data } = await axios({
      ...signed,
      data: event.body,
      url: API_URL, // compulsory
    });

    console.log('Successfully received data: ', data);
    return data;
  } catch (error) {
    console.log('An error occurred', error);

    throw error;
  }
} else{
  const signed = await sigv4.sign({
    method: method,
    hostname: apiUrl.host,
    path: apiUrl.pathname,
    protocol: apiUrl.protocol,
    headers: {
      host: apiUrl.hostname, // compulsory
    },
  });
  console.log('signed = '+JSON.stringify(signed));

  try {
    const { data } = await axios({
      ...signed,
      url: API_URL,
    });

    console.log('Successfully received data: ', data);
    return data;
  } catch (error) {
    console.log('An error occurred', error);

    throw error;
  }
  
}
};        

The Function code is generic, except for the 4 HTTP Headers (signed) that needs to be submitted to our MySQL Lambda Function:

      'mysql_host': event.headers.mysql_host,
      'mysql_user': event.headers.mysql_user,
      'mysql_password': event.headers.mysql_password,
      'mysql_database': event.headers.mysql_database,        

We also want to configure our MySQL Lambda Function for AWS_IAM Auth type:

Article content
The MySQL Lambda Function needs to be set to AWS_IAM Auth type.

The Basic Function extracts the AWS Signature values from a base64 encoded Basic Authentication token, that we need to submit via the Authorization HTTP Header.

We need to create an AWS User and assign a Policy to the User, e.g. via a Role. Here, we are assigning the Policy directly to the User:

Article content
Policy to allow access to the MySQL Function.

The Authorization HTTP Header value is set to Basic {base64 encoded token}. To create the token using a similar string as shown below, you can use this free tool.

{"AWS_ACCESS_KEY_ID":"AQDSTBKABCDEFTXET","AWS_SECRET_ACCESS_KEY":"TGabcKZH5a2efg+SXqDk/nUABCDJLpl7ABcsbABC","AWS_SERVICE":"lambda","AWS_REGION":"us-east-1"}        

We will use Postman to test our Function. In a real life scenario, we can use any product that can support an Authorization HTTP Header. The URL consists of a concatenation of the 2 Function URLs: We need to remove https:// for the 2nd MySQL Function URL:

https://abcdef....lambda-url.us-east-1.on.aws/zxcvb....lambda-url.us-east-1.on.aws        
Article content
Postman with the Authorization and MySQL HTTP Headers.

The body is a JSON document that contains a SQL Query:

{"Query":"select emp.employee_id as empid ... from ..."}        
Article content
Postman POST request and response.

The next step would be to configure the product of your choice to query the MySQL database using our Basic Function. I tested this in my lab and it allowed me to successfully query MySQL using a Web Service connector with an Authorization HTTP Header, and the 4 MySQL Headers.

I hope that you found this article interesting, and that it will inspire you to explore and evolve your own strategy for adding security to Lambda Functions when AWS Signature is not available as an option.

To view or add a comment, sign in

More articles by Michel Bluteau

Explore content categories