Securely connect to an Amazon RDS
Tags: devops, terraform, database, security
Published: 2024-05-06 01:14:09

Hello there! In this post I am going to show you Terraform code example of how to create resources for secure connection to your DB in RDS cluster.

---

I am going to follow approach provided by AWS team in this [article](https://aws.amazon.com/blogs/database/securely-connect-to-an-amazon-rds-or-amazon-ec2-database-instance-remotely-with-your-preferred-gui/)

Ok, let's figure out what resources we need to create.
I assume that you already have: VPC and RDS Cluster (if not yet then check out [previous post](https://dev.to/aws-builders/create-rds-cluster-and-manage-passwords-in-2024-4n3d) ).

- VPC endpoints (ssm, ssmmessages, ec2messages)
- EC2 instance
- Security Group for EC2 instance
- IAM role and instance profile

---

I will put details after each part of the code. Also if you want to skip and jump right to the code then everything can be found in my [repo](https://github.com/nbekenov/rds-aurora/tree/main)

Below code creates VPC endpoints. Since we need 3 of them I am looping through the list. We also will need security group.

```
# ----------------
# VPC Endpoints
# ----------------
locals {
  endpoints = {
    "endpoint-ssm" = {
      name        = "ssm"
      private_dns = false
    },
    "endpoint-ssm-messages" = {
      name        = "ssmmessages"
      private_dns = false
    },
    "endpoint-ec2-messages" = {
      name        = "ec2messages"
      private_dns = false
    },
  }
}

resource "aws_vpc_endpoint" "endpoints" {
  for_each            = local.endpoints
  
  vpc_id              = module.vpc.vpc_id
  vpc_endpoint_type   = "Interface"
  service_name        = "com.amazonaws.${data.aws_region.current.name}.${each.value.name}"
  security_group_ids  = [aws_security_group.vpc_endpoint_sg.id]
  subnet_ids          = data.aws_subnets.private.ids
  private_dns_enabled = each.value.private_dns

}

# SG for VPC endpoints
resource "aws_security_group" "vpc_endpoint_sg" {
  name_prefix = "vpc-endpoint-sg"
  vpc_id      = module.vpc.vpc_id
  description = "security group for VPC endpoints"

  ingress {
    from_port   = 0
    to_port     = 65535
    protocol    = "tcp"
    cidr_blocks = [module.vpc.vpc_cidr_block]
    description = "allow all TCP within VPC CIDR"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    description = "allow all outbound traffic from VPC"
  }

  tags = {
    Name = "vpc-endpoints-sg"
  }
}
```
Now let's create EC2 instance and configure instance profile for it.
```
resource "aws_instance" "bastion_host" {
  ami                     = data.aws_ami.amazon_linux_2_ssm.id
  instance_type           = "t3.nano"
  subnet_id               = data.aws_subnets.private.ids[1]
  vpc_security_group_ids  = data.aws_security_groups.vpc_endpoint_sg.ids
  iam_instance_profile    = aws_iam_instance_profile.bastion_host_instance_profile.name
  user_data               = templatefile("ssm-agent-installer.sh", {})
  disable_api_termination = false
  metadata_options {
    http_endpoint = "enabled"
    http_tokens   = "required"
  }
  tags = {
    Name = "ssm-bastion-host"
  }
}

## Instance profile
resource "aws_iam_role" "bastion_host_role" {
  name = "EC2-SSM-Session-Manager-Role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = "ec2.amazonaws.com"
        }
        Action = "sts:AssumeRole"
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "bastion_host_role_policy" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  role       = aws_iam_role.bastion_host_role.name
}

resource "aws_iam_instance_profile" "bastion_host_instance_profile" {
  name = "EC2_SSM_InstanceProfile"
  role = aws_iam_role.bastion_host_role.name
}
```
For Instance IAM role we are using existing policy AmazonSSMManagedInstanceCore that we will need in order to be able to use SSM.
Note that in user_data I am using shell script to install necessary packages.
```
#!/bin/bash
main(){
    #####Installing dependencies and packages ####
    echo "Installing Security Updates..."
    sudo yum -y update
    echo "Installing ec2 instance connect..."
    sudo yum install ec2-instance-connect
    echo "Installing latest aws-ssm agent..."
    sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm
    sudo systemctl enable amazon-ssm-agent
    echo "Starting latest aws-ssm agent..."
    sudo systemctl start amazon-ssm-agent
    sudo amazon-linux-extras enable postgresql14
    sudo yum -y install postgresql
}
time main > /tmp/time-output.txt
```
Once all infra is created follow the steps from my Readme
https://github.com/nbekenov/rds-aurora/blob/main/bastion_host/README.md

- Create a remote port forwarding session
```
aws ssm start-session \   
    --region us-east-1 \   
    --target <bastion instance id> \    
    --document-name AWS-StartPortForwardingSessionToRemoteHost \    
    --parameters host="<rds endpoint name>",portNumber="5432" localPortNumber="5432"
```
- Connect to DB using PGAdmin

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cg6u777u8d7z3hlpx47r.png)


Use username and password from AWS Secrets Manager

---

In the next post I will be providing details on how to run DB Migrations using Flyway and Lambda Function