diff --git a/README.md b/README.md index 6665e07..8f26783 100644 --- a/README.md +++ b/README.md @@ -227,4 +227,4 @@ Or find them below: - [Deploy a Serverless Django App on Google App Engine](https://londonapp.dev/c5) -hello 123 \ No newline at end of file +hello 123456 \ No newline at end of file diff --git a/infra/deploy/database.tf b/infra/deploy/database.tf new file mode 100644 index 0000000..9c2088a --- /dev/null +++ b/infra/deploy/database.tf @@ -0,0 +1,53 @@ +############ +# Database # +############ + +resource "aws_db_subnet_group" "main" { + name = "${local.prefix}-main" + subnet_ids = [ + aws_subnet.private_a.id, + aws_subnet.private_b.id + ] + + tags = { + Name = "${local.prefix}-db-subnet-group" + } +} + +resource "aws_security_group" "rds" { + description = "Allow access to the RDS database instance." + name = "${local.prefix}-rds-inbound-access" + vpc_id = aws_vpc.main.id + + ingress { + protocol = "tcp" + from_port = 5432 + to_port = 5432 + } + + tags = { + Name = "${local.prefix}-db-security-group" + } +} + +resource "aws_db_instance" "main" { + identifier = "${local.prefix}-db" + db_name = "recipe" + allocated_storage = 20 + storage_type = "gp2" + engine = "postgres" + engine_version = "15.7" + auto_minor_version_upgrade = true + instance_class = "db.t4g.micro" + username = var.db_username + password = var.db_password + skip_final_snapshot = true + db_subnet_group_name = aws_db_subnet_group.main.name + multi_az = false + backup_retention_period = 0 + vpc_security_group_ids = [aws_security_group.rds.id] + + tags = { + Name = "${local.prefix}-main" + } +} \ No newline at end of file diff --git a/infra/deploy/network.tf b/infra/deploy/network.tf new file mode 100644 index 0000000..7b883f0 --- /dev/null +++ b/infra/deploy/network.tf @@ -0,0 +1,206 @@ +##Network infra ## + +########################## +# Network infrastructure # +########################## + +resource "aws_vpc" "main" { + cidr_block = "10.1.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true +} + +######################################################### +# Internet Gateway needed for inbound access to the ALB # +######################################################### +resource "aws_internet_gateway" "main" { + vpc_id = aws_vpc.main.id + + tags = { + Name = "${local.prefix}-main" + } +} + +################################################## +# Public subnets for load balancer public access # +################################################## +resource "aws_subnet" "public_a" { + vpc_id = aws_vpc.main.id + cidr_block = "10.1.1.0/24" + map_public_ip_on_launch = true + availability_zone = "${data.aws_region.current.name}a" + + tags = { + Name = "${local.prefix}-public-a" + } +} + +resource "aws_route_table" "public_a" { + vpc_id = aws_vpc.main.id + + tags = { + Name = "${local.prefix}-public-a" + } +} + +resource "aws_route_table_association" "public_a" { + subnet_id = aws_subnet.public_a.id + route_table_id = aws_route_table.public_a.id +} + +resource "aws_route" "public_internet_access_a" { + route_table_id = aws_route_table.public_a.id + destination_cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.main.id +} + +resource "aws_subnet" "public_b" { + vpc_id = aws_vpc.main.id + cidr_block = "10.1.2.0/24" + map_public_ip_on_launch = true + availability_zone = "${data.aws_region.current.name}b" + + tags = { + Name = "${local.prefix}-public-b" + } +} + +resource "aws_route_table" "public_b" { + vpc_id = aws_vpc.main.id + + tags = { + Name = "${local.prefix}-public-b" + } +} + +resource "aws_route_table_association" "public_b" { + subnet_id = aws_subnet.public_b.id + route_table_id = aws_route_table.public_b.id +} + +resource "aws_route" "public_internet_access_b" { + route_table_id = aws_route_table.public_b.id + destination_cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.main.id +} + +############################################ +# Private Subnets for internal access only # +############################################ +resource "aws_subnet" "private_a" { + vpc_id = aws_vpc.main.id + cidr_block = "10.1.10.0/24" + availability_zone = "${data.aws_region.current.name}a" + + tags = { + Name = "${local.prefix}-private-a" + } +} + +resource "aws_subnet" "private_b" { + vpc_id = aws_vpc.main.id + cidr_block = "10.1.11.0/24" + availability_zone = "${data.aws_region.current.name}b" + + tags = { + Name = "${local.prefix}-private-b" + } +} + + +######################################################################### +## Endpoints to allow ECS to access ECR, CloudWatch and Systems Manager # +######################################################################### + +resource "aws_security_group" "endpoint_access" { + description = "Access to endpoints" + name = "${local.prefix}-endpoint-access" + vpc_id = aws_vpc.main.id + + ingress { + cidr_blocks = [aws_vpc.main.cidr_block] + from_port = 443 + to_port = 443 + protocol = "tcp" + } +} + +resource "aws_vpc_endpoint" "ecr" { + vpc_id = aws_vpc.main.id + service_name = "com.amazonaws.${data.aws_region.current.name}.ecr.api" + vpc_endpoint_type = "Interface" + private_dns_enabled = true + + subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id] + + security_group_ids = [ + aws_security_group.endpoint_access.id + ] + + tags = { + Name = "${local.prefix}-ecr-endpoint" + } +} + +resource "aws_vpc_endpoint" "dkr" { + vpc_id = aws_vpc.main.id + service_name = "com.amazonaws.${data.aws_region.current.name}.ecr.dkr" + vpc_endpoint_type = "Interface" + private_dns_enabled = true + + subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id] + + security_group_ids = [ + aws_security_group.endpoint_access.id + ] + + tags = { + Name = "${local.prefix}-dkr-endpoint" + } +} + +resource "aws_vpc_endpoint" "cloudwatch_logs" { + vpc_id = aws_vpc.main.id + service_name = "com.amazonaws.${data.aws_region.current.name}.logs" + vpc_endpoint_type = "Interface" + private_dns_enabled = true + + subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id] + + security_group_ids = [ + aws_security_group.endpoint_access.id + ] + + tags = { + Name = "${local.prefix}-cloudwatch-endpoint" + } +} + +resource "aws_vpc_endpoint" "ssm" { + vpc_id = aws_vpc.main.id + service_name = "com.amazonaws.${data.aws_region.current.name}.ssmmessages" + vpc_endpoint_type = "Interface" + private_dns_enabled = true + + subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id] + + security_group_ids = [ + aws_security_group.endpoint_access.id + ] + + tags = { + Name = "${local.prefix}-ssmmessages-endpoint" + } +} + +resource "aws_vpc_endpoint" "s3" { + vpc_id = aws_vpc.main.id + service_name = "com.amazonaws.${data.aws_region.current.name}.s3" + vpc_endpoint_type = "Gateway" + route_table_ids = [ + aws_vpc.main.default_route_table_id + ] + tags = { + Name = "${local.prefix}-s3-endpoint" + } +} \ No newline at end of file diff --git a/infra/deploy/variables.tf b/infra/deploy/variables.tf index 45582ef..180a9f5 100644 --- a/infra/deploy/variables.tf +++ b/infra/deploy/variables.tf @@ -12,4 +12,13 @@ variable "project" { variable "contact" { description = "Contact email for tagging resources" default = "mark@example.com" +} + +variable "db_username" { + description = "Username for the recipe app api database" + default = "recipeapp" +} + +variable "db_password" { + description = "Password for the Terraform database" } \ No newline at end of file diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index 9061a71..81450f7 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -10,4 +10,5 @@ services: - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} - AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} - AWS_DEFAULT_REGION=ap-southeast-2 - - TF_WORKSPACE=${TF_WORKSPACE} \ No newline at end of file + - TF_WORKSPACE=${TF_WORKSPACE} + - TF_VAR_db_password=${TF_VAR_db_password} \ No newline at end of file diff --git a/infra/setup/iam.tf b/infra/setup/iam.tf index 8731c69..b9cf15a 100644 --- a/infra/setup/iam.tf +++ b/infra/setup/iam.tf @@ -81,4 +81,126 @@ resource "aws_iam_policy" "ecr" { resource "aws_iam_user_policy_attachment" "ecr" { user = aws_iam_user.cd.name policy_arn = aws_iam_policy.ecr.arn +} + + +######################### +# Policy for EC2 access # +######################### + +data "aws_iam_policy_document" "ec2" { + statement { + effect = "Allow" + actions = [ + "ec2:DescribeVpcs", + "ec2:CreateTags", + "ec2:CreateVpc", + "ec2:DeleteVpc", + "ec2:DescribeSecurityGroups", + "ec2:DeleteSubnet", + "ec2:DeleteSecurityGroup", + "ec2:DescribeNetworkInterfaces", + "ec2:DetachInternetGateway", + "ec2:DescribeInternetGateways", + "ec2:DeleteInternetGateway", + "ec2:DetachNetworkInterface", + "ec2:DescribeVpcEndpoints", + "ec2:DescribeRouteTables", + "ec2:DeleteRouteTable", + "ec2:DeleteVpcEndpoints", + "ec2:DisassociateRouteTable", + "ec2:DeleteRoute", + "ec2:DescribePrefixLists", + "ec2:DescribeSubnets", + "ec2:DescribeVpcAttribute", + "ec2:DescribeNetworkAcls", + "ec2:AssociateRouteTable", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupEgress", + "ec2:CreateSecurityGroup", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:CreateVpcEndpoint", + "ec2:ModifySubnetAttribute", + "ec2:CreateSubnet", + "ec2:CreateRoute", + "ec2:CreateRouteTable", + "ec2:CreateInternetGateway", + "ec2:AttachInternetGateway", + "ec2:ModifyVpcAttribute", + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeAvailabilityZones" + ] + resources = ["*"] + } +} + +resource "aws_iam_policy" "ec2" { + name = "${aws_iam_user.cd.name}-ec2" + description = "Allow user to manage EC2 resources." + policy = data.aws_iam_policy_document.ec2.json +} + +resource "aws_iam_user_policy_attachment" "ec2" { + user = aws_iam_user.cd.name + policy_arn = aws_iam_policy.ec2.arn +} + + +######################### +# Policy for RDS access # +######################### + +data "aws_iam_policy_document" "rds" { + statement { + effect = "Allow" + actions = [ + "rds:DescribeDBSubnetGroups", + "rds:DescribeDBInstances", + "rds:CreateDBSubnetGroup", + "rds:DeleteDBSubnetGroup", + "rds:CreateDBInstance", + "rds:DeleteDBInstance", + "rds:ListTagsForResource", + "rds:ModifyDBInstance", + "rds:AddTagsToResource" + ] + resources = ["*"] + } + + +} + +data "aws_iam_policy_document" "service_linked_rds" { + statement { + effect = "Allow" + actions = ["iam:CreateServiceLinkedRole"] + resources = ["arn:aws:iam::*:role/aws-service-role/rds.amazonaws.com/AWSServiceRoleForRDS"] + condition { + test = "StringLike" + variable = "iam:AWSServiceName" + values = ["rds.amazonaws.com"] + } + } +} + +resource "aws_iam_policy" "service_linked_rds" { + name = "${aws_iam_user.cd.name}-service_linked_rds" + description = "Allow Amazon RDS to call AWS services on behalf of your DB instances." + policy = data.aws_iam_policy_document.service_linked_rds.json +} + +resource "aws_iam_user_policy_attachment" "service_linked_rds" { + user = aws_iam_user.cd.name + policy_arn = aws_iam_policy.service_linked_rds.arn +} + +resource "aws_iam_policy" "rds" { + name = "${aws_iam_user.cd.name}-rds" + description = "Allow user to manage RDS resources." + policy = data.aws_iam_policy_document.rds.json +} + +resource "aws_iam_user_policy_attachment" "rds" { + user = aws_iam_user.cd.name + policy_arn = aws_iam_policy.rds.arn } \ No newline at end of file