diff --git a/.github/workflows/api-rds-apply.yml b/.github/workflows/api-rds-apply.yml new file mode 100644 index 00000000..dcdd96e4 --- /dev/null +++ b/.github/workflows/api-rds-apply.yml @@ -0,0 +1,42 @@ +name: api-rds apply terraform + +on: + push: + branches: + - main + paths: + - .github/workflows/api-rds-apply.yml + - terraform/services/api-rds/** + workflow_dispatch: # Allow manual trigger + +jobs: + terraform-apply: + permissions: + contents: read + id-token: write + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./terraform/services/api-rds + strategy: + fail-fast: false + matrix: + app: [ab2d] + env: [test] + steps: + - uses: actions/checkout@v4 + - uses: ./actions/setup-tfenv-terraform + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ matrix.app == 'ab2d' && secrets[format('{0}_{1}_ACCOUNT', matrix.app, matrix.env)] || secrets.BCDA_ACCOUNT }}:role/delegatedadmin/developer/${{ matrix.app }}-${{ matrix.env }}-github-actions + aws-region: ${{ vars.AWS_REGION }} + - run: terraform init -backend-config=../../backends/${{ matrix.app }}-${{ matrix.env }}.s3.tfbackend + - uses: cmsgov/ab2d-bcda-dpc-platform/actions/aws-params-env-action@main + env: + AWS_REGION: ${{ vars.AWS_REGION }} + with: + params: | + TF_VAR_jenkins_security_group_id=/jenkins/security-group + - run: terraform apply -auto-approve + env: + TF_VAR_app: ${{ matrix.app }} diff --git a/.github/workflows/api-rds-plan.yml b/.github/workflows/api-rds-plan.yml index 7cb32683..cc836c43 100644 --- a/.github/workflows/api-rds-plan.yml +++ b/.github/workflows/api-rds-plan.yml @@ -3,6 +3,7 @@ name: api-rds plan terraform on: pull_request: paths: + - .github/workflows/api-rds-plan.yml - terraform/services/api-rds/** workflow_dispatch: # Allow manual trigger @@ -26,8 +27,8 @@ jobs: strategy: fail-fast: false matrix: - app: [ab2d, bcda, dpc] - env: [dev, test, sbx, prod] + app: [ab2d] + env: [test] steps: - uses: actions/checkout@v4 - uses: ./actions/setup-tfenv-terraform @@ -36,6 +37,12 @@ jobs: role-to-assume: arn:aws:iam::${{ matrix.app == 'ab2d' && secrets[format('{0}_{1}_ACCOUNT', matrix.app, matrix.env)] || secrets.BCDA_ACCOUNT }}:role/delegatedadmin/developer/${{ matrix.app }}-${{ matrix.env }}-github-actions aws-region: ${{ vars.AWS_REGION }} - run: terraform init -backend-config=../../backends/${{ matrix.app }}-${{ matrix.env }}.s3.tfbackend + - uses: cmsgov/ab2d-bcda-dpc-platform/actions/aws-params-env-action@main + env: + AWS_REGION: ${{ vars.AWS_REGION }} + with: + params: | + TF_VAR_jenkins_security_group_id=/jenkins/security-group - run: terraform plan env: TF_VAR_app: ${{ matrix.app }} diff --git a/terraform/services/api-rds/data.tf b/terraform/services/api-rds/data.tf new file mode 100644 index 00000000..8ce0299f --- /dev/null +++ b/terraform/services/api-rds/data.tf @@ -0,0 +1,54 @@ +locals { + secret_date = "2020-01-02-09-15-01" +} + +data "aws_default_tags" "data_tags" {} + +data "aws_secretsmanager_secret" "secret_database_password" { + name = "ab2d/${local.db_name}/module/db/database_password/${local.secret_date}" +} +data "aws_secretsmanager_secret_version" "database_password" { + secret_id = data.aws_secretsmanager_secret.secret_database_password.id +} + +data "aws_secretsmanager_secret" "secret_database_user" { + name = "ab2d/${local.db_name}/module/db/database_user/${local.secret_date}" +} +data "aws_secretsmanager_secret_version" "database_user" { + secret_id = data.aws_secretsmanager_secret.secret_database_user.id +} + +data "aws_caller_identity" "current" {} + +data "aws_region" "current" {} + +data "aws_vpc" "target_vpc" { + filter { + name = "tag:Name" + values = ["${local.db_name}"] + } +} + +data "aws_subnet" "private_subnet_a" { + filter { + name = "tag:Name" + values = ["${local.db_name}-private-a"] + } +} + +data "aws_subnet" "private_subnet_b" { + filter { + name = "tag:Name" + values = ["${local.db_name}-private-b"] + } +} + +data "aws_security_group" "controller_security_group_id" { + tags = { + Name = "${local.db_name}-deployment-controller-sg" + } +} + +data "aws_kms_alias" "main_kms" { + name = "alias/${local.db_name}-main-kms" +} diff --git a/terraform/services/api-rds/main.tf b/terraform/services/api-rds/main.tf index 6f13d72b..05cade5a 100644 --- a/terraform/services/api-rds/main.tf +++ b/terraform/services/api-rds/main.tf @@ -2,47 +2,132 @@ locals { db_name = { ab2d = { dev = "ab2d-dev" - test = "ab2d-east-prod-test" - sbx = "ab2d-sbx-sandbox" + test = "ab2d-east-impl" prod = "ab2d-east-prod" - } - bcda = { - dev = "${var.app}-${var.env}" - test = "${var.app}-${var.env}" - sbx = "${var.app}-${var.env}" - prod = "${var.app}-${var.env}" - } - dpc = { - dev = "${var.app}-${var.env}" - test = "${var.app}-${var.env}" - sbx = "${var.app}-${var.env}" - prod = "${var.app}-${var.env}" - } + sbx = "ab2d-sbx-sandbox" + }[var.env] + bcda = "${var.app}-${var.env}" + dpc = "${var.app}-${var.env}" + }[var.app] +} + +## Begin module/main.tf + +# Create database security group +resource "aws_security_group" "sg_database" { + name = "${local.db_name}-database-sg" + description = "${local.db_name} database security group" + vpc_id = data.aws_vpc.target_vpc.id + tags = merge( + data.aws_default_tags.data_tags.tags, + tomap({ "Name" = "${local.db_name}-database-sg" }) + ) + + lifecycle { + create_before_destroy = true } } +resource "aws_vpc_security_group_egress_rule" "egress_all" { + security_group_id = aws_security_group.sg_database.id + + description = "Allow all egress" + cidr_ipv4 = "0.0.0.0/0" + ip_protocol = -1 +} + +resource "aws_vpc_security_group_ingress_rule" "db_access_from_jenkins_agent" { + description = "Jenkins Agent Access" + from_port = "5432" + to_port = "5432" + ip_protocol = "tcp" + referenced_security_group_id = var.jenkins_security_group_id + security_group_id = aws_security_group.sg_database.id +} + +resource "aws_vpc_security_group_ingress_rule" "db_access_from_controller" { + description = "Controller Access" + from_port = "5432" + to_port = "5432" + ip_protocol = "tcp" + referenced_security_group_id = data.aws_security_group.controller_security_group_id.id + security_group_id = aws_security_group.sg_database.id +} + +# Create database subnet group + +resource "aws_db_subnet_group" "subnet_group" { + name = "${local.db_name}-rds-subnet-group" + subnet_ids = [data.aws_subnet.private_subnet_a.id, data.aws_subnet.private_subnet_b.id] +} + +# Create database parameter group + +resource "aws_db_parameter_group" "parameter_group" { + name = "${local.db_name}-rds-parameter-group-v15" + family = "postgres15" + + parameter { + name = "backslash_quote" + value = "safe_encoding" + apply_method = "immediate" + } + parameter { + name = "shared_preload_libraries" + value = "pg_stat_statements,pg_cron" + apply_method = "pending-reboot" + } + parameter { + name = "cron.database_name" + value = var.app == "ab2d" && var.env == "test" ? "impl" : var.env + apply_method = "pending-reboot" + } + parameter { + name = "statement_timeout" + value = "1200000" + apply_method = "immediate" + } +} + +# Create database instance + resource "aws_db_instance" "api" { - identifier = local.db_name[var.app][var.env] - allocated_storage = 500 - max_allocated_storage = 0 - storage_encrypted = true + allocated_storage = 500 + engine = "postgres" + engine_version = 15.5 + instance_class = "db.m6i.2xlarge" + identifier = local.db_name + storage_encrypted = true + deletion_protection = true enabled_cloudwatch_logs_exports = [ "postgresql", "upgrade", ] - deletion_protection = true - storage_type = "io1" - skip_final_snapshot = true - engine = "postgres" - iam_database_authentication_enabled = false - engine_version = "15.5" - instance_class = "db.m6i.2xlarge" - tags = { - Name = "${local.db_name[var.app][var.env]}-rds" - "cpm backup" = "Monthly" - contact = "ab2d-ops@semanticbits.com" - environment = "${local.db_name[var.app][var.env]}" - role = "db" - terraform_module = "data" + skip_final_snapshot = true + + db_subnet_group_name = aws_db_subnet_group.subnet_group.name + parameter_group_name = aws_db_parameter_group.parameter_group.name + backup_retention_period = 7 + iops = local.db_name == "ab2d-east-prod" ? "20000" : "5000" + apply_immediately = true + kms_key_id = data.aws_kms_alias.main_kms.target_key_arn + multi_az = local.db_name == "ab2d-east-prod" ? true : false + vpc_security_group_ids = [aws_security_group.sg_database.id] + username = data.aws_secretsmanager_secret_version.database_user.secret_string + password = data.aws_secretsmanager_secret_version.database_password.secret_string + # I'd really love to swap the password parameter here to manage_master_user_password since it's already in secrets store + + tags = merge( + data.aws_default_tags.data_tags.tags, + tomap({ "Name" = "${local.db_name}-rds", + "role" = "db", + "cpm backup" = "Monthly" + }) + ) + lifecycle { + ignore_changes = [ + engine_version, + parameter_group_name + ] } } diff --git a/terraform/services/api-rds/variables.tf b/terraform/services/api-rds/variables.tf index 0a3dea4b..4d96291c 100644 --- a/terraform/services/api-rds/variables.tf +++ b/terraform/services/api-rds/variables.tf @@ -1,3 +1,5 @@ +# Restricting valid "app" values until service has been extended or BCDA and DPC + variable "app" { description = "The application name (ab2d, bcda, dpc)" type = string @@ -15,3 +17,9 @@ variable "env" { error_message = "Valid value for env is dev, test, sbx, or prod." } } + +variable "jenkins_security_group_id" { + description = "Stores the security group managing Jenkins Agent for AB2D including account number for AB2D Management" + type = string + nullable = false +}