Architecture Diagram

Hello and welcome back to Going Serverless with AWS, In part 1 we learned what Serverless means and what we are going to build. In part 2 We will learn how to create an RDS instance with the AWS cli tools, and build our first lambda function, package it, deploy it, and invoke it using the AWS cli tools.

In the spirit of keeping this as simple as possible you will be able to copy and paste all commands and get a working setup however this will be super insecure as passwords and things will be set using the AWS cli. DO NOT RUN THIS AS A PRODUCTION APPLICATION… SERIOUSLY DON’T

So lets get started!

Creating your Database

What is RDS?

Amazon Relational Database Service (Amazon RDS) makes it easy to set up, operate, and scale a relational database in the cloud. It provides cost-efficient and resizable capacity while automating time-consuming administration tasks such as hardware provisioning, database setup, patching and backups. It frees you to focus on your applications so you can give them the fast performance, high availability, security and compatibility they need.

Creating our databse

To create our database as promised we will use the AWS Command Line Tools. Please make sure you have run ‘aws configure’ and selected a region close to you and setup your access keys.

To create the database we use the rds command from the aws command line tools. For the sake of simplicity we will make this publicly accessible.

aws rds create-db-instance \
    --db-instance-identifier mySQLForLambdaTut \
    --db-instance-class db.t2.micro \
    --engine MySQL \
    --allocated-storage 5 \
    --publicly-accessible \
    --db-name ServerlessDB \
    --master-username kscot-servereless \
    --master-user-password as3cur3p4ss \
    --backup-retention-period 3

The command we have just run will return JSON with details about the RDS Instance creation. Now we need to think about creating our tables for our users. In order to do this we need to get our Endpoint to connect to. This can be acheived by going to the AWS RDS Console or calling aws rds describe-db-instances –db-instance-identifier=mysqlforlambdatut and checking the Endpoint Address in the JSON.

Endpoint": {
                "HostedZoneId": "Z1TTGA775OQIYO",
                "Port": 3306,
                "Address": "mysqlforlambdatut.<id>.eu-west-2.rds.amazonaws.com"
            },

Now we have our endpoint we can connect to it with an SQL Tool of our choice so we can create our schema.

CREATE TABLE users
(
    id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
    email VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL,
    salt VARCHAR(15)
);
CREATE UNIQUE INDEX users_id_uindex ON users (id);
CREATE UNIQUE INDEX users_email_uindex ON users (email);

We now have our users table setup and ready. Now we can start building our Lambda function.

User Registration with AWS Lambda

What is Lambda?

AWS Lambda lets you run code without provisioning or managing servers. You pay only for the compute time you consume – there is no charge when your code is not running. With Lambda, you can run code for virtually any type of application or backend service – all with zero administration. Just upload your code and Lambda takes care of everything required to run and scale your code with high availability. You can set up your code to automatically trigger from other AWS services or call it directly from any web or mobile app.

Writing our first Function

What we are going to create is called a Deployment Package. This allows us to write complex lambda functions locally using any library available for our language of choice. I am focusing on Python for this series however Lambda supports Java, C#, NodeJS and Python. So lets get started.

We are going to start with out Registration function createUser. We need users to authenticate against so we need to register them first. Instead of manually adding adding a new user its a good starting point for our Lambda function adventure.

Create an empty directory called createUser for this part of the project.

Third Party Deps

Part of our microservice we need to use the PyMySql library not provided as part of AWS Lambda. To do this we should create a requirements.txt file and install the deps.

Our requirements.txt file is

PyMySql==0.7.11

To install these into our project directory we call

pip install -t . -r requirements.txt

This should have created the pymysql showing us the library can be used in our project.

Python Code

Now we have our requirements we need to write the code. Simply use the example code provided below. Save this as app.py

import sys
import logging
import pymysql
import string
import random
import hashlib

# rds settings
rds_host = "rds-instance-endpoint"
name = "kscotservereless"
password = "as3cur3p4ss"
db_name = "ServerlessDB"

logger = logging.getLogger()
logger.setLevel(logging.INFO)

try:
    conn = pymysql.connect(rds_host, user=name, passwd=password, db=db_name, connect_timeout=5)
except:
    logger.error("ERROR: Unexpected error: Could not connect to RDS")
    sys.exit()

logger.info("SUCCESS: Connection to RDS succeeded")


def handler(event, context):
    """
    create a user in our RDS Instance
    """

    try:
        with conn.cursor() as cur:
            salt = generate_salt()
            salted = encrypt_password(event['password'], salt)
            sql = "INSERT INTO `users` (`email`, `password`, `salt`) VALUES (%s, %s, %s)"
            cur.execute(sql, (event['email'], salted, salt))
        conn.commit()
        return "{statusCode: 200, message: 'User created succesfully'}"
    except pymysql.DataError as e:
        logger.error("Error : %s" % e.message)
        return "{statusCode: 500, message: 'Duplicate Entry'}"


def generate_salt():
    """
    generate a random string to act as a salt
    """
    return ''.join(random.choice(string.ascii_uppercase) for _ in range(12))

def encrypt_password(password, salt):
    m = hashlib.sha256()
    m.update(password + salt)
    hashed = m.hexdigest()
    return hashed

This isn’t a Python tutorial so I won’t be explaining this line by line. However what it does is uses the event dictionary passed from lambda to the handler in our application.

The handler contains an email and password, We take the password and Hash it with sha256 together with a randomly generated SALT.

Lambda Roles

In order for our Lambda function to access the RDS instance we need to give it a role allowing it to communicate with the internet and have basic execution functionality. I have prepared a JSON IAM role policy to help you here.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "ec2:CreateNetworkInterface",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DeleteNetworkInterface"
            ],
            "Resource": "*"
        }
    ]
}

to import this role run

aws iam create-role --role-name LambdaRole --assume-role-policy-document file://LambdaTut-Policy.json

Take note of the ARN for this role as you need it to deploy your function.

The Deployment Package

What you upload to Lambda is called a Deployment Package. To create this zip up all the files inside your project directory. DO NOT ZIP THE ACTUAL DIRECTORY, MAKE SURE ITS ONLY THE FILES.

Now we upload it to Lambda, In order to do this we need to use the default VPC and Have a security group that allows external access.

To associate to a VPC you associate to subnets, You can find a list of your subnets using aws ec2 describe-subnets

Creating a security group goes beyond the scope of this guide. However its easy to make one with the AWS Console, Make sure it has global outbound connections allowed (Use your RDS Security group if you want).

Deploying the package

To deploy your package make sure you know where your zip file is. Using the cli you can deploy with ease. Make sure you have your Subnet IDs and security group ID, and role ARN

aws lambda create-function \
--function-name createUser \
--zip-file fileb://createUser.zip \
--role arn:aws:iam::<accountID>:role/lambdaTut \
--vpc-config SubnetIds=subnet-b68883ce,subnet-f8032bb2,SecurityGroupIds=sg-a97893c0 \ 
--handler app.handler \ --runtime python2.7 \ --timeout 15 \ --memory-size 128

This will output some JSON describing your newly created Lambda function.

Testing the Deployment

There are a couple of ways you can test the deployment. The easiest being the AWS Console. However I will be testing it using the CLI.

aws lambda invoke --function-name createUser --invocation-type Event --payload '{"email": "a@user.com", "password": "Test"}'

If all has gone well you will be shown some JSON with statusCode 202 letting you know that it worked as planned.

You can verify this by Launching mysql and selecting all from users.

mysql> select * from users;
+----+---------------+------------------------------------------------------------------+--------------+
| id | email     | password                                                             | salt         |
+----+---------------+------------------------------------------------------------------+--------------+
| 1  | a@user.com | dc3907ff094389c7699c1069cf8778f002e9eb42ecdcb8999d9d1fe172da193c    | LVTOLCFCRWCZ |
+----+---------------+------------------------------------------------------------------+--------------+

Congratulations you can Register a user in RDS using Lambda!

Stay tuned for part 3 Where we will build our API Gateway for User Registration, Test it using cURL and build our registration web interface hosting it all in S3 so you don’t have to worry about servers.

One thought to “Going Serverless with AWS – Serverless User Authentication – Part 2”

Leave a Reply