Skip to content

Commit

Permalink
Setup VPC and RDS Proxy to enable Lambda to connect to DB
Browse files Browse the repository at this point in the history
  • Loading branch information
danielbreves committed Aug 23, 2023
1 parent 7b5bfa5 commit e2dd31d
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,5 @@ jobs:
--no-confirm-changeset
--no-fail-on-empty-changeset
--stack-name fastapi-backend-lambda
--parameter-overrides "SecretKeyArn=${{ vars.SECRET_ARN }} DBSecretArn=${{ vars.DB_SECRET_ARN }} DBEndpoint=${{ vars.DB_ENDPOINT }}"
--parameter-overrides "LambdaSecurityGroupId=${{ vars.LAMBDA_SG_ID }} LambdaSubnet1Id=${{ vars.LAMBDA_SUBNET_1_ID }} LambdaSubnet2Id=${{ vars.LAMBDA_SUBNET_2_ID }} SecretKeyArn=${{ vars.SECRET_ARN }} DBSecretArn=${{ vars.DB_SECRET_ARN }} DBEndpoint=${{ vars.DB_ENDPOINT }}"
--on-failure DELETE
18 changes: 18 additions & 0 deletions backend/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ Parameters:
Type: String
Default: example
Description: ARN for the secret in SM
LambdaSecurityGroupId:
Type: String
Default: example
Description: Security group id for the lambda
LambdaSubnet1Id:
Type: String
Default: example
Description: Lambda subnet 1 id
LambdaSubnet2Id:
Type: String
Default: example
Description: Lambda subnet 2 id
DBSecretArn:
Type: String
Default: example
Expand Down Expand Up @@ -48,6 +60,12 @@ Resources:
DB_ENDPOINT: !Ref DBEndpoint
DB_NAME: !Ref DBName
Role: !GetAtt FastApiBackendRole.Arn
VpcConfig:
SecurityGroupIds:
- !Ref LambdaVpcSecurityGroupId
SubnetIds:
- !Ref LambdaSubnet1Id
- !Ref LambdaSubnet2Id
Events:
HttpApiEvent:
Type: HttpApi
Expand Down
167 changes: 145 additions & 22 deletions terraform/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,58 @@ provider "aws" {
region = var.aws_region
}

resource "aws_ecr_repository" "ecr_repository" {
name = "fastapi-lambda-ecr-repo"
force_delete = true
data "aws_caller_identity" "current" {}

locals {
account_id = data.aws_caller_identity.current.account_id
}

resource "aws_ecr_lifecycle_policy" "ecr_repository_policy" {
repository = aws_ecr_repository.ecr_repository.name
################################################################################
# VPC
################################################################################

policy = jsonencode({
rules = [
{
rulePriority = 1,
description = "Keep only the last 5 images",
selection = {
tagStatus = "untagged",
countType = "imageCountMoreThan",
countNumber = 5,
},
action = {
type = "expire"
}
}
]
})
resource "aws_vpc" "backend_vpc" {
cidr_block = var.vpc_cidr
}

resource "aws_subnet" "db_subnet" {
count = length(var.db_cidr)
vpc_id = aws_vpc.backend_vpc.id
cidr_block = element(var.db_cidr, count.index)
availability_zone = element(var.db_azs, count.index)
tags = {
Name = "db-subnet-${count.index + 1}"
}
}

resource "aws_subnet" "lambda_subnet" {
count = length(var.lambda_cidr)
vpc_id = aws_vpc.backend_vpc.id
cidr_block = element(var.lambda_cidr, count.index)
availability_zone = element(var.lambda_azs, count.index)
tags = {
Name = "lambda-subnet-${count.index + 1}"
}
}

resource "aws_security_group" "db_sg" {
vpc_id = aws_vpc.backend_vpc.id
}

resource "aws_db_instance" "fastapi-db" {
resource "aws_security_group" "lambda_sg" {
vpc_id = aws_vpc.backend_vpc.id
}

################################################################################
# RDS
################################################################################

resource "aws_db_subnet_group" "db_subnet_group" {
name = "FastAPIDBSubnetGroup"
subnet_ids = [aws_subnet.db_subnet[0].id, aws_subnet.db_subnet[1].id]
}

resource "aws_db_instance" "fastapi_db" {
allocated_storage = 5
db_name = "fastapidb"
engine = "postgres"
Expand All @@ -58,4 +83,102 @@ resource "aws_db_instance" "fastapi-db" {
parameter_group_name = "default.postgres15"
skip_final_snapshot = true
# final_snapshot_identifier = "final-snapshot"

vpc_security_group_ids = [aws_security_group.db_sg.id]
db_subnet_group_name = aws_db_subnet_group.db_subnet_group.id

multi_az = true # Enable multi-AZ deployment for high availability
}

################################################################################
# RDS Proxy
################################################################################

resource "random_password" "db_proxy_password" {
length = 24
}

resource "aws_secretsmanager_secret" "db_proxy_secret" {
name = "DBProxySecret"
}

resource "aws_secretsmanager_secret_version" "db_version" {
secret_id = aws_secretsmanager_secret.db_proxy_secret.id
# https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy-setup.html#rds-proxy-secrets-arns
secret_string = jsonencode({
"username" = var.lambda_db_username
"password" = random_password.db_proxy_password.result
})
}

resource "aws_iam_role" "db_proxy_role" {
name = "DBProxyRole"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "rds.amazonaws.com"
}
}]
})
}

resource "aws_iam_policy" "db_proxy_policy" {
name = "DBProxyPolicy"

policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = "secretsmanager:GetSecretValue",
Effect = "Allow",
Resource = aws_secretsmanager_secret.db_proxy_secret.arn
}
]
})
}

resource "aws_iam_role_policy_attachment" "db_proxy_role_policy_attachment" {
policy_arn = aws_iam_policy.db_proxy_policy.arn
role = aws_iam_role.db_proxy_role.name
}

resource "aws_db_proxy" "db_proxy" {
name = "DBProxy"
debug_logging = true
idle_client_timeout = 1800
require_tls = true
role_arn = aws_iam_role.db_proxy_role.arn
engine_family = "POSTGRESQL"

vpc_security_group_ids = [aws_security_group.db_sg.id]
vpc_subnet_ids = [aws_subnet.db_subnet.id]

auth {
auth_scheme = "SECRETS"
description = "using secret manager"
iam_auth = "DISABLED"
secret_arn = aws_secretsmanager_secret.db_proxy_secret.arn
}

depends_on = [aws_cloudwatch_log_group.db_proxy_log_group]
}

resource "aws_db_proxy_default_target_group" "default" {
db_proxy_name = aws_db_proxy.db_proxy.name
}

resource "aws_db_proxy_target" "example" {
db_instance_identifier = aws_db_instance.fastapi_db.identifier
db_proxy_name = aws_db_proxy.db_proxy.name
target_group_name = aws_db_proxy_default_target_group.default.name
}

resource "aws_cloudwatch_log_group" "db_proxy_log_group" {
name = "/aws/rds/proxy/fastapidb"
retention_in_days = 30
}
31 changes: 24 additions & 7 deletions terraform/outputs.tf
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
output "ecr_repository_url" {
value = aws_ecr_repository.ecr_repository.repository_url
output "lambda_security_group" {
description = "security group to be assigned to lambda function"
value = aws_security_group.lambda_sg.id
}

output "db_endpoint" {
value = aws_db_instance.fastapi-db.endpoint
output "lambda_subnet_1" {
value = aws_subnet.lambda_subnet[0].id
}

output "db_master_user_secret" {
value = aws_db_instance.fastapi-db.master_user_secret
}
output "lambda_subnet_2" {
value = aws_subnet.lambda_subnet[1].id
}

output "lambda_db_username" {
value = var.lambda_db_username
}

output "rds_db_name" {
value = aws_db_instance.fastapi_db.db_name
}

output "rds_proxy_endpoint" {
value = aws_db_proxy.db_proxy.endpoint
}

output "rds_proxy_secret_arn" {
value = aws_secretsmanager_secret.db_proxy_secret.arn
}
34 changes: 33 additions & 1 deletion terraform/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,36 @@ variable "aws_region" {
type = string
default = "ap-southeast-2"
description = "AWS region for all resources"
}
}

variable "vpc_cidr" {
description = "CIDR block for the vpc"
type = string
default = "10.0.0.0/16"
}

variable "db_cidr" {
type = list
default = ["10.0.11.0/24", "10.0.12.0/24"]
}

variable "lambda_cidr" {
type = list
default = ["10.0.21.0/24", "10.0.22.0/24"]
}

variable "db_azs" {
type = list
default = ["ap-southeast-2a", "ap-southeast-2b"]
}

variable "lambda_azs" {
type = list
default = ["ap-southeast-2a", "ap-southeast-2b"]
}

variable "lambda_db_username" {
type = string
default = "app_db_username"
description = "DB username for the app" # Needs to be created manually: https://github.com/aws-samples/serverless-patterns/blob/main/apigw-http-api-lambda-rds-proxy-terraform/vpc-rds-setup/main.tf#L236-L239
}

0 comments on commit e2dd31d

Please sign in to comment.