333 lines
9.1 KiB
Terraform
333 lines
9.1 KiB
Terraform
/**
|
|
* # rds-mysql-proxy
|
|
*
|
|
* Create vpc, rds, dbproxy, and a bastion
|
|
*/
|
|
|
|
locals {
|
|
vpc_cidr = "10.18.0.0/16"
|
|
# ensure there is room for future expansion
|
|
private_net_start = cidrsubnet(local.vpc_cidr, 2, 1)
|
|
public_net_start = cidrsubnet(local.vpc_cidr, 2, 2)
|
|
rds_port = 13306
|
|
}
|
|
|
|
data "aws_availability_zones" "this" {
|
|
state = "available"
|
|
}
|
|
|
|
resource "random_shuffle" "Select2Az" {
|
|
input = data.aws_availability_zones.this.names
|
|
result_count = 2
|
|
}
|
|
|
|
module "vpc" {
|
|
source = "terraform-aws-modules/vpc/aws"
|
|
version = "6.6.0"
|
|
|
|
name = "lab-${var.owner}-vpc"
|
|
cidr = local.vpc_cidr
|
|
|
|
azs = random_shuffle.Select2Az.result
|
|
enable_ipv6 = false
|
|
public_subnets = cidrsubnets(local.public_net_start, 8, 8)
|
|
database_subnets = cidrsubnets(local.private_net_start, 8, 8)
|
|
create_database_subnet_group = true
|
|
|
|
enable_dns_hostnames = true
|
|
enable_dns_support = true
|
|
enable_nat_gateway = false
|
|
|
|
enable_flow_log = false
|
|
create_flow_log_cloudwatch_log_group = false
|
|
create_flow_log_cloudwatch_iam_role = false
|
|
manage_default_network_acl = false
|
|
}
|
|
|
|
|
|
module "db" {
|
|
source = "terraform-aws-modules/rds/aws"
|
|
version = "7.1.0"
|
|
|
|
identifier = "${var.environment}-${var.owner}-test"
|
|
engine = "mysql"
|
|
engine_version = "8.4.8"
|
|
family = "mysql8.4" # DB parameter group
|
|
major_engine_version = "8.4" # DB option group
|
|
instance_class = "db.t4g.medium"
|
|
|
|
storage_type = "gp3"
|
|
allocated_storage = 20
|
|
max_allocated_storage = 20
|
|
|
|
db_name = "appdb"
|
|
username = "mysqldba"
|
|
port = local.rds_port
|
|
|
|
multi_az = false
|
|
create_db_subnet_group = false
|
|
db_subnet_group_name = module.vpc.database_subnet_group_name
|
|
vpc_security_group_ids = [aws_security_group.rds-sg.id]
|
|
|
|
skip_final_snapshot = true
|
|
deletion_protection = false
|
|
apply_immediately = true
|
|
|
|
parameters = [
|
|
{
|
|
name = "character_set_client"
|
|
value = "utf8mb4"
|
|
},
|
|
{
|
|
name = "character_set_server"
|
|
value = "utf8mb4"
|
|
}
|
|
]
|
|
}
|
|
|
|
resource "aws_security_group" "rds-sg" {
|
|
name = "rds-sg"
|
|
description = "Allow rds inbound traffic"
|
|
vpc_id = module.vpc.vpc_id
|
|
|
|
ingress {
|
|
description = "RDS access from bastion"
|
|
from_port = local.rds_port
|
|
to_port = local.rds_port
|
|
protocol = "tcp"
|
|
security_groups = [aws_security_group.bastion-sg.id, aws_security_group.dbproxy-sg.id]
|
|
}
|
|
|
|
egress {
|
|
from_port = 0
|
|
to_port = 0
|
|
protocol = "-1"
|
|
cidr_blocks = ["0.0.0.0/0"]
|
|
ipv6_cidr_blocks = ["::/0"]
|
|
}
|
|
}
|
|
|
|
# bastion
|
|
module "BastionRole" {
|
|
source = "../../modules/security_identity_compliance/iam-role-v2"
|
|
description = "EKS bastion instance profile"
|
|
role-name = "BastionInstanceProfile"
|
|
trusted-entity = "ec2.amazonaws.com"
|
|
create-instance-profile = true
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "BastionProfilePermissions" {
|
|
role = module.BastionRole.name
|
|
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
|
|
}
|
|
|
|
module "bastion" {
|
|
source = "../../modules/compute/ec2"
|
|
|
|
additional-tags = {}
|
|
ami-id = data.aws_ami.this.id
|
|
asso-eip = false
|
|
asso-public-ip = true
|
|
use-ipv6 = true
|
|
data-volumes = {}
|
|
ebs-encrypted = true
|
|
instance-name = "lab-${var.owner}-rds-client"
|
|
instance-type = "t4g.micro"
|
|
key-name = aws_key_pair.kp.key_name
|
|
kms-key-id = ""
|
|
root-volume-size = "8"
|
|
security-groups = [aws_security_group.bastion-sg.id]
|
|
subnet-id = module.vpc.public_subnets[0]
|
|
instance-profile = module.BastionRole.profile-name[0]
|
|
spot-max-price = 0.0116 # t4g.micro
|
|
user-data = <<EOF
|
|
#!/bin/bash
|
|
hostnamectl hostname mysql-client
|
|
dnf -y install mariadb1011-client-utils
|
|
EOF
|
|
}
|
|
|
|
data "aws_ami" "this" {
|
|
most_recent = true
|
|
name_regex = "^al2023-ami-2023.*-kernel-6.1-arm64"
|
|
owners = ["amazon"]
|
|
}
|
|
|
|
resource "tls_private_key" "sshkey" {
|
|
algorithm = "ED25519"
|
|
}
|
|
|
|
resource "aws_key_pair" "kp" {
|
|
key_name = "${var.environment}-${var.owner}-bastion-key"
|
|
public_key = tls_private_key.sshkey.public_key_openssh
|
|
}
|
|
|
|
resource "aws_security_group" "bastion-sg" {
|
|
name = "bastion-sg"
|
|
description = "Allow bastion inbound traffic"
|
|
vpc_id = module.vpc.vpc_id
|
|
|
|
ingress {
|
|
description = "ssh admin"
|
|
from_port = 22
|
|
to_port = 22
|
|
protocol = "tcp"
|
|
cidr_blocks = ["0.0.0.0/0"]
|
|
}
|
|
|
|
egress {
|
|
from_port = 0
|
|
to_port = 0
|
|
protocol = "-1"
|
|
cidr_blocks = ["0.0.0.0/0"]
|
|
ipv6_cidr_blocks = ["::/0"]
|
|
}
|
|
}
|
|
|
|
|
|
# rds proxy
|
|
module "dbproxy-role" {
|
|
source = "../../modules/security_identity_compliance/iam-role-v2"
|
|
description = "RDS db proxy role"
|
|
role-name = "${var.environment}-${var.owner}-dbproxy"
|
|
trusted-entity = "rds.amazonaws.com"
|
|
create-instance-profile = false
|
|
policies = {
|
|
DbProxySecret = {
|
|
description = "DbproxyAccess"
|
|
policy = jsonencode(
|
|
{
|
|
"Version" : "2012-10-17",
|
|
"Statement" : [
|
|
{
|
|
"Sid" : "SecretsManagerGetAndDescribeSecret",
|
|
"Effect" : "Allow",
|
|
"Action" : [
|
|
"secretsmanager:GetSecretValue",
|
|
"secretsmanager:DescribeSecret"
|
|
],
|
|
"Resource" : aws_secretsmanager_secret.dbproxy.arn
|
|
},
|
|
{
|
|
"Sid" : "KMSDecryptKey",
|
|
"Effect" : "Allow",
|
|
"Action" : [
|
|
"kms:Decrypt"
|
|
],
|
|
"Resource" : "arn:aws:kms:*:*:key/*",
|
|
"Condition" : {
|
|
"StringLike" : {
|
|
"kms:EncryptionContext:SecretARN" : "arn:aws:secretsmanager:*:*:secret:*",
|
|
"kms:ViaService" : "secretsmanager.*.amazonaws.com"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
data "aws_secretsmanager_secret_version" "dbsecret" {
|
|
secret_id = module.db.db_instance_master_user_secret_arn
|
|
}
|
|
|
|
/*
|
|
When client connects to dbproxy, client supplied credential is checked
|
|
against the one stored in secretsmanager. If they match, dbproxy will
|
|
create connection to backend database using the password on secretsmanager,
|
|
if a new connection is required.
|
|
|
|
In this example, I will not create a separate database user. The master user
|
|
will be used.
|
|
*/
|
|
|
|
resource "aws_secretsmanager_secret" "dbproxy" {
|
|
name = "${var.environment}-${var.owner}-dbproxy"
|
|
description = "dbproxy credential"
|
|
}
|
|
|
|
# use master database user for dbproxy
|
|
resource "aws_secretsmanager_secret_version" "dbproxy" {
|
|
secret_id = aws_secretsmanager_secret.dbproxy.id
|
|
secret_string = jsonencode(
|
|
{
|
|
"username" : jsondecode(data.aws_secretsmanager_secret_version.dbsecret.secret_string).username,
|
|
"password" : jsondecode(data.aws_secretsmanager_secret_version.dbsecret.secret_string).password,
|
|
"engine" : module.db.db_instance_engine,
|
|
"host" : module.db.db_instance_address,
|
|
"port" : module.db.db_instance_port,
|
|
"dbname" : module.db.db_instance_name,
|
|
"dbInstanceIdentifier" : module.db.db_instance_identifier
|
|
}
|
|
)
|
|
}
|
|
|
|
resource "random_pet" "this" {
|
|
length = 1
|
|
}
|
|
|
|
resource "aws_db_proxy" "this" {
|
|
name = "${var.environment}-${var.owner}-dbproxy-${random_pet.this.id}"
|
|
debug_logging = false
|
|
engine_family = "MYSQL"
|
|
idle_client_timeout = 600
|
|
require_tls = false # connection from client to proxy
|
|
role_arn = module.dbproxy-role.role-arn
|
|
vpc_security_group_ids = [aws_security_group.dbproxy-sg.id]
|
|
vpc_subnet_ids = module.vpc.private_subnets
|
|
|
|
auth {
|
|
auth_scheme = "SECRETS"
|
|
description = "db master user"
|
|
iam_auth = "DISABLED"
|
|
secret_arn = aws_secretsmanager_secret.dbproxy.arn # module.db.db_instance_master_user_secret_arn
|
|
}
|
|
}
|
|
|
|
resource "aws_db_proxy_default_target_group" "dbproxy" {
|
|
db_proxy_name = aws_db_proxy.this.name
|
|
|
|
connection_pool_config {
|
|
connection_borrow_timeout = 120
|
|
max_connections_percent = 100
|
|
max_idle_connections_percent = 50
|
|
}
|
|
|
|
lifecycle {
|
|
replace_triggered_by = [aws_db_proxy.this.id]
|
|
}
|
|
}
|
|
|
|
resource "aws_db_proxy_target" "dbproxy" {
|
|
db_instance_identifier = module.db.db_instance_identifier
|
|
db_proxy_name = aws_db_proxy.this.name
|
|
target_group_name = aws_db_proxy_default_target_group.dbproxy.name
|
|
|
|
lifecycle {
|
|
replace_triggered_by = [aws_db_proxy.this.id]
|
|
}
|
|
}
|
|
|
|
resource "aws_security_group" "dbproxy-sg" {
|
|
name = "dbproxy-sg"
|
|
description = "Allow inbound traffic to dbproxy"
|
|
vpc_id = module.vpc.vpc_id
|
|
|
|
ingress {
|
|
description = "Access from bastion"
|
|
from_port = 3306
|
|
to_port = 3306
|
|
protocol = "tcp"
|
|
security_groups = [aws_security_group.bastion-sg.id]
|
|
}
|
|
|
|
egress {
|
|
from_port = 0
|
|
to_port = 0
|
|
protocol = "-1"
|
|
cidr_blocks = ["0.0.0.0/0"]
|
|
ipv6_cidr_blocks = ["::/0"]
|
|
}
|
|
} |