diff --git a/.dockerignore b/.dockerignore index 9b9920f..6211f1a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,8 +1,9 @@ node_modules -.github -.vscode +terraform build docs +.github +.vscode .env *.log .gitignore diff --git a/.gitignore b/.gitignore index 9897e88..1c5f1b5 100644 --- a/.gitignore +++ b/.gitignore @@ -138,4 +138,11 @@ src/database/schemas/generated src/docs/swagger_output.json # TSDoc artifacts -docs \ No newline at end of file +docs + +# Terraform files +*.tfvars +*.tfvars.json +*.tfstate +*.tfstate.* +**/.terraform/* \ No newline at end of file diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..95cd792 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,288 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.43.0" + } + } + + required_version = ">= 1.7.5" +} + +provider "aws" { + access_key = var.aws_access_key_id + secret_key = var.aws_secret_access_key + region = var.aws_region +} + +# VPC Resources +resource "aws_vpc" "main_vpc" { + cidr_block = var.vpc_cidr + + tags = { + "Environment" = var.infra_env + "Name" = "auth-vpc-${var.infra_env}" + "Project" = "authentication-app" + "ManagedBy" = "terraform" + "Organization" = "andrewlod" + } +} + +resource "aws_subnet" "public_subnets" { + for_each = var.public_subnet_map + + vpc_id = "${aws_vpc.vpc.id}" + cidr_block = "${each.value}" + availability_zone = "${each.key}" + map_public_ip_on_launch = true + + tags = { + "Environment" = var.infra_env + "Name" = "auth-public-subnet" + "Project" = "authentication-app" + "ManagedBy" = "terraform" + "Organization" = "andrewlod" + } +} + +locals { + public_subnet_ids = [ for subnet in aws_subnet.public_subnets : subnet.id ] +} + +resource "aws_internet_gateway" "igw" { + vpc_id = "${aws_vpc.main_vpc.id}" + + tags = { + "Environment" = var.infra_env + "Name" = "auth-vpc-igw" + "Project" = "authentication-app" + "ManagedBy" = "terraform" + "Organization" = "andrewlod" + } +} + +resource "aws_route_table" "public_route_table" { + vpc_id = "${aws_vpc.main_vpc.id}" + + tags = { + "Environment" = var.infra_env + "Name" = "auth-vpc-public-rt" + "Project" = "authentication-app" + "ManagedBy" = "terraform" + "Organization" = "andrewlod" + } +} + +resource "aws_route" "public_internet_gateway" { + route_table_id = aws_route_table.public_route_table.id + destination_cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.igw.id +} + +resource "aws_route_table_association" "public_subnets_associations" { + for_each = toset(local.public_subnet_ids) + + subnet_id = each.key + route_table_id = aws_route_table.public_route_table.id +} + +resource "aws_security_group" "public_sg" { + name = "auth-public-sg" + description = "Security group to allow inbound/outbound from the VPC on application ports" + vpc_id = aws_vpc.main_vpc.id + depends_on = [aws_vpc.main_vpc] + + ingress { + from_port = "80" + to_port = "80" + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + from_port = "443" + to_port = "443" + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + from_port = "0" + to_port = "0" + protocol = "-1" + self = true + } + + egress { + from_port = "0" + to_port = "0" + protocol = "-1" + cidr_blocks = [ "0.0.0.0" ] + } + tags = { + "Environment" = var.infra_env + "Name" = "auth-public-sg" + "Project" = "authentication-app" + "ManagedBy" = "terraform" + "Organization" = "andrewlod" + } +} + +resource "aws_security_group" "private_sg" { + name = "auth-private-sg" + description = "Security group for internal VPC traffic" + vpc_id = aws_vpc.main_vpc.id + depends_on = [aws_vpc.main_vpc] + + ingress { + from_port = "0" + to_port = "0" + protocol = "-1" + self = true + } + + egress { + from_port = "0" + to_port = "0" + protocol = "-1" + cidr_blocks = [ "0.0.0.0" ] + } + tags = { + "Environment" = var.infra_env + "Name" = "auth-public-sg" + "Project" = "authentication-app" + "ManagedBy" = "terraform" + "Organization" = "andrewlod" + } +} + +# RDS Resources +resource "aws_db_subnet_group" "authentication_db_sng" { + name = "authdbsng" + subnet_ids = local.public_subnet_ids + + tags = { + "Environment" = var.infra_env + "Name" = "authdbsng" + "Project" = "authentication-app" + "ManagedBy" = "terraform" + "Organization" = "andrewlod" + } +} + +resource "aws_db_instance" "authentication_db" { + allocated_storage = var.db_storage + db_name = "${var.db_name}-${var.infra_env}" + engine = "postgres" + engine_version = "16.2" + instance_class = var.db_instance_type + username = var.db_username + password = var.db_password + skip_final_snapshot = true + publicly_accessible = true + + db_subnet_group_name = aws_db_subnet_group.authentication_db_sng.name + + tags = { + "Environment" = var.infra_env + "Name" = "${var.db_name}-${var.infra_env}" + "Project" = "authentication-app" + "ManagedBy" = "terraform" + "Organization" = "andrewlod" + } +} + +resource "aws_secretsmanager_secret" "db_secret" { + name = "${var.db_name}-${var.infra_env}-secret" + + tags = { + "Environment" = var.infra_env + "Name" = "${var.db_name}-${var.infra_env}-secret" + "Project" = "authentication-app" + "ManagedBy" = "terraform" + "Organization" = "andrewlod" + } +} + +resource "aws_secretsmanager_secret_version" "db_secret_values" { + secret_id = aws_secretsmanager_secret.db_secret.id + secret_string = jsonencode({ + "DATABASE_URL" = "postgres://${var.db_username}:${var.db_password}@${aws_db_instance.authentication_db.endpoint}/${var.db_schema_name}?schema=public" + "ENDPOINT" = "${aws_db_instance.authentication_db.endpoint}" + }) +} + +# IAM Resources +resource "aws_iam_role" "eks_fargate_execution_role" { + name = "eks-fargate-execution-role" + + assume_role_policy = jsonencode({ + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "eks-fargate-pods.amazonaws.com" + } + }] + Version = "2012-10-17" + }) +} + +resource "aws_iam_role_policy_attachment" "eks_fargate_execution_attachment" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSFargatePodExecutionRolePolicy" + role = aws_iam_role.eks_fargate_execution_role.arn +} + +resource "aws_iam_role" "eks_cluster_role" { + name = "eks-cluster-role" + assume_role_policy = jsonencode({ + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "eks.amazonaws.com" + } + }] + Version = "2012-10-17" + }) +} + +resource "aws_iam_role_policy_attachment" "eks_cluster_policy_attachment" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy" + role = aws_iam_role.eks_cluster_role.name +} + +resource "aws_iam_role_policy_attachment" "eks_cluster_vpc_policy_attachment" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSVPCResourceController" + role = aws_iam_role.eks_cluster_role.name +} + +# EKS Resources +resource "aws_eks_cluster" "authentication_cluster" { + name = "authentication-cluster-${var.infra_env}" + role_arn = aws_iam_role.eks_cluster_role.arn + + vpc_config { + security_group_ids = [aws_security_group.public_sg.id, aws_security_group.private_sg.id] + subnet_ids = local.public_subnet_ids + } + + tags = { + "Environment" = var.infra_env + "Name" = "authentication-cluster-${var.infra_env}" + "Project" = "authentication-app" + "ManagedBy" = "terraform" + "Organization" = "andrewlod" + } +} + +resource "aws_eks_fargate_profile" "auth_cluster_fargate_profile" { + fargate_profile_name = "authentication-cluster-profile-${var.infra_env}" + cluster_name = aws_eks_cluster.authentication_cluster.name + pod_execution_role_arn = aws_iam_role.eks_fargate_execution_role.arn + subnet_ids = local.public_subnet_ids + + selector { + namespace = "default" + } +} \ No newline at end of file diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..ca798d1 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,80 @@ +variable "infra_env" { + type = string + description = "Infrastructure environment (prod/dev/test)" +} + +variable "aws_access_key_id" { + type = string + description = "AWS Access Key ID" + sensitive = true +} + +variable "aws_secret_access_key" { + type = string + description = "AWS Secret Access Key" + sensitive = true +} + +variable "aws_region" { + type = string + description = "AWS primary region to create resources" + default = "us-east-1" +} + +variable "vpc_cidr" { + type = string + description = "CIDR of the main VPC" + default = "10.0.0.0/24" +} + +variable "public_subnet_map" { + type = map(string) + description = "Mapping between public subnet AZs and CIDRs" + default = { + "us-east-1a" = "10.0.0.0/28" + "us-east-1b" = "10.0.0.16/28" + } +} + +variable "private_subnet_map" { + type = map(string) + description = "Mapping between private subnet AZs and CIDRs" + default = { + "us-east-1a" = "10.0.0.32/28" + "us-east-1b" = "10.0.0.48/28" + } +} + +variable "db_storage" { + type = number + description = "Amount of storage allocated into the RDS instance, in GB" + default = 10 +} + +variable "db_name" { + type = string + description = "Name of the RDS instance" +} + +variable "db_schema_name" { + type = string + description = "Name of the authentication system schema in the RDS database" +} + +variable "db_instance_type" { + type = string + description = "Type of the RDS instance" + default = "db.t3.micro" +} + +variable "db_username" { + type = string + description = "RDS database username" + sensitive = true +} + +variable "db_password" { + type = string + description = "RDS database password" + sensitive = true +} \ No newline at end of file