Secure AWS Infrastructure for Serverless Projects with Terragrunt

& DevOps Practitioner

& DevOps Practitioner
Contents
- Github Repository
- Introduction
- Overview of the Repository
- Key Features
- Repository Structure
- How to Use This Repository
- Integrating Serverless Applications
- Why This Setup?
- Conclusion
Github Repository
If you prefer to jump straight to the code: aws-terraform-vpc-rds-bastion
Introduction
As a freelance serverless consultant, having a reusable, secure, and scalable infrastructure is critical for delivering projects efficiently. To address this need, I created a Terraform and Terragrunt-based repository that provisions a robust AWS infrastructure. This setup is designed to support serverless applications while providing secure access to an RDS database. In this blog post, I'll walk you through the key components of this repository and how it can be used to bootstrap serverless projects.
Overview of the Repository
This repository provisions a secure AWS infrastructure that includes the following components:
- VPC: A Virtual Private Cloud with public, private, and database subnets.
- RDS: A MySQL database instance with secure configurations.
- Bastion Host: An EC2 instance for secure SSH access to private resources.
- NAT Gateway: For outbound internet access from private subnets.
- Security Groups: Fine-grained access control for RDS, Lambda functions, and the Bastion Host.
- Key Pairs: SSH key management for the Bastion Host.
The infrastructure is modular, allowing you to reuse components across multiple projects. It is also configured to integrate seamlessly with serverless applications, enabling Lambda functions to securely access the RDS database.
Key Features
1. VPC Design
The VPC module creates a network with three tiers of subnets:
- Public Subnets: For resources like the Bastion Host and NAT Gateway.
- Private Subnets: For application resources like Lambda functions.
- Database Subnets: For the RDS instance.
This design ensures that sensitive resources, such as the RDS database, remain isolated from the public internet.
2. RDS Configuration
The RDS module provisions a MySQL database with the following features:
- Private Subnet Placement: Ensures the database is not publicly accessible.
- Security Group Integration: Allows access only from the Lambda and Bastion Host security groups.
- CloudWatch Logs: Enables monitoring of database activity.
3. Bastion Host
The Bastion Host provides secure SSH access to private resources. It is configured with:
- Public Subnet Placement: Accessible from the internet.
- Security Group Rules: Restricts SSH access to a specific IP address (your IP).
- Encrypted Root Volume: Ensures data security.
4. Security Groups
The repository includes security groups for:
- RDS: Allows access from Lambda functions and the Bastion Host.
- Lambda: Allows outbound internet access for serverless functions.
- Bastion Host: Restricts SSH access to your IP address.
5. Key Pair Management
The key-pairs
module generates an SSH key pair for the Bastion Host. The private key is stored locally, ensuring secure access.
Repository Structure
The repository is organized into two main directories:
modules
: Contains reusable Terraform modules for each infrastructure component.environents/dev
: Contains Terragrunt configurations for the development environment.
The environents
directory is easily extendable to prod
or qa
, just ensure you also include a related env file in the root of the repository.
Example Structure:
.
├── README.md
├── env-example.dev.hcl
├── environments
│ └── dev
│ ├── bastion-host
│ │ └── terragrunt.hcl
│ ├── key-pairs
│ │ └── terragrunt.hcl
│ ├── nat-gateway
│ │ └── terragrunt.hcl
│ ├── rds
│ │ └── terragrunt.hcl
│ ├── root.hcl
│ ├── security-groups
│ │ └── terragrunt.hcl
│ └── vpc
│ └── terragrunt.hcl
└── modules
├── bastion-host
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
├── key-pairs
│ ├── main.tf
│ └── variables.tf
├── nat-gateway
│ ├── main.tf
│ └── variables.tf
├── rds
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
├── security-groups
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
└── vpc
├── main.tf
├── outputs.tf
└── variables.tf
How to Use This Repository
Prerequisites
- Terraform >= 1.0.0
- Terragrunt >= 0.35.0
- AWS CLI configured with appropriate credentials and permissions.
1. Clone the Repository
git clone https://github.com/your-repo/aws-terraform-vpc-rds-bastion.git
cd aws-terraform-vpc-rds-bastion
2. Configure Environment Variables
Add an env.dev.hcl
in the root file with your specific inputs:
inputs = {
infra_name = "your-infra-name"
aws_region = "your-region"
env = "dev"
iac = "terragrunt"
my_ip = "your-ip/32"
bastion_host_private_key_name = "your-key-name"
}
3. Initialize
Navigate to the desired environment directory and initialize Terragrunt:
cd environments/dev
terragrunt run-all init
4. Plan
Run the following command to show what will be provisioned in your infrastructure:
terragrunt run-all plan -out=tfplan && terragrunt run-all show tfplan
5. Apply
Run the following command to provision the infrastructure:
terragrunt run-all apply tfplan
Integrating Serverless applications
Once the infrastructure is provisioned, deploying serverless lambda backend services is a simple case of configuring the Lambda functions to use the lambda security group and private subnets of the VPC.
Below is simple example using the SST Framework v3:
export const api = new sst.aws.ApiGatewayV2("Api", {
cors: {
allowOrigins: ["https://localhost:3000"],
allowMethods: ["GET", "POST", "PUT", "DELETE"],
allowHeaders: ["*"],
allowCredentials: true,
},
});
$transform(sst.aws.Function, (args, _opts) => {
args.environment = {
...args.environment,
DB_HOST: process.env.DB_HOST as string,
DB_PORT: process.env.DB_PORT as string,
DB_USER: process.env.DB_USER as string,
DB_PASSWORD: process.env.DB_PASSWORD as string,
DB_NAME: process.env.DB_NAME as string,
};
args.vpc = {
privateSubnets: [
process.env.VPC_PRIVATE_SUBNET_1_ID,
process.env.VPC_PRIVATE_SUBNET_2_ID,
process.env.VPC_PRIVATE_SUBNET_3_ID,
],
securityGroups: [process.env.SECURITY_GROUP_LAMBDA_ID],
};
});
const verifyUserFunction = new sst.aws.Function("verifyUserFunction", {
handler: "packages/functions/src/user/verifyUser.handler",
});
api.route("GET /user/verify", verifyUserFunction.arn);
The VPC_PRIVATE_SUBNET_1_ID
, VPC_PRIVATE_SUBNET_2_ID
, VPC_PRIVATE_SUBNET_3_ID
and SECURITY_GROUP_LAMBDA_ID
values are resolved in the via the respective outputs from the vpc
and security-group
modules.
Why This Setup?
This repository is tailored for freelance serverless consultants or developers who need a secure, reusable infrastructure for AWS projects. It abstracts the complexity of setting up a secure environment, allowing you to focus on building serverless applications.
Benefits:
- Security: Isolated subnets, restricted access, and encrypted resources.
- Reusability: Modular design for easy replication across projects.
- Scalability: Supports serverless architectures with minimal changes.
Conclusion
This Terraform and Terragrunt-based repository is a powerful tool for provisioning secure AWS infrastructure. Whether you're a freelance consultant or a developer working on serverless projects, this setup provides a solid foundation for deploying applications that require secure access to an RDS database.
Feel free to fork this repository and adapt it to your needs. Contributions are welcome—open an issue or submit a pull request if you have ideas for improvement.
Thanks for reading.
John O