Amazon RDS Proxy – Improved Application Security, Resilience and Scalability

Amazon RDS Proxy is a fully managed, highly available database proxy for Amazon Relational Database Service (RDS) that makes applications more scalable, more resilient to database failures, and more secure.

https://aws.amazon.com/rds/proxy/

In this article I will demonstrate how you can configure an Amazon RDS Proxy for an Amazon Aurora database. With the provided Terraform code, you can launch a sample database to test RDS Proxy.

This short video presentation by AWS explains the benefits of RDS Proxy and demonstrates how it can be configured with the AWS console.

Database

The Terraform code at aw5academy/terraform/rds-proxy will create the following resources:

We will use the EC2 instance as a mock for an application that needs to communicate with our Aurora database.

Note: at the time of writing this article, Terraform does not support RDS Proxy resources. So we will need to manually create this component from the AWS console.

Let’s first deploy our Terraform code with:

git clone https://gitlab.com/aw5academy/terraform/rds-proxy.git
cd rds-proxy
terraform init
terraform apply

Once Terraform has been applied, it is worth examining the security groups that were created.

Inbound security group rules for our Aurora database
Inbound security group rules for our RDS proxy

We can see that the Aurora database only allows connections from the Proxy and the Proxy only allows connections from the EC2 instance.

Additionally, a Secrets Manager secret was created. Our RDS Proxy will use the values from this secret to connect to our database. Note how it is the proxy alone that uses these credentials. We will see later that our application (the EC2 instance) will use IAM authentication to establish a connection with the RDS proxy and so the application never needs to know the database credentials.

Secrets Manager secret containing our database credentials

RDS Proxy

Now we can create our RDS proxy from the AWS RDS console. During the creation of the proxy, provide the following settings

  1. Select PostgreSQL for Engine compatibility;
  2. Tick Require Transport Layer Security;
  3. Select rds-proxy-test for Database;
  4. Select the secret with prefix rds-proxy-test for Secrets Manager secret(s);
  5. Select rds-proxy-test-proxy-role for IAM role;
  6. Select Required for IAM authentication;
  7. Select rds-proxy-test-proxy for Existing VPC security groups;
Create RDS Proxy Settings
Create RDS Proxy Settings

Now wait for the proxy to be created. This can take some time. Once complete, obtain the RDS Proxy endpoint from the console which, we will use to connect to from our EC2 instance.

Application

Let’s test our setup. SSH into the EC2 instance with:

ssh -i rds-proxy-test.pem ec2-user@`terraform output ec2-public-ip`

From the terminal, set the RDSHOST environment variable. E.g.

export RDSHOST=rds-proxy-test.proxy-abcdefghijkl.us-east-1.rds.amazonaws.com

We can now test our connection to the database via the RDS proxy with:

./proxy.sh
Terminal output from successful connection to the database via the RDS proxy

Success! The proxy.sh script uses the psql tool and is obtaining the permissions to connect to the proxy via the aws rds generate-db-auth-token AWS CLI command. We can also use generate_db_auth_token from boto3 for Python:

python3.8 proxy.py
Terminal output from successful connection to the database via the RDS proxy

Wrap-Up

The RDS Proxy feature can improve application security as we have seen, with the proxy alone having access to the database credentials and the application using IAM authentication to connect to the proxy.

Application resilience is improved since RDS Proxy improves failover times by up to 66%.

Lastly, your applications will be able to scale more effectively since RDS Proxy will pool and share connections to the database.

To cleanup the resources we created, first delete the RDS Proxy from the console and then from your terminal, destroy the Terraform stack with:

terraform init
terraform destroy

Configure a Desktop Environment For an Amazon Linux EC2 Jumpbox

In this article I will show how you can launch an Amazon Linux EC2 instance with a desktop environment that will serve as a jumpbox. Connections to this jumpbox will be made through RDP via a session manager port tunneling session. By using session manager, our EC2 instance’s security group does not require ingress rules allowing RDP or other ports to connect, thus improving the security of the jumpbox.

Recommended Reading

Before continuing with this article I would strongly recommend reading my earlier article Access Private EC2 Instances With AWS Systems Manager Session Manager. That article will explain the fundamental workings of session manager and shows how to deploy resources to your AWS account that will be required for setting up the jumpbox described in this article.

Terraform

Firstly, if you haven’t already done so, deploy the Terraform code at aw5academy/terraform/session-manager to setup session manager. Be sure to also follow the Post Apply Steps documented in the README.md.

When the session-manager stack is deployed we need to read some of the Terraform outputs as we will need their values for the jumpbox stack’s input variables. We can retrieve the outputs and set them as environment variables with:

export TF_VAR_private_subnet_id=`terraform output private-subnet-id`
export TF_VAR_vpc_id=`terraform output vpc-id`

Now we can deploy the jumpbox Terraform code:

cd ../
git clone https://gitlab.com/aw5academy/terraform/jumpbox.git
cd jumpbox
terraform init
terraform apply

After the stack deploys, wait approximately 5 minutes. This is to allow time for the converge of the aw5academy/chef/jumpbox Chef cookbook which, is part of the EC2 instance’s user data. This cookbook installs the MATE desktop environment on the Amazon Linux instance. Also see here for more information on installing a GUI on Amazon Linux.

Jump

Let’s make sure we can connect to the jumpbox with a terminal session. The jump.sh script can be used:

bash jump.sh

You should see something like the following:

Now we can try a remote desktop session. Terminate the terminal session with exit and then run:

bash jump.sh -d

You should now see the port forwarding session being started:

Also printed are the connection details for RDP. Open your RDP client and enter localhost:55678 for the computer to connect to and provide the supplied user name. Check the Allow me to save credentials option and click Connect:

Provide the password at the prompt and click OK:

Success!

Behind The Scenes

An explanation of what is occurring when we use our jump.sh script…

In order to start an RDP session the client needs to know the username and password for an account on the jumpbox. Rather than creating a generic account to be shared among clients we dynamically create temporary (1 day lifetime) accounts. This is accomplished through the following actions:

  • The client creates a random username using urandom;
  • The client creates a random password using urandom;
  • The client creates a SHA-512 hash of the password using openssl;
  • The client puts the hashed password into an AWS Systems Manager Parameter Store encrypted parameter with a parameter name including the username;
  • The client uses the send-command API action to run the /root/create-temp-user.sh script on the jumpbox passing the username as a parameter;
  • The jumpbox retrieves the hashed password from parameter store;
  • The jumpbox deletes the hashed password from parameter store;
  • The jumpbox creates an account with the provided username and the retrieved hash of the password;
  • The jumpbox marks the account and password to expire after 1 day;

With these steps, the password never leaves the client and is always stored either encrypted and/or hashed and is only stored for as long as it is required.

Summary

That’s all there is to it. After your jumpbox is enabled you can configure your private applications to accept traffic from the jumpbox’s security group. The chromium browser can then be used to access these applications securely. I hope you find this article useful.

Access Private EC2 Instances With AWS Systems Manager Session Manager

In this article I will demonstrate how you can connect to EC2 instances located in private subnets by using AWS Systems Manager Session Manager.

Session Manager is a fully managed AWS Systems Manager capability that lets you manage your EC2 instances, on-premises instances, and virtual machines (VMs) through an interactive one-click browser-based shell or through the AWS CLI. Session Manager provides secure and auditable instance management without the need to open inbound ports, maintain bastion hosts, or manage SSH keys.

https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html

Terraform

The Terraform code at aw5academy/terraform/session-manager will provision the resources for us.

Deploy the stack by issuing the following commands:

git clone https://gitlab.com/aw5academy/terraform/session-manager.git
cd session-manager
terraform init
terraform apply

Post Apply Steps

Some things are not configured in Terraform and must be set manually. These are the Session Manager preferences. To set these:

  • Login to the AWS console;
  • Open the Systems Manager service;
  • Click on ‘Session Manager’ under ‘Instances & Nodes’;
  • Click on the ‘Preferences’ tab;
  • Click ‘Edit’;
  • Enable KMS Encryption and point to the alias/session-manager key;
  • Enable session logging to S3 bucket ssm-session-logs... with encryption enabled;
  • Enable session logging to CloudWatch log group /aws/ssm/session-logs with encryption enabled;
  • Save the changes;

Session Manager Plugin

To be able to use Session Manager from the AWS CLI you also need to install the Session Manager Plugin.

Start Session

Let’s try it out. First, we will use the AWS CLI to launch a new EC2 instance in the private subnet that was created by the Terraform code. This instance will have no key pair and will use the VPC’s default security group which allows no inbound traffic from outside the VPC.

aws ec2 run-instances \
    --image-id $(aws ssm get-parameters --names /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 --query 'Parameters[0].[Value]' --output text --region us-east-1) \
    --instance-type t3a.nano \
    --subnet-id $(terraform output private-subnet-id) \
    --iam-instance-profile Name=session-manager \
    --output json \
    --region us-east-1 \
    --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=session-manager-test}]' \
    --count 1 > /tmp/ssm-test-instance.json

Next, run this command to wait for the instance to become ready:

while true; do if [[ $(aws ssm describe-instance-information --filters "Key=InstanceIds,Values=`cat /tmp/ssm-test-instance.json |jq -r .Instances[0].InstanceId`" --region us-east-1 |jq -r .InstanceInformationList[0].PingStatus) == "Online" ]]; then echo "Instance ready." && break; else echo "Instance starting..." && sleep 5; fi; done

When the instance is ready we can connect to it with session manager:

aws ssm start-session --target $(cat /tmp/ssm-test-instance.json |jq -r .Instances[0].InstanceId) --region us-east-1

That’s it! You are now connected to a private EC2 instance in your VPC which, has no public IP, no key pair and no inbound access from outside the VPC defined in its security group.

Port Forwarding

As well as starting a shell session on an instance you can also use session manager to start a port forwarding session. Suppose you have an EC2 instance with a tomcat server running on port 8080, you could start a port forwarding session that maps local port 18080 to the instance’s port of 8080:

aws ssm start-session --target <INSTANCE_ID> \
                      --region us-east-1 \
                      --document-name AWS-StartPortForwardingSession \
                      --parameters "localPortNumber=18080,portNumber=8080"

You could then access the tomcat server via http://localhost:18080 on your workstation.

Restricting Access

You can create IAM policies to define who can access which instances. For example, the following policy will permit session manager access to instances that are not tagged with Team=admins:

{
  "Version":"2012-10-17",
  "Statement":[
    {
      "Effect":"Allow",
      "Action":[
        "ssm:DescribeInstanceInformation",
        "ssm:StartSession"
      ],
      "Resource":"arn:aws:ec2:*:*:instance/*",
      "Condition":{
        "StringNotEquals":{
          "ssm:resourceTag/Team": [
            "admins"
          ]
        }
      }
    },
    {
      "Effect":"Allow",
      "Action":[
        "ssm:StartSession"
      ],
      "Resource":[
        "arn:aws:ssm:*::document/AWS-StartSSHSession"
      ]
    }
  ]
}

Run As

By default, session manager sessions are launched via a system-generated ssm-user. We can change this by launching the session manager preferences, checking the `Enable Run As support for Linux instances option and providing the alternative user.

Now when we start a session we are logged in as this user:

Additionally, you may add a tag to IAM roles or users with the tag key being SSMSessionRunAs and the tag value being the user account to login with. This allows you to further control access to your EC2 instances. See here for more details on this.

Summary

I hope this article demonstrates both how useful session manager is and how easy it is to setup and configure. Beyond the advantages described above you also get a full log of all sessions delivered to a CloudWatch log group and an S3 bucket for auditing purposes. These are configured in the Terraform code I have provided.

Amazon Elastic File System (EFS) Integration With AWS Lambda

AWS has recently announced support for Amazon Elastic File System (EFS) within AWS Lambda. This change creates new possibilities for serverless applications. In this article I will demonstrate one such possibility — centralising the storage and updating of the ClamAV virus database.

ClamAV

ClamAV® is an open source antivirus engine for detecting trojans, viruses, malware & other malicious threats.

Like any antivirus solution, ClamAV needs to be kept up to date to be fully effective. Ordinarily the virus database can be updated by issuing the freshclam command. However, this requires that the instance running the command have internet access. When developing secure architectures in public cloud it is sometimes necessary to have fully isolated subnets which, do not have internet access. Additionally, strict security compliance requirements may dictate that virus definitions are not updated directly from the internet but instead be updated from a centralised location within the VPC.

Combining EFS, Lambda and EC2 we can create a configuration that will meet these requirements.

Design

The below diagram represents the architecture we will implement.

Our virus database will be stored on an EFS file system. EC2 instances will be configured to use this file system for their virus definitions (we will deploy the instance in a public subnet in this example just to keep things simple). A “freshclam” Lambda function will keep the virus database stored on EFS up to date.

Terraform

The Terraform code at aw5academy/terraform/clamav will provision the resources for us.

Deploy the stack by issuing the following commands:

git clone https://gitlab.com/aw5academy/terraform/clamav.git
cd clamav
terraform init
terraform apply

Chef

As part of the Terraform stack we create an EC2 instance. This instance’s user data clones the repository at aw5academy/chef/clamav containing a Chef cookbook which, bootstraps the instance, installing ClamAV, mounting the EFS file system and configuring the virus database to point to a path on the EFS file system.

EC2 Instance

Lets now login to our EC2 instance to test our setup.

SSH into the EC2 instance with:

ssh -i clamav.pem ec2-user@`terraform output ec2-public-ip`

Next verify no virus definitions are present:

clamconf |grep -A 3 "Database information"

As expected, we see none because our Lambda function has not yet executed. So lets invoke the “freshclam” lambda function with:

aws lambda invoke --function-name freshclam /dev/null --region us-east-1

Now verify the virus definitions are present:

clamconf |grep -A 3 "Database information"

As we now have a valid database we can perform a virus scan:

clamscan .bash_profile

Success!

Cleanup

To remove the stack, from your local terminal run:

terraform destroy

Summary

This is just one example of a real world application of EFS with Lambda. I hope you find this article and the sample code useful.