From 640072c6a66a7dd20e42c293e46a01cbb3ff1769 Mon Sep 17 00:00:00 2001 From: shri Date: Thu, 8 Aug 2024 20:54:23 +0200 Subject: [PATCH] Add opentofu infrastructure provisioning templates --- deploy/aws/Dockerfile.app | 86 ++++++ deploy/aws/README.md | 0 deploy/aws/ecs-task-definition-prod.json | 277 ++++++++++++++++++ deploy/aws/tf/main.tf | 114 +++++++ deploy/aws/tf/modules/alb/main.tf | 54 ++++ deploy/aws/tf/modules/alb/outputs.tf | 11 + deploy/aws/tf/modules/alb/variables.tf | 31 ++ deploy/aws/tf/modules/cloudwatch/main.tf | 11 + deploy/aws/tf/modules/cloudwatch/outputs.tf | 6 + deploy/aws/tf/modules/cloudwatch/variables.tf | 12 + deploy/aws/tf/modules/ecr/main.tf | 10 + deploy/aws/tf/modules/ecr/outputs.tf | 6 + deploy/aws/tf/modules/ecr/variables.tf | 11 + deploy/aws/tf/modules/ecs/main.tf | 34 +++ deploy/aws/tf/modules/ecs/outputs.tf | 6 + deploy/aws/tf/modules/ecs/variables.tf | 46 +++ deploy/aws/tf/modules/iam/main.tf | 136 +++++++++ deploy/aws/tf/modules/iam/outputs.tf | 16 + deploy/aws/tf/modules/iam/variables.tf | 47 +++ deploy/aws/tf/modules/jumpbox/main.tf | 39 +++ deploy/aws/tf/modules/jumpbox/outputs.tf | 6 + deploy/aws/tf/modules/jumpbox/variables.tf | 31 ++ deploy/aws/tf/modules/rds/main.tf | 59 ++++ deploy/aws/tf/modules/rds/outputs.tf | 11 + deploy/aws/tf/modules/rds/variables.tf | 26 ++ deploy/aws/tf/modules/redis/main.tf | 28 ++ deploy/aws/tf/modules/redis/outputs.tf | 6 + deploy/aws/tf/modules/redis/variables.tf | 26 ++ deploy/aws/tf/modules/s3/main.tf | 10 + deploy/aws/tf/modules/s3/outputs.tf | 6 + deploy/aws/tf/modules/s3/variables.tf | 11 + deploy/aws/tf/modules/vpc/main.tf | 146 +++++++++ deploy/aws/tf/modules/vpc/outputs.tf | 46 +++ deploy/aws/tf/modules/vpc/variables.tf | 17 ++ deploy/aws/tf/modules/vpc_endpoints/main.tf | 90 ++++++ .../aws/tf/modules/vpc_endpoints/outputs.tf | 13 + .../aws/tf/modules/vpc_endpoints/variables.tf | 36 +++ deploy/aws/tf/outputs.tf | 16 + deploy/aws/tf/variables.tf | 43 +++ 39 files changed, 1580 insertions(+) create mode 100644 deploy/aws/Dockerfile.app create mode 100644 deploy/aws/README.md create mode 100644 deploy/aws/ecs-task-definition-prod.json create mode 100644 deploy/aws/tf/main.tf create mode 100644 deploy/aws/tf/modules/alb/main.tf create mode 100644 deploy/aws/tf/modules/alb/outputs.tf create mode 100644 deploy/aws/tf/modules/alb/variables.tf create mode 100644 deploy/aws/tf/modules/cloudwatch/main.tf create mode 100644 deploy/aws/tf/modules/cloudwatch/outputs.tf create mode 100644 deploy/aws/tf/modules/cloudwatch/variables.tf create mode 100644 deploy/aws/tf/modules/ecr/main.tf create mode 100644 deploy/aws/tf/modules/ecr/outputs.tf create mode 100644 deploy/aws/tf/modules/ecr/variables.tf create mode 100644 deploy/aws/tf/modules/ecs/main.tf create mode 100644 deploy/aws/tf/modules/ecs/outputs.tf create mode 100644 deploy/aws/tf/modules/ecs/variables.tf create mode 100644 deploy/aws/tf/modules/iam/main.tf create mode 100644 deploy/aws/tf/modules/iam/outputs.tf create mode 100644 deploy/aws/tf/modules/iam/variables.tf create mode 100644 deploy/aws/tf/modules/jumpbox/main.tf create mode 100644 deploy/aws/tf/modules/jumpbox/outputs.tf create mode 100644 deploy/aws/tf/modules/jumpbox/variables.tf create mode 100644 deploy/aws/tf/modules/rds/main.tf create mode 100644 deploy/aws/tf/modules/rds/outputs.tf create mode 100644 deploy/aws/tf/modules/rds/variables.tf create mode 100644 deploy/aws/tf/modules/redis/main.tf create mode 100644 deploy/aws/tf/modules/redis/outputs.tf create mode 100644 deploy/aws/tf/modules/redis/variables.tf create mode 100644 deploy/aws/tf/modules/s3/main.tf create mode 100644 deploy/aws/tf/modules/s3/outputs.tf create mode 100644 deploy/aws/tf/modules/s3/variables.tf create mode 100644 deploy/aws/tf/modules/vpc/main.tf create mode 100644 deploy/aws/tf/modules/vpc/outputs.tf create mode 100644 deploy/aws/tf/modules/vpc/variables.tf create mode 100644 deploy/aws/tf/modules/vpc_endpoints/main.tf create mode 100644 deploy/aws/tf/modules/vpc_endpoints/outputs.tf create mode 100644 deploy/aws/tf/modules/vpc_endpoints/variables.tf create mode 100644 deploy/aws/tf/outputs.tf create mode 100644 deploy/aws/tf/variables.tf diff --git a/deploy/aws/Dockerfile.app b/deploy/aws/Dockerfile.app new file mode 100644 index 00000000..e83c3ab8 --- /dev/null +++ b/deploy/aws/Dockerfile.app @@ -0,0 +1,86 @@ +ARG PYTHON_BUILDER_IMAGE=3.12-slim-bookworm + +## ---------------------------------------------------------------------------------- ## +## ------------------------- Python base -------------------------------------------- ## +## ---------------------------------------------------------------------------------- ## +FROM python:${PYTHON_BUILDER_IMAGE} as python-base +ENV PIP_DEFAULT_TIMEOUT=100 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PIP_NO_CACHE_DIR=1 \ + PIP_ROOT_USER_ACTION=ignore \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONFAULTHANDLER=1 \ + PYTHONHASHSEED=random \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 +RUN apt-get update \ + && apt-get upgrade -y \ + && apt-get install -y --no-install-recommends git tini curl \ + && apt-get install -y dnsutils \ + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /root/.cache \ + && rm -rf /var/apt/lists/* \ + && rm -rf /var/cache/apt/* \ + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false\ + && mkdir -p /workspace/app \ + && pip install --quiet -U pip wheel setuptools virtualenv + +## ---------------------------------------------------------------------------------- ## +## ------------------------- Python build base -------------------------------------- ## +## ---------------------------------------------------------------------------------- ## +FROM python-base AS build-base +ARG PDM_INSTALL_ARGS="" +ENV PDM_INSTALL_ARGS="${PDM_INSTALL_ARGS}" \ + GRPC_PYTHON_BUILD_WITH_CYTHON=1 \ + PATH="/workspace/app/.venv/bin:/usr/local/bin:$PATH" +## -------------------------- add build packages ----------------------------------- ## +RUN apt-get install -y --no-install-recommends build-essential curl \ + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /root/.cache \ + && rm -rf /var/apt/lists/* \ + && rm -rf /var/cache/apt/* \ + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false + +## -------------------------- install application ----------------------------------- ## +WORKDIR /workspace/app +COPY pyproject.toml pdm.lock README.md .pre-commit-config.yaml .pylintrc LICENSE.md Makefile \ + ./ +COPY scripts ./scripts/ +RUN python -m venv --copies /workspace/app/.venv \ + && /workspace/app/.venv/bin/pip install --quiet pdm nodeenv cython \ + && pdm install ${PDM_INSTALL_ARGS} --no-self \ + && pdm export ${PDM_INSTALL_ARGS} --without-hashes --prod --output=requirements.txt +COPY src ./src/ +RUN pdm build + + +## ---------------------------------------------------------------------------------- ## +## -------------------------------- runtime build ----------------------------------- ## +## ---------------------------------------------------------------------------------- ## +## ------------------------- use base image ---------------------------------------- ## + +FROM python-base as run-image +ARG ENV_SECRETS="runtime-secrets" +ARG LITESTAR_APP="app.asgi:app" +ENV ENV_SECRETS="${ENV_SECRETS}" \ + LITESTAR_APP="${LITESTAR_APP}" + +RUN addgroup --system --gid 65532 nonroot \ + && adduser --no-create-home --system --uid 65532 nonroot \ + && chown -R nonroot:nonroot /workspace +## -------------------------- install application ----------------------------------- ## +COPY --from=build-base --chown=65532:65532 /workspace/app/requirements.txt /tmp/requirements.txt +COPY --from=build-base --chown=65532:65532 /workspace/app/dist /tmp/ +WORKDIR /workspace/app +RUN pip install --quiet --disable-pip-version-check --no-deps --requirement=/tmp/requirements.txt +RUN pip install --quiet --disable-pip-version-check --no-deps /tmp/*.whl + +USER nonroot +STOPSIGNAL SIGINT +EXPOSE 8000 +ENTRYPOINT [ "tini", "--" ] +CMD [ "litestar", "run", "--host", "0.0.0.0" ] +VOLUME /workspace/app diff --git a/deploy/aws/README.md b/deploy/aws/README.md new file mode 100644 index 00000000..e69de29b diff --git a/deploy/aws/ecs-task-definition-prod.json b/deploy/aws/ecs-task-definition-prod.json new file mode 100644 index 00000000..7ff7a0c5 --- /dev/null +++ b/deploy/aws/ecs-task-definition-prod.json @@ -0,0 +1,277 @@ +{ + "family": "chapter-app-family-prod", + "containerDefinitions": [ + { + "name": "app", + "image": "050206582437.dkr.ecr.eu-central-1.amazonaws.com/chapter-app-prod:latest", + "cpu": 256, + "memory": 512, + "essential": true, + "command": ["litestar", "run", "--host", "0.0.0.0"], + "dependsOn": [ + { + "condition": "SUCCESS", + "containerName": "db-migrator" + }, + { + "condition": "SUCCESS", + "containerName": "worker" + } + ], + "portMappings": [ + { + "containerPort": 8000, + "hostPort": 8000, + "protocol": "tcp" + } + ], + "environment": [ + { + "name": "LITESTAR_APP", + "value": "app.asgi:create_app" + }, + { + "name": "APP_ENVIRONMENT", + "value": "docker" + }, + { + "name": "OPENAPI_CONTACT_EMAIL", + "value": "dev@nectar.run" + }, + { + "name": "OPENAPI_CONTACT_NAME", + "value": "Devs" + }, + { + "name": "OPENAPI_TITLE", + "value": "Chapter API" + }, + { + "name": "DB_MIGRATION_DDL_VERSION_TABLE", + "value": "ddl_version" + }, + { "name": "SAQ_USE_SERVER_LIFESPAN", "value": "false" } + ], + "secrets": [ + { + "name": "APP_SECRET_KEY", + "valueFrom": "arn:aws:ssm:eu-central-1:050206582437:parameter/prod/chapter/app/app-secret-key" + }, + { + "name": "SLACK_ALERTS_URL", + "valueFrom": "arn:aws:ssm:eu-central-1:050206582437:parameter/prod/chapter/app/slack-alerts-url" + }, + { + "name": "DB_PASSWORD", + "valueFrom": "arn:aws:secretsmanager:eu-central-1:050206582437:secret:prod/nectar/rds-Jig3q1:password::" + }, + { + "name": "DB_HOST", + "valueFrom": "arn:aws:secretsmanager:eu-central-1:050206582437:secret:prod/nectar/rds-YzLzXz:host::" + }, + { + "name": "DB_PORT", + "valueFrom": "arn:aws:secretsmanager:eu-central-1:050206582437:secret:prod/nectar/rds-YzLzXz:port::" + }, + { + "name": "DB_USER", + "valueFrom": "arn:aws:secretsmanager:eu-central-1:050206582437:secret:prod/nectar/rds-YzLzXz:username::" + }, + { + "name": "DB_NAME", + "valueFrom": "arn:aws:secretsmanager:eu-central-1:050206582437:secret:prod/nectar/rds-YzLzXz:dbname::" + } + ], + "healthCheck": { + "command": [ + "CMD-SHELL", + "curl -f http://localhost:8000/health || exit 1" + ], + "interval": 30, + "timeout": 5, + "retries": 3, + "startPeriod": 60 + }, + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-create-group": "true", + "awslogs-group": "/ecs/chapter-app-app-prod", + "awslogs-region": "eu-central-1", + "awslogs-stream-prefix": "ecs" + } + } + }, + { + "name": "db-migrator", + "image": "050206582437.dkr.ecr.eu-central-1.amazonaws.com/nectar:latest", + "cpu": 256, + "memory": 512, + "essential": false, + "command": ["litestar", "database", "upgrade", "--no-prompt"], + "environment": [ + { + "name": "LITESTAR_APP", + "value": "app.asgi:create_app" + }, + { + "name": "APP_ENVIRONMENT", + "value": "docker" + }, + { + "name": "OPENAPI_CONTACT_EMAIL", + "value": "dev@nectar.run" + }, + { + "name": "OPENAPI_CONTACT_NAME", + "value": "Devs" + }, + { + "name": "OPENAPI_TITLE", + "value": "Nectar API" + }, + { + "name": "DB_MIGRATION_DDL_VERSION_TABLE", + "value": "ddl_version" + }, + { "name": "SAQ_USE_SERVER_LIFESPAN", "value": "false" } + ], + "secrets": [ + { + "name": "APP_SECRET_KEY", + "valueFrom": "arn:aws:ssm:eu-central-1:050206582437:parameter/prod/chapter/app/app-secret-key" + }, + { + "name": "SLACK_ALERTS_URL", + "valueFrom": "arn:aws:ssm:eu-central-1:050206582437:parameter/prod/chapter/app/slack-alerts-url" + }, + { + "name": "DB_PASSWORD", + "valueFrom": "arn:aws:secretsmanager:eu-central-1:050206582437:secret:prod/nectar/rds-Jig3q1:password::" + }, + { + "name": "DB_HOST", + "valueFrom": "arn:aws:secretsmanager:eu-central-1:050206582437:secret:prod/nectar/rds-YzLzXz:host::" + }, + { + "name": "DB_PORT", + "valueFrom": "arn:aws:secretsmanager:eu-central-1:050206582437:secret:prod/nectar/rds-YzLzXz:port::" + }, + { + "name": "DB_USER", + "valueFrom": "arn:aws:secretsmanager:eu-central-1:050206582437:secret:prod/nectar/rds-YzLzXz:username::" + }, + { + "name": "DB_NAME", + "valueFrom": "arn:aws:secretsmanager:eu-central-1:050206582437:secret:prod/nectar/rds-YzLzXz:dbname::" + } + ], + "healthCheck": { + "command": ["CMD-SHELL", "exit 0"], + "interval": 30, + "timeout": 5, + "retries": 3, + "startPeriod": 60 + }, + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-create-group": "true", + "awslogs-group": "/ecs/chapter-app-db-migrator-prod", + "awslogs-region": "eu-central-1", + "awslogs-stream-prefix": "ecs" + } + } + }, + { + "name": "worker", + "image": "050206582437.dkr.ecr.eu-central-1.amazonaws.com/chapter:latest", + "cpu": 256, + "memory": 512, + "essential": false, + "command": ["litestar", "workers", "run"], + "environment": [ + { + "name": "LITESTAR_APP", + "value": "app.asgi:create_app" + }, + { + "name": "APP_ENVIRONMENT", + "value": "docker" + }, + { + "name": "OPENAPI_CONTACT_EMAIL", + "value": "dev@nectar.run" + }, + { + "name": "OPENAPI_CONTACT_NAME", + "value": "Devs" + }, + { + "name": "OPENAPI_TITLE", + "value": "Nectar API" + }, + { + "name": "DB_MIGRATION_DDL_VERSION_TABLE", + "value": "ddl_version" + }, + { "name": "SAQ_USE_SERVER_LIFESPAN", "value": "false" } + ], + "secrets": [ + { + "name": "APP_SECRET_KEY", + "valueFrom": "arn:aws:ssm:eu-central-1:050206582437:parameter/prod/chapter/app/app-secret-key" + }, + { + "name": "SLACK_ALERTS_URL", + "valueFrom": "arn:aws:ssm:eu-central-1:050206582437:parameter/prod/chapter/app/slack-alerts-url" + }, + { + "name": "DB_PASSWORD", + "valueFrom": "arn:aws:secretsmanager:eu-central-1:050206582437:secret:prod/nectar/rds-Jig3q1:password::" + }, + { + "name": "DB_HOST", + "valueFrom": "arn:aws:secretsmanager:eu-central-1:050206582437:secret:prod/nectar/rds-YzLzXz:host::" + }, + { + "name": "DB_PORT", + "valueFrom": "arn:aws:secretsmanager:eu-central-1:050206582437:secret:prod/nectar/rds-YzLzXz:port::" + }, + { + "name": "DB_USER", + "valueFrom": "arn:aws:secretsmanager:eu-central-1:050206582437:secret:prod/nectar/rds-YzLzXz:username::" + }, + { + "name": "DB_NAME", + "valueFrom": "arn:aws:secretsmanager:eu-central-1:050206582437:secret:prod/nectar/rds-YzLzXz:dbname::" + } + ], + "healthCheck": { + "command": ["CMD-SHELL", "exit 0"], + "interval": 30, + "timeout": 5, + "retries": 3, + "startPeriod": 60 + }, + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-create-group": "true", + "awslogs-group": "/ecs/chapter-app-worker-prod", + "awslogs-region": "eu-central-1", + "awslogs-stream-prefix": "ecs" + } + } + } + ], + "runtimePlatform": { + "operatingSystemFamily": "LINUX" + }, + "networkMode": "awsvpc", + "requiresCompatibilities": ["FARGATE"], + "cpu": 1024, + "memory": 2048, + "executionRoleArn": "arn:aws:iam::050206582437:role/chapter-app-ecs-task-execution-role-prod", + "taskRoleArn": "arn:aws:iam::050206582437:role/chapter-app-ecs-task-role-prod" +} diff --git a/deploy/aws/tf/main.tf b/deploy/aws/tf/main.tf new file mode 100644 index 00000000..67099112 --- /dev/null +++ b/deploy/aws/tf/main.tf @@ -0,0 +1,114 @@ +# tf/main.tf +# +data "aws_caller_identity" "current" {} + +locals { + ecs_task_definition_json = file("../ecs-task-definition-${var.environment}.json") +} + +terraform { + backend "s3" { + bucket = "${var.app_name}-tf-backend" # Replace with your S3 bucket name + key = "${var.environment}/state/terraform.tfstate" # Replace with your desired state file path + region = var.region # Replace with your AWS region + dynamodb_table = "${var.app_name}-tf-lock-table-${var.environment}" # Optional: for state locking + encrypt = true # Encrypt the state file at rest + } +} + +provider "aws" { + region = var.region +} + +module "vpc" { + source = "./modules/vpc" + app_name = var.app_name + environment = var.environment +} + +module "vpc_endpoints" { + source = "./modules/vpc_endpoints" + app_name = var.app_name + environment = var.environment + region = var.region + vpc_id = module.vpc.vpc_id + private_subnets = module.vpc.private_subnets + security_group_ids = [module.vpc.rds_security_group_id] + public_route_table_ids = module.vpc.public_route_table_ids +} + +module "alb" { + source = "./modules/alb" + app_name = var.app_name + environment = var.environment + domain_name = var.domain_name + vpc_id = module.vpc.vpc_id + public_subnets = module.vpc.public_subnets + security_groups = [module.vpc.alb_security_group_id] +} + +module "ecs" { + source = "./modules/ecs" + app_name = var.app_name + environment = var.environment + vpc_id = module.vpc.vpc_id + private_subnets = module.vpc.private_subnets + ecs_task_execution_role = module.iam.ecs_execution_role_arn + ecs_task_role = module.iam.ecs_task_role_arn + alb_target_group_arn = module.alb.target_group_arn + security_groups = [module.vpc.ecs_security_group_id] + ecs_task_definition_json = local.ecs_task_definition_json +} + +module "rds" { + source = "./modules/rds" + app_name = var.app_name + environment = var.environment + vpc_id = module.vpc.vpc_id + private_subnets = module.vpc.private_subnets + security_group_ids = [module.vpc.rds_security_group_id] +} + +module "jumpbox" { + source = "./modules/jumpbox" + app_name = var.app_name + environment = var.environment + vpc_id = module.vpc.vpc_id + public_subnet_id = module.vpc.public_subnets[0] + security_group_ids = [module.vpc.ec2_jumpbox_security_group_id] + ec2_key_pair_name = var.ec2_key_pair_name +} + +module "s3" { + source = "./modules/s3" + app_name = var.app_name + environment = var.environment +} + +module "redis" { + source = "./modules/redis" + app_name = var.app_name + environment = var.environment + vpc_id = module.vpc.vpc_id + private_subnets = module.vpc.private_subnets + security_group_ids = [module.vpc.redis_security_group_id] +} + +module "cloudwatch" { + source = "./modules/cloudwatch" + app_name = var.app_name + environment = var.environment +} + +module "iam" { + source = "./modules/iam" + app_name = var.app_name + environment = var.environment + region = var.region + aws_account_id = data.aws_caller_identity.current.account_id + app_bucket_name = module.s3.app_bucket_name + rds_db_id = module.rds.rds_db_id + cloudwatch_log_group_name = module.cloudwatch.cloudwatch_log_group_name + github_repo = var.github_repo + github_branch = var.github_branch +} diff --git a/deploy/aws/tf/modules/alb/main.tf b/deploy/aws/tf/modules/alb/main.tf new file mode 100644 index 00000000..81a07406 --- /dev/null +++ b/deploy/aws/tf/modules/alb/main.tf @@ -0,0 +1,54 @@ +# tf/modules/alb/main.tf + +resource "aws_lb" "app_alb" { + name = "${var.app_name}-app-alb-${var.environment}" + internal = false + load_balancer_type = "application" + security_groups = var.security_groups + subnets = var.public_subnets + + tags = { + Name = "app-alb" + Environment = var.environment + } + + depends_on = [var.vpc_id] +} + +resource "aws_lb_target_group" "app_tg" { + name = "${var.app_name}-tg-${var.environment}" + port = 80 + protocol = "HTTP" + target_type = "ip" + vpc_id = var.vpc_id + + health_check { + path = "/health" + protocol = "HTTP" + interval = 30 + timeout = 5 + healthy_threshold = 5 + unhealthy_threshold = 2 + } + + depends_on = [var.vpc_id] +} + +data "aws_acm_certificate" "domain_cert" { + domain = var.domain_name + statuses = ["ISSUED"] +} + +resource "aws_lb_listener" "https" { + load_balancer_arn = aws_lb.app_alb.arn + port = "443" + protocol = "HTTPS" + certificate_arn = data.aws_acm_certificate.domain_cert.arn + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.app_tg.arn + } + + depends_on = [aws_lb_target_group.app_tg] +} diff --git a/deploy/aws/tf/modules/alb/outputs.tf b/deploy/aws/tf/modules/alb/outputs.tf new file mode 100644 index 00000000..194d3ab4 --- /dev/null +++ b/deploy/aws/tf/modules/alb/outputs.tf @@ -0,0 +1,11 @@ +# tf/modules/alb/outputs.tf + +output "alb_dns_name" { + description = "DNS name of the ALB" + value = aws_lb.app_alb.dns_name +} + +output "target_group_arn" { + description = "ARN of the ALB target group" + value = aws_lb_target_group.app_tg.arn +} diff --git a/deploy/aws/tf/modules/alb/variables.tf b/deploy/aws/tf/modules/alb/variables.tf new file mode 100644 index 00000000..090a4bde --- /dev/null +++ b/deploy/aws/tf/modules/alb/variables.tf @@ -0,0 +1,31 @@ +# tf/modules/alb/variables.tf + +variable "app_name" { + description = "App name" + type = string +} + +variable "environment" { + description = "Environment name (e.g., dev, prod)" + type = string +} + +variable "domain_name" { + description = "App domain name, used to reference the right certificate" + type = string +} + +variable "vpc_id" { + description = "VPC ID where ALB will be deployed" + type = string +} + +variable "public_subnets" { + description = "Public subnet IDs for ALB" + type = list(string) +} + +variable "security_groups" { + description = "Security groups for ALB" + type = list(string) +} diff --git a/deploy/aws/tf/modules/cloudwatch/main.tf b/deploy/aws/tf/modules/cloudwatch/main.tf new file mode 100644 index 00000000..f33c8e99 --- /dev/null +++ b/deploy/aws/tf/modules/cloudwatch/main.tf @@ -0,0 +1,11 @@ +# tf/modules/cloudwatch/main.tf + +resource "aws_cloudwatch_log_group" "app_log_group" { + name = "/ecs/${var.app_name}-app-${var.environment}" + retention_in_days = 7 + + tags = { + Name = "${var.app_name}-log-group-${var.environment}" + Env = var.environment + } +} diff --git a/deploy/aws/tf/modules/cloudwatch/outputs.tf b/deploy/aws/tf/modules/cloudwatch/outputs.tf new file mode 100644 index 00000000..fd15301b --- /dev/null +++ b/deploy/aws/tf/modules/cloudwatch/outputs.tf @@ -0,0 +1,6 @@ +# tf/modules/cloudwatch/outputs.tf + +output "cloudwatch_log_group_name" { + description = "Name of the CloudWatch Log Group" + value = aws_cloudwatch_log_group.app_log_group.name +} diff --git a/deploy/aws/tf/modules/cloudwatch/variables.tf b/deploy/aws/tf/modules/cloudwatch/variables.tf new file mode 100644 index 00000000..e4f30d4a --- /dev/null +++ b/deploy/aws/tf/modules/cloudwatch/variables.tf @@ -0,0 +1,12 @@ +# tf/modules/clouddwatch/variables.tf + +variable "app_name" { + description = "App name" + type = string +} + +variable "environment" { + description = "Environment name (e.g., dev, prod)" + type = string +} + diff --git a/deploy/aws/tf/modules/ecr/main.tf b/deploy/aws/tf/modules/ecr/main.tf new file mode 100644 index 00000000..aefd2bb7 --- /dev/null +++ b/deploy/aws/tf/modules/ecr/main.tf @@ -0,0 +1,10 @@ +# tf/modules/ecr/main.tf + +resource "aws_ecr_repository" "app_repository" { + name = "${var.app_name}-app-repository-${var.environment}" + + tags = { + Name = "${var.app_name}-app-repository-${var.environment}" + Environment = var.environment + } +} diff --git a/deploy/aws/tf/modules/ecr/outputs.tf b/deploy/aws/tf/modules/ecr/outputs.tf new file mode 100644 index 00000000..44da6def --- /dev/null +++ b/deploy/aws/tf/modules/ecr/outputs.tf @@ -0,0 +1,6 @@ +# tf/modules/ecr/outputs.tf + +output "repository_url" { + description = "URL of the ECR repository" + value = aws_ecr_repository.app_repository.repository_url +} diff --git a/deploy/aws/tf/modules/ecr/variables.tf b/deploy/aws/tf/modules/ecr/variables.tf new file mode 100644 index 00000000..2d31bb35 --- /dev/null +++ b/deploy/aws/tf/modules/ecr/variables.tf @@ -0,0 +1,11 @@ +# tf/modules/ecr/variables.tf + +variable "app_name" { + description = "App name" + type = string +} + +variable "environment" { + description = "The environment name (e.g., dev, staging, prod)" + type = string +} diff --git a/deploy/aws/tf/modules/ecs/main.tf b/deploy/aws/tf/modules/ecs/main.tf new file mode 100644 index 00000000..45d5a1e2 --- /dev/null +++ b/deploy/aws/tf/modules/ecs/main.tf @@ -0,0 +1,34 @@ +# tf/modules/ecs/main.tf + +resource "aws_ecs_cluster" "app_cluster" { + name = "${var.app_name}-cluster-${var.environment}" +} + +resource "aws_ecs_task_definition" "app_task" { + family = jsondecode(var.ecs_task_definition_json).family + network_mode = jsondecode(var.ecs_task_definition_json).networkMode + requires_compatibilities = jsondecode(var.ecs_task_definition_json).requiresCompatibilities + cpu = jsondecode(var.ecs_task_definition_json).cpu + memory = jsondecode(var.ecs_task_definition_json).memory + execution_role_arn = var.ecs_task_execution_role + task_role_arn = var.ecs_task_role + container_definitions = jsonencode(jsondecode(var.ecs_task_definition_json).containerDefinitions) +} + +resource "aws_ecs_service" "app_service" { + name = "${var.app_name}-service-${var.environment}" + cluster = aws_ecs_cluster.app_cluster.id + task_definition = aws_ecs_task_definition.app_task.arn + desired_count = 1 + launch_type = "FARGATE" + network_configuration { + subnets = var.private_subnets + security_groups = var.security_groups + assign_public_ip = false + } + load_balancer { + target_group_arn = var.alb_target_group_arn + container_name = "app" + container_port = 8000 + } +} diff --git a/deploy/aws/tf/modules/ecs/outputs.tf b/deploy/aws/tf/modules/ecs/outputs.tf new file mode 100644 index 00000000..9f37a452 --- /dev/null +++ b/deploy/aws/tf/modules/ecs/outputs.tf @@ -0,0 +1,6 @@ +# tf/modules/ecs/outputs.tf + +output "ecs_cluster_name" { + description = "Name of the ECS cluster" + value = aws_ecs_cluster.app_cluster.name +} diff --git a/deploy/aws/tf/modules/ecs/variables.tf b/deploy/aws/tf/modules/ecs/variables.tf new file mode 100644 index 00000000..d6362bc5 --- /dev/null +++ b/deploy/aws/tf/modules/ecs/variables.tf @@ -0,0 +1,46 @@ +# tf/modules/ecs/variables.tf + +variable "app_name" { + description = "App name" + type = string +} + +variable "environment" { + description = "Environment name (e.g., dev, prod)" + type = string +} + +variable "vpc_id" { + description = "VPC ID" + type = string +} + +variable "private_subnets" { + description = "Private subnet IDs for ECS tasks" + type = list(string) +} + +variable "ecs_task_execution_role" { + description = "IAM role for ECS task execution" + type = string +} + +variable "ecs_task_role" { + description = "IAM role for ECS task" + type = string +} + +variable "alb_target_group_arn" { + description = "ALB target group ARN" + type = string +} + +variable "security_groups" { + description = "Security groups for ECS tasks" + type = list(string) +} + +variable "ecs_task_definition_json" { + description = "ECS task definition json" + type = string +} diff --git a/deploy/aws/tf/modules/iam/main.tf b/deploy/aws/tf/modules/iam/main.tf new file mode 100644 index 00000000..4ff2ac63 --- /dev/null +++ b/deploy/aws/tf/modules/iam/main.tf @@ -0,0 +1,136 @@ +# tf/modules/iam/main.tf + +resource "aws_iam_role" "ecs_task_execution_role" { + name = "${var.app_name}-ecs-task-execution-role-${var.environment}" + + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [{ + Action = "sts:AssumeRole", + Effect = "Allow", + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + }] + }) + + managed_policy_arns = [ + "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", + "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole" + ] +} + +resource "aws_iam_role" "ecs_task_role" { + name = "${var.app_name}-ecs-task-role-${var.environment}" + + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [{ + Action = "sts:AssumeRole", + Effect = "Allow", + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + }] + }) + + managed_policy_arns = [ + "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", + ] + + inline_policy { + name = "${var.app_name}-ecs-task-outbound-access-policy-${var.environment}" + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = ["ssm:GetParameters", "ssm:GetParameter"], + Resource = "arn:aws:ssm:${var.region}:${var.aws_account_id}:parameter/${var.app_name}/${var.environment}/*" + }, + { + Effect = "Allow", + Action = ["secretsmanager:GetSecretValue"], + Resource = "arn:aws:secretsmanager:${var.region}:${var.aws_account_id}:secret:${var.app_name}/${var.environment}/*" + }, + { + Effect = "Allow", + Action = ["s3:ListBucket", "s3:GetObject"], + Resource = "arn:aws:s3:::${var.app_bucket_name}/*" + }, + { + Effect = "Allow", + Action = ["rds:Connect"], + Resource = "arn:aws:rds:${var.region}:${var.aws_account_id}:db:${var.rds_db_id}" + }, + { + Effect = "Allow", + Action = ["elasticache:DescribeCacheClusters"], + Resource = "*" + }, + { + Effect = "Allow", + Action = ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"], + Resource = "arn:aws:logs:${var.region}:${var.aws_account_id}:log-group:/ecs/${var.app_name}*:*" + } + ] + }) + } +} + +# Define the IAM Role with OIDC trust policy +resource "aws_iam_role" "github_actions_role" { + name = "${var.app_name}-github-actions-role-${var.environment}" + + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Principal = { + Federated = "arn:aws:iam::${var.aws_account_id}:oidc-provider/token.actions.githubusercontent.com" + }, + Action = "sts:AssumeRoleWithWebIdentity", + Condition = { + StringEquals = { + "token.actions.githubusercontent.com:sub" : "repo:${var.github_repo}:ref:refs/heads/${var.github_branch}" + } + } + } + ] + }) +} + +# Define IAM Policy for ECS and ECR permissions +resource "aws_iam_policy" "github_actions_policy" { + name = "${var.app_name}-github-actions-policy-${var.environment}" + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = [ + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + "ecr:CompleteLayerUpload", + "ecr:UploadLayerPart", + "ecr:InitiateLayerUpload", + "ecr:PutImage", + "ecr:BatchCheckLayerAvailability", + "ecs:UpdateService", + "ecs:DescribeServices", + "ecs:DescribeTaskDefinition", + "ecs:RegisterTaskDefinition" + ], + Resource = "*" + } + ] + }) +} + +# Attach the policy to the role +resource "aws_iam_role_policy_attachment" "attach_github_actions_policy" { + role = aws_iam_role.github_actions_role.name + policy_arn = aws_iam_policy.github_actions_policy.arn +} diff --git a/deploy/aws/tf/modules/iam/outputs.tf b/deploy/aws/tf/modules/iam/outputs.tf new file mode 100644 index 00000000..f0bbd5c6 --- /dev/null +++ b/deploy/aws/tf/modules/iam/outputs.tf @@ -0,0 +1,16 @@ +# tf/modules/iam/outputs.tf + +output "ecs_execution_role_arn" { + description = "ARN of the ECS task execution role" + value = aws_iam_role.ecs_task_execution_role.arn +} + +output "ecs_task_role_arn" { + description = "ARN of the ECS task role" + value = aws_iam_role.ecs_task_role.arn +} + +output "github_actions_role_arn" { + description = "The ARN of the IAM Role for GitHub Actions" + value = aws_iam_role.github_actions_role.arn +} diff --git a/deploy/aws/tf/modules/iam/variables.tf b/deploy/aws/tf/modules/iam/variables.tf new file mode 100644 index 00000000..e798acfb --- /dev/null +++ b/deploy/aws/tf/modules/iam/variables.tf @@ -0,0 +1,47 @@ +# tf/modules/iam/variables.tf + +variable "app_name" { + description = "App name" + type = string +} + +variable "environment" { + description = "Environment name (e.g., dev, prod)" + type = string +} + +variable "region" { + description = "AWS region" + type = string +} + +variable "aws_account_id" { + description = "AWS Account ID" + type = string +} + +variable "app_bucket_name" { + description = "App's s3 bucket" + type = string +} + +variable "rds_db_id" { + description = "RDS database ID" + type = string +} + +variable "cloudwatch_log_group_name" { + description = "Cloudwatch log group name" + type = string +} + +variable "github_repo" { + description = "GitHub repository in the format 'owner/repo'" + type = string +} + +variable "github_branch" { + description = "GitHub branch to allow access" + type = string + default = "main" +} diff --git a/deploy/aws/tf/modules/jumpbox/main.tf b/deploy/aws/tf/modules/jumpbox/main.tf new file mode 100644 index 00000000..c90d0555 --- /dev/null +++ b/deploy/aws/tf/modules/jumpbox/main.tf @@ -0,0 +1,39 @@ +# tf/modules/jumpbox/main.tf + +data "aws_ami" "aws_linux" { + most_recent = true + owners = ["137112412989"] # Amazon + + filter { + name = "name" + values = ["al2023-ami-2023.5.20240805.0-kernel-6.1-x86_64"] + } +} + +resource "aws_instance" "ec2_jumpbox" { + ami = data.aws_ami.aws_linux.id + instance_type = "t3.micro" + subnet_id = var.public_subnet_id + vpc_security_group_ids = var.security_group_ids + key_name = var.ec2_key_pair_name + associate_public_ip_address = true + + tags = { + Name = "${var.app_name}-ec2-jumpbox-${var.environment}" + Environment = var.environment + } + + depends_on = [var.vpc_id, var.public_subnet_id] +} + +# Allocate an Elastic IP +resource "aws_eip" "ec2_jumpbox_eip" { + depends_on = [aws_instance.ec2_jumpbox] +} + +# Associate the Elastic IP with the EC2 instance +resource "aws_eip_association" "ec2_jumpbox_eip_assoc" { + instance_id = aws_instance.ec2_jumpbox.id + allocation_id = aws_eip.ec2_jumpbox_eip.id + depends_on = [aws_instance.ec2_jumpbox, aws_eip.ec2_jumpbox_eip] +} diff --git a/deploy/aws/tf/modules/jumpbox/outputs.tf b/deploy/aws/tf/modules/jumpbox/outputs.tf new file mode 100644 index 00000000..a5591ca7 --- /dev/null +++ b/deploy/aws/tf/modules/jumpbox/outputs.tf @@ -0,0 +1,6 @@ +# tf/modules/jumpbox/outputs.tf + +output "jumpbox_id" { + description = "ID of the Jumpbox EC2 instance" + value = aws_instance.ec2_jumpbox.id +} diff --git a/deploy/aws/tf/modules/jumpbox/variables.tf b/deploy/aws/tf/modules/jumpbox/variables.tf new file mode 100644 index 00000000..8711d5d1 --- /dev/null +++ b/deploy/aws/tf/modules/jumpbox/variables.tf @@ -0,0 +1,31 @@ +# tf/modules/jumpbox/variables.tf + +variable "app_name" { + description = "App name" + type = string +} + +variable "environment" { + description = "Environment name (e.g., dev, prod)" + type = string +} + +variable "vpc_id" { + description = "VPC ID" + type = string +} + +variable "public_subnet_id" { + description = "Public subnet ID for the jumpbox" + type = string +} + +variable "security_group_ids" { + description = "Security group IDs for the jumpbox" + type = list(string) +} + +variable "ec2_key_pair_name" { + description = "SSH key name for the EC2 instance" + type = string +} diff --git a/deploy/aws/tf/modules/rds/main.tf b/deploy/aws/tf/modules/rds/main.tf new file mode 100644 index 00000000..a37c3470 --- /dev/null +++ b/deploy/aws/tf/modules/rds/main.tf @@ -0,0 +1,59 @@ +# tf/modules/rds/main.tf + +resource "aws_db_subnet_group" "app_db_subnet_group" { + name = "${var.app_name}-db-subnet-group-${var.environment}" + subnet_ids = var.private_subnets + + tags = { + Name = "${var.app_name}-db-subnet-group-${var.environment}" + Environment = var.environment + } + + depends_on = [var.vpc_id] +} + +resource "random_password" "app_db_password" { + length = 16 + special = true +} + +resource "aws_secretsmanager_secret" "app_db_password" { + name = "${var.app_name}-db-password-${var.environment}" + description = "The RDS database password" +} + +resource "aws_secretsmanager_secret_version" "app_db_password_version" { + secret_id = aws_secretsmanager_secret.app_db_password.id + secret_string = random_password.app_db_password.result + depends_on = [aws_secretsmanager_secret.app_db_password, random_password.app_db_password] +} + +data "aws_secretsmanager_secret_version" "app_db_password_version_data" { + secret_id = aws_secretsmanager_secret.app_db_password.id + depends_on = [aws_secretsmanager_secret.app_db_password, aws_secretsmanager_secret_version.app_db_password_version] +} + +resource "aws_db_instance" "app_db" { + identifier = "${var.app_name}-db-${var.environment}" + engine = "postgres" + instance_class = "db.t4g.micro" + allocated_storage = 20 + storage_type = "gp2" + username = "app_user" + password = data.aws_secretsmanager_secret_version.app_db_password_version_data.secret_string + db_subnet_group_name = aws_db_subnet_group.app_db_subnet_group.name + vpc_security_group_ids = var.security_group_ids + + skip_final_snapshot = true + + backup_retention_period = 7 # Retain backups for 7 days + backup_window = "05:00-06:00" # Define a backup window (optional) + maintenance_window = "Sun:07:00-Sun:13:00" # Define a maintenance window (optional) + + tags = { + Name = "${var.app_name}-db-${var.environment}" + Enviorment = var.environment + } + + depends_on = [var.vpc_id, aws_secretsmanager_secret.app_db_password] +} diff --git a/deploy/aws/tf/modules/rds/outputs.tf b/deploy/aws/tf/modules/rds/outputs.tf new file mode 100644 index 00000000..51f4fcd3 --- /dev/null +++ b/deploy/aws/tf/modules/rds/outputs.tf @@ -0,0 +1,11 @@ +# tf/modules/rds/outputs.tf + +output "rds_db_id" { + description = "Identifier of the RDS instance" + value = aws_db_instance.app_db.identifier +} + +output "rds_endpoint" { + description = "Endpoint of the RDS instance" + value = aws_db_instance.app_db.endpoint +} diff --git a/deploy/aws/tf/modules/rds/variables.tf b/deploy/aws/tf/modules/rds/variables.tf new file mode 100644 index 00000000..fb0da496 --- /dev/null +++ b/deploy/aws/tf/modules/rds/variables.tf @@ -0,0 +1,26 @@ +# tf/modules/rds/variables.tf + +variable "app_name" { + description = "App name" + type = string +} + +variable "environment" { + description = "Environment name (e.g., dev, prod)" + type = string +} + +variable "vpc_id" { + description = "VPC ID" + type = string +} + +variable "private_subnets" { + description = "Private subnet IDs for RDS" + type = list(string) +} + +variable "security_group_ids" { + description = "Security group IDs for RDS" + type = list(string) +} diff --git a/deploy/aws/tf/modules/redis/main.tf b/deploy/aws/tf/modules/redis/main.tf new file mode 100644 index 00000000..6c5d1354 --- /dev/null +++ b/deploy/aws/tf/modules/redis/main.tf @@ -0,0 +1,28 @@ +# tf/modules/redis/main.tf + +resource "aws_elasticache_subnet_group" "redis_subnet_group" { + name = "${var.app_name}-redis-subnet-group-${var.environment}" + subnet_ids = var.private_subnets + + tags = { + Name = "${var.app_name}-redis-subnet-group-${var.environment}" + } + + depends_on = [var.vpc_id] +} + +resource "aws_elasticache_cluster" "redis_cluster" { + cluster_id = "${var.app_name}-redis-cluster-${var.environment}" + engine = "redis" + node_type = "cache.t3.micro" + num_cache_nodes = 1 + subnet_group_name = aws_elasticache_subnet_group.redis_subnet_group.name + security_group_ids = var.security_group_ids + + tags = { + Name = "${var.app_name}-redis-cluster-${var.environment}" + Environment = var.environment + } + + depends_on = [var.vpc_id, aws_elasticache_subnet_group.redis_subnet_group] +} diff --git a/deploy/aws/tf/modules/redis/outputs.tf b/deploy/aws/tf/modules/redis/outputs.tf new file mode 100644 index 00000000..5f30cfbc --- /dev/null +++ b/deploy/aws/tf/modules/redis/outputs.tf @@ -0,0 +1,6 @@ +# tf/modules/redis/outputs.tf + +output "redis_endpoint" { + description = "Endpoint of the Redis cluster" + value = aws_elasticache_cluster.redis_cluster.cache_nodes.0.address +} diff --git a/deploy/aws/tf/modules/redis/variables.tf b/deploy/aws/tf/modules/redis/variables.tf new file mode 100644 index 00000000..233f032b --- /dev/null +++ b/deploy/aws/tf/modules/redis/variables.tf @@ -0,0 +1,26 @@ +# tf/modules/redis/variables.tf + +variable "app_name" { + description = "App name" + type = string +} + +variable "environment" { + description = "Environment name (e.g., dev, prod)" + type = string +} + +variable "vpc_id" { + description = "VPC ID" + type = string +} + +variable "private_subnets" { + description = "Private subnet IDs for Redis" + type = list(string) +} + +variable "security_group_ids" { + description = "Security group IDs for Redis" + type = list(string) +} diff --git a/deploy/aws/tf/modules/s3/main.tf b/deploy/aws/tf/modules/s3/main.tf new file mode 100644 index 00000000..4b0f187f --- /dev/null +++ b/deploy/aws/tf/modules/s3/main.tf @@ -0,0 +1,10 @@ +# tf/modules/s3/main.tf + +resource "aws_s3_bucket" "app_bucket" { + bucket = "${var.app_name}-bucket-${var.environment}" + + tags = { + Name = "${var.app_name}-bucket-${var.environment}" + Environment = var.environment + } +} diff --git a/deploy/aws/tf/modules/s3/outputs.tf b/deploy/aws/tf/modules/s3/outputs.tf new file mode 100644 index 00000000..4b046d68 --- /dev/null +++ b/deploy/aws/tf/modules/s3/outputs.tf @@ -0,0 +1,6 @@ +# tf/modules/s3/outputs.tf + +output "app_bucket_name" { + description = "Name of the S3 bucket" + value = aws_s3_bucket.app_bucket.bucket +} diff --git a/deploy/aws/tf/modules/s3/variables.tf b/deploy/aws/tf/modules/s3/variables.tf new file mode 100644 index 00000000..df7f90f8 --- /dev/null +++ b/deploy/aws/tf/modules/s3/variables.tf @@ -0,0 +1,11 @@ +# tf/modules/s3/variables.tf + +variable "app_name" { + description = "App name" + type = string +} + +variable "environment" { + description = "Environment name (e.g., dev, prod)" + type = string +} diff --git a/deploy/aws/tf/modules/vpc/main.tf b/deploy/aws/tf/modules/vpc/main.tf new file mode 100644 index 00000000..f1220f13 --- /dev/null +++ b/deploy/aws/tf/modules/vpc/main.tf @@ -0,0 +1,146 @@ +# tf/modules/vpc/main.tf + +module "vpc" { + # TODO: Find equivalent module in opentofu registry + source = "terraform-aws-modules/vpc/aws" + + name = "${var.app_name}-vpc-${var.environment}" + cidr = var.cidr_block + + azs = ["eu-central-1a", "eu-central-1b"] + private_subnets = ["10.0.1.0/24", "10.0.2.0/24"] + public_subnets = ["10.0.101.0/24", "10.0.102.0/24"] + + enable_nat_gateway = true + single_nat_gateway = true + + tags = { + OpenTofu = "true", + Environment = var.environment + } +} + +resource "aws_security_group" "alb_sg" { + vpc_id = module.vpc.vpc_id + + ingress { + from_port = 443 + to_port = 443 + 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"] + } + + tags = { + Name = "alb-sg" + Environment = var.environment + } + + depends_on = [module.vpc.vpc_id] +} + +resource "aws_security_group" "ecs_sg" { + vpc_id = module.vpc.vpc_id + + ingress { + from_port = 8000 + to_port = 8000 + protocol = "tcp" + security_groups = [aws_security_group.alb_sg.id] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "ecs-sg" + Environment = var.environment + } + + depends_on = [module.vpc.vpc_id] +} + +resource "aws_security_group" "ec2_jumpbox_sg" { + vpc_id = module.vpc.vpc_id + + ingress { + 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"] + } + + tags = { + Name = "jumpbox-sg" + Environment = var.environment + } + + depends_on = [module.vpc.vpc_id] +} + +resource "aws_security_group" "rds_sg" { + vpc_id = module.vpc.vpc_id + + ingress { + from_port = 5432 + to_port = 5432 + protocol = "tcp" + security_groups = [aws_security_group.ecs_sg.id, aws_security_group.ec2_jumpbox_sg.id] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "rds-sg" + Environment = var.environment + } + + depends_on = [module.vpc.vpc_id] +} + +resource "aws_security_group" "redis_sg" { + vpc_id = module.vpc.vpc_id + + ingress { + from_port = 6379 + to_port = 6379 + protocol = "tcp" + security_groups = [aws_security_group.ecs_sg.id] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "redis-sg" + Environment = var.environment + } + + depends_on = [module.vpc.vpc_id] +} diff --git a/deploy/aws/tf/modules/vpc/outputs.tf b/deploy/aws/tf/modules/vpc/outputs.tf new file mode 100644 index 00000000..0ae838b8 --- /dev/null +++ b/deploy/aws/tf/modules/vpc/outputs.tf @@ -0,0 +1,46 @@ +# tf/modules/vpc/outputs.tf + +output "vpc_id" { + description = "VPC ID" + value = module.vpc.vpc_id +} + +output "private_subnets" { + description = "Private subnet IDs" + value = module.vpc.private_subnets +} + +output "public_subnets" { + description = "Public subnet IDs" + value = module.vpc.public_subnets +} + +output "public_route_table_ids" { + description = "Public route table IDs" + value = module.vpc.public_route_table_ids +} + +output "alb_security_group_id" { + description = "ALB security group ID" + value = aws_security_group.alb_sg.id +} + +output "ecs_security_group_id" { + description = "ECS security group ID" + value = aws_security_group.ecs_sg.id +} + +output "rds_security_group_id" { + description = "RDS security group ID" + value = aws_security_group.rds_sg.id +} + +output "ec2_jumpbox_security_group_id" { + description = "Jumpbox security group ID" + value = aws_security_group.ec2_jumpbox_sg.id +} + +output "redis_security_group_id" { + description = "Redis security group ID" + value = aws_security_group.redis_sg.id +} diff --git a/deploy/aws/tf/modules/vpc/variables.tf b/deploy/aws/tf/modules/vpc/variables.tf new file mode 100644 index 00000000..e3314e96 --- /dev/null +++ b/deploy/aws/tf/modules/vpc/variables.tf @@ -0,0 +1,17 @@ +# tf/modules/vpc/variables.tf + +variable "app_name" { + description = "App name" + type = string +} + +variable "environment" { + description = "Environment name (e.g., dev, prod)" + type = string +} + +variable "cidr_block" { + description = "CIDR block for the VPC" + type = string + default = "10.0.0.0/16" +} diff --git a/deploy/aws/tf/modules/vpc_endpoints/main.tf b/deploy/aws/tf/modules/vpc_endpoints/main.tf new file mode 100644 index 00000000..5a60379e --- /dev/null +++ b/deploy/aws/tf/modules/vpc_endpoints/main.tf @@ -0,0 +1,90 @@ +# tf/modules/vpc_endpoints/main.tf + +resource "aws_vpc_endpoint" "s3" { + vpc_id = var.vpc_id + service_name = "com.amazonaws.${var.region}.s3" + vpc_endpoint_type = "Gateway" + route_table_ids = var.public_route_table_ids + + tags = { + Name = "s3-vpc-endpoint" + Environment = var.environment + } + + depends_on = [var.vpc_id] +} + +resource "aws_vpc_endpoint" "cw_logs" { + vpc_id = var.vpc_id + service_name = "com.amazonaws.${var.region}.logs" + vpc_endpoint_type = "Interface" + subnet_ids = var.private_subnets + security_group_ids = var.security_group_ids + + tags = { + Name = "cw-logs-vpc-endpoint" + Environment = var.environment + } + + depends_on = [var.vpc_id] +} + +resource "aws_vpc_endpoint" "ssm" { + vpc_id = var.vpc_id + service_name = "com.amazonaws.${var.region}.ssm" + vpc_endpoint_type = "Interface" + subnet_ids = var.private_subnets + security_group_ids = var.security_group_ids + + tags = { + Name = "ssm-vpc-endpoint" + Environment = var.environment + } + + depends_on = [var.vpc_id] +} + +resource "aws_vpc_endpoint" "secretsmanager" { + vpc_id = var.vpc_id + service_name = "com.amazonaws.${var.region}.secretsmanager" + vpc_endpoint_type = "Interface" + subnet_ids = var.private_subnets + security_group_ids = var.security_group_ids + + tags = { + Name = "secretsmanager-vpc-endpoint" + Environment = var.environment + } + + depends_on = [var.vpc_id] +} + +resource "aws_vpc_endpoint" "rds" { + vpc_id = var.vpc_id + service_name = "com.amazonaws.${var.region}.rds" + vpc_endpoint_type = "Interface" + subnet_ids = var.private_subnets + security_group_ids = var.security_group_ids + + tags = { + Name = "rds-vpc-endpoint" + Environment = var.environment + } + + depends_on = [var.vpc_id] +} + +resource "aws_vpc_endpoint" "redis" { + vpc_id = var.vpc_id + service_name = "com.amazonaws.${var.region}.elasticache" + vpc_endpoint_type = "Interface" + subnet_ids = var.private_subnets + security_group_ids = var.security_group_ids + + tags = { + Name = "redis-vpc-endpoint" + Environment = var.environment + } + + depends_on = [var.vpc_id] +} diff --git a/deploy/aws/tf/modules/vpc_endpoints/outputs.tf b/deploy/aws/tf/modules/vpc_endpoints/outputs.tf new file mode 100644 index 00000000..ac5fef43 --- /dev/null +++ b/deploy/aws/tf/modules/vpc_endpoints/outputs.tf @@ -0,0 +1,13 @@ +# tf/modules/vpc_endpoints/outputs.tf + +output "vpc_endpoint_ids" { + description = "IDs of the VPC endpoints" + value = [ + aws_vpc_endpoint.s3.id, + aws_vpc_endpoint.cw_logs.id, + aws_vpc_endpoint.ssm.id, + aws_vpc_endpoint.secretsmanager.id, + aws_vpc_endpoint.rds.id, + aws_vpc_endpoint.redis.id + ] +} diff --git a/deploy/aws/tf/modules/vpc_endpoints/variables.tf b/deploy/aws/tf/modules/vpc_endpoints/variables.tf new file mode 100644 index 00000000..d1683ddf --- /dev/null +++ b/deploy/aws/tf/modules/vpc_endpoints/variables.tf @@ -0,0 +1,36 @@ +# terraform/modules/vpc_endpoints/variables.tf + +variable "app_name" { + description = "App name" + type = string +} + +variable "environment" { + description = "Environment name (e.g., dev, prod)" + type = string +} + +variable "region" { + description = "AWS region" + type = string +} + +variable "vpc_id" { + description = "VPC ID" + type = string +} + +variable "private_subnets" { + description = "Private subnet IDs for VPC endpoints" + type = list(string) +} + +variable "security_group_ids" { + description = "Security group IDs for VPC endpoints" + type = list(string) +} + +variable "public_route_table_ids" { + description = "Public route table IDs" + type = list(string) +} diff --git a/deploy/aws/tf/outputs.tf b/deploy/aws/tf/outputs.tf new file mode 100644 index 00000000..0c0b9d8f --- /dev/null +++ b/deploy/aws/tf/outputs.tf @@ -0,0 +1,16 @@ +# tf/outputs.tf + +output "alb_dns_name" { + description = "DNS name of the ALB" + value = module.alb.alb_dns_name +} + +output "rds_endpoint" { + description = "RDS endpoint" + value = module.rds.rds_endpoint +} + +output "redis_endpoint" { + description = "Redis endpoint" + value = module.redis.redis_endpoint +} diff --git a/deploy/aws/tf/variables.tf b/deploy/aws/tf/variables.tf new file mode 100644 index 00000000..145e0ed3 --- /dev/null +++ b/deploy/aws/tf/variables.tf @@ -0,0 +1,43 @@ +# tf/variables.tf + +variable "region" { + description = "AWS Region" + type = string + default = "eu-central-1" +} + +variable "environment" { + description = "Environment name (e.g., dev, prod)" + type = string + default = "prod" +} + +variable "app_name" { + description = "App name" + type = string + default = "chapter-app" +} + +variable "domain_name" { + description = "App domain name, used to reference the right certificate" + type = string + default = "*.chapter.show" +} + +variable "ec2_key_pair_name" { + description = "EC2 key-pair name for the jumpbox" + type = string + default = "nectar-shri" +} + +variable "github_repo" { + description = "GitHub repository in the format 'owner/repo'" + type = string + default = "nectar-run/chapter" +} + +variable "github_branch" { + description = "GitHub branch to allow access" + type = string + default = "main" +}