From c4401974cdc7fd3aeb8ac0dffbaa374940cc67d6 Mon Sep 17 00:00:00 2001 From: Brage Gording Date: Mon, 8 Sep 2025 16:44:32 +0100 Subject: [PATCH 1/7] Introduce a read-only user for data-replication - User must be created within aws account due to database living in private subnet - For simplicity just create user on startup of application if does not already exist - Password is managed via terraform and injected into container --- bin/data-replication | 46 ++++++++++++++++++++ bin/docker-start | 3 ++ terraform/data_replication/ecs.tf | 3 +- terraform/data_replication/iam.tf | 3 +- terraform/data_replication/rds.tf | 2 +- terraform/data_replication/ssm_parameters.tf | 26 +++++++++++ terraform/data_replication/variables.tf | 4 ++ 7 files changed, 83 insertions(+), 4 deletions(-) create mode 100755 bin/data-replication create mode 100644 terraform/data_replication/ssm_parameters.tf diff --git a/bin/data-replication b/bin/data-replication new file mode 100755 index 0000000000..015b2c5a88 --- /dev/null +++ b/bin/data-replication @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +# Data replication script - creates read-only database role and keeps container running + +set -e + +echo "Starting data replication setup..." + +if [ -z "$READ_ONLY_DB_PASSWORD" ]; then + echo "Error: READ_ONLY_DB_PASSWORD environment variable is not set" + exit 1 +fi + +echo "Creating read-only database role..." + +bundle exec rails runner " +begin + ActiveRecord::Base.connection.execute(\" + DO \$\$ + BEGIN + IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'grafana_ro') THEN + CREATE ROLE grafana_ro WITH LOGIN PASSWORD '#{ENV['READ_ONLY_DB_PASSWORD']}'; + ELSE + ALTER ROLE grafana_ro WITH PASSWORD '#{ENV['READ_ONLY_DB_PASSWORD']}'; + END IF; + END + \$\$; + \") + + ActiveRecord::Base.connection.execute(\" + GRANT CONNECT ON DATABASE #{ActiveRecord::Base.connection.current_database} TO grafana_ro; + GRANT USAGE ON SCHEMA public TO grafana_ro; + GRANT SELECT ON ALL TABLES IN SCHEMA public TO grafana_ro; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO grafana_ro; + \") + + puts 'Read-only role created/updated successfully' +rescue => e + puts \"Error creating read-only role: \#{e.message}\" + exit 1 +end +" + +echo "Data replication setup completed. Keeping container running..." + +exec tail -f /dev/null \ No newline at end of file diff --git a/bin/docker-start b/bin/docker-start index 52fe86d30d..15c63be851 100755 --- a/bin/docker-start +++ b/bin/docker-start @@ -8,6 +8,9 @@ if [ "$SERVER_TYPE" == "web" ]; then elif [ "$SERVER_TYPE" == "sidekiq" ]; then echo "Starting sidekiq server..." exec "$BIN_DIR"/sidekiq +elif [ "$SERVER_TYPE" == "data-replication" ]; then + echo "Starting data replication server..." + exec "$BIN_DIR"/data-replication elif [ "$SERVER_TYPE" == "none" ]; then echo "No server started" exec tail -f /dev/null # Keep container running diff --git a/terraform/data_replication/ecs.tf b/terraform/data_replication/ecs.tf index 8b6cd87b4c..bbfd27f5fa 100644 --- a/terraform/data_replication/ecs.tf +++ b/terraform/data_replication/ecs.tf @@ -26,8 +26,7 @@ module "db_access_service" { subnets = local.subnet_list vpc_id = aws_vpc.vpc.id } - server_type = "none" - server_type_name = "data-replication" + server_type = "data-replication" task_config = { environment = local.task_envs secrets = local.task_secrets diff --git a/terraform/data_replication/iam.tf b/terraform/data_replication/iam.tf index 099c05e31e..33251ce855 100644 --- a/terraform/data_replication/iam.tf +++ b/terraform/data_replication/iam.tf @@ -11,7 +11,8 @@ data "aws_iam_policy_document" "ecs_permissions" { sid = "dbSecretSid" actions = ["secretsmanager:GetSecretValue"] resources = [ - var.db_secret_arn + var.db_secret_arn, + aws_secretsmanager_secret.ro_db_password.arn ] effect = "Allow" } diff --git a/terraform/data_replication/rds.tf b/terraform/data_replication/rds.tf index 0c1515c318..730e9850d1 100644 --- a/terraform/data_replication/rds.tf +++ b/terraform/data_replication/rds.tf @@ -36,7 +36,7 @@ resource "aws_rds_cluster" "cluster" { } lifecycle { - ignore_changes = [cluster_identifier] + ignore_changes = [cluster_identifier, snapshot_identifier] } } diff --git a/terraform/data_replication/ssm_parameters.tf b/terraform/data_replication/ssm_parameters.tf new file mode 100644 index 0000000000..b344279bc3 --- /dev/null +++ b/terraform/data_replication/ssm_parameters.tf @@ -0,0 +1,26 @@ +# Create a password that is automatically populated in secrets manager using the random password generator for aws + +# Generate a random password for the read-only database user +ephemeral "aws_secretsmanager_random_password" "ro_db_password" { +} + +# Store the generated password in AWS Secrets Manager +resource "aws_secretsmanager_secret" "ro_db_password" { + name = "${local.name_prefix}-ro-db-password-${substr(uuid(), 0, 4)}" + description = "Read-only database user password for data replication" + recovery_window_in_days = 7 + + tags = { + Name = "${local.name_prefix}-ro-db-password" + } + lifecycle { + ignore_changes = [name] + replace_triggered_by = [aws_rds_cluster.cluster] + } +} + +resource "aws_secretsmanager_secret_version" "ro_db_password" { + secret_id = aws_secretsmanager_secret.ro_db_password.id + secret_string_wo = ephemeral.aws_secretsmanager_random_password.ro_db_password.random_password + secret_string_wo_version = 1 +} diff --git a/terraform/data_replication/variables.tf b/terraform/data_replication/variables.tf index 7bdea5566e..983580a189 100644 --- a/terraform/data_replication/variables.tf +++ b/terraform/data_replication/variables.tf @@ -124,6 +124,10 @@ locals { { name = "RAILS_MASTER_KEY" valueFrom = var.rails_master_key_path + }, + { + name = "READ_ONLY_DB_PASSWORD" + valueFrom = aws_secretsmanager_secret.ro_db_password.arn } ] } From d3dc8574aaecbff4dc3f203e83844763ace48883 Mon Sep 17 00:00:00 2001 From: Brage Gording Date: Thu, 11 Sep 2025 11:46:17 +0100 Subject: [PATCH 2/7] Working postgress connection - Security group to handle incoming/outgoing connections to grafana - Grafana now has connection to VPC allowing DB access - To reliably reference data-replication resources new tags have been included --- terraform/app/modules/ecs_service/main.tf | 5 +- .../app/modules/ecs_service/variables.tf | 7 +++ terraform/data_replication/ecs.tf | 3 +- terraform/data_replication/network.tf | 34 ++++++----- terraform/data_replication/rds.tf | 22 +++++++ terraform/data_replication/ssm_parameters.tf | 3 +- terraform/data_replication/variables.tf | 5 +- terraform/monitoring/aws/variables.tf | 4 +- terraform/monitoring/aws/workspace.tf | 61 +++++++++++++++++++ terraform/monitoring/grafana/main.tf | 2 + 10 files changed, 122 insertions(+), 24 deletions(-) diff --git a/terraform/app/modules/ecs_service/main.tf b/terraform/app/modules/ecs_service/main.tf index 1f220e7f72..af8197a1c0 100644 --- a/terraform/app/modules/ecs_service/main.tf +++ b/terraform/app/modules/ecs_service/main.tf @@ -17,12 +17,13 @@ resource "aws_security_group" "this" { } } -resource "aws_security_group_rule" "egress_all" { +resource "aws_security_group_rule" "egress" { + count = min(length(var.default_egress_cidr_blocks), 1) type = "egress" from_port = 0 to_port = 0 protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] + cidr_blocks = var.default_egress_cidr_blocks security_group_id = aws_security_group.this.id } diff --git a/terraform/app/modules/ecs_service/variables.tf b/terraform/app/modules/ecs_service/variables.tf index 785da0c6c3..be4d067fa0 100644 --- a/terraform/app/modules/ecs_service/variables.tf +++ b/terraform/app/modules/ecs_service/variables.tf @@ -17,6 +17,13 @@ variable "server_type_name" { nullable = true } +variable "default_egress_cidr_blocks" { + type = list(string) + description = "The default CIDR blocks for egress rules from the service. Defaults to allow all outbound traffic." + default = ["0.0.0.0/0"] + nullable = false +} + variable "minimum_replica_count" { type = number description = "Minimum amount of allowed replicas for the service. Also the replica count when creating th service." diff --git a/terraform/data_replication/ecs.tf b/terraform/data_replication/ecs.tf index bbfd27f5fa..a1d7eac865 100644 --- a/terraform/data_replication/ecs.tf +++ b/terraform/data_replication/ecs.tf @@ -39,5 +39,6 @@ module "db_access_service" { region = var.region health_check_command = ["CMD-SHELL", "echo 'alive' || exit 1"] } - depends_on = [aws_rds_cluster_instance.instance] + default_egress_cidr_blocks = var.allowed_egress_cidr_blocks + depends_on = [aws_rds_cluster_instance.instance] } diff --git a/terraform/data_replication/network.tf b/terraform/data_replication/network.tf index dbbbbafed4..7b6344b587 100644 --- a/terraform/data_replication/network.tf +++ b/terraform/data_replication/network.tf @@ -11,12 +11,18 @@ resource "aws_subnet" "subnet_a" { vpc_id = aws_vpc.vpc.id cidr_block = "10.0.1.0/24" availability_zone = "${var.region}a" + tags = { + Private = true + } } resource "aws_subnet" "subnet_b" { vpc_id = aws_vpc.vpc.id cidr_block = "10.0.2.0/24" availability_zone = "${var.region}b" + tags = { + Private = true + } } resource "aws_route_table" "private" { @@ -36,45 +42,43 @@ resource "aws_subnet" "public_subnet" { vpc_id = aws_vpc.vpc.id cidr_block = "10.0.3.0/24" availability_zone = "${var.region}a" + tags = { + Private = false + } } -resource "aws_internet_gateway" "internet_gateway" { - count = local.shared_egress_infrastructure_count +resource "aws_internet_gateway" "this" { vpc_id = aws_vpc.vpc.id tags = { Name = "data-replication-igw-${var.environment}" } } -resource "aws_eip" "nat_ip" { - count = local.shared_egress_infrastructure_count +resource "aws_eip" "this" { domain = "vpc" - depends_on = [aws_internet_gateway.internet_gateway] + depends_on = [aws_internet_gateway.this] } -resource "aws_nat_gateway" "nat_gateway" { - count = local.shared_egress_infrastructure_count +resource "aws_nat_gateway" "this" { subnet_id = aws_subnet.public_subnet.id - allocation_id = aws_eip.nat_ip[0].id + allocation_id = aws_eip.this.id connectivity_type = "public" - depends_on = [aws_internet_gateway.internet_gateway] + depends_on = [aws_internet_gateway.this] tags = { Name = "data-replication-nat-gateway-${var.environment}" } } resource "aws_route" "private_to_public" { - count = length(var.allowed_egress_cidr_blocks) route_table_id = aws_route_table.private.id - destination_cidr_block = var.allowed_egress_cidr_blocks[count.index] - nat_gateway_id = aws_nat_gateway.nat_gateway[0].id + nat_gateway_id = aws_nat_gateway.this.id + destination_cidr_block = "0.0.0.0/0" } resource "aws_route" "public_to_igw" { - count = length(var.allowed_egress_cidr_blocks) route_table_id = aws_route_table.public.id - destination_cidr_block = var.allowed_egress_cidr_blocks[count.index] - gateway_id = aws_internet_gateway.internet_gateway[0].id + gateway_id = aws_internet_gateway.this.id + destination_cidr_block = "0.0.0.0/0" } resource "aws_route_table" "public" { diff --git a/terraform/data_replication/rds.tf b/terraform/data_replication/rds.tf index 730e9850d1..e57496ae00 100644 --- a/terraform/data_replication/rds.tf +++ b/terraform/data_replication/rds.tf @@ -5,6 +5,18 @@ resource "aws_db_subnet_group" "dbsg" { resource "aws_security_group" "rds" { vpc_id = aws_vpc.vpc.id + tags = { + Name = "${local.name_prefix}-rds-sg" + } +} + +resource "aws_security_group_rule" "ecs_to_grafana" { + type = "egress" + from_port = 5432 + to_port = 5432 + protocol = "tcp" + security_group_id = module.db_access_service.security_group_id + source_security_group_id = aws_security_group.rds.id } resource "aws_security_group_rule" "rds_inbound" { @@ -16,6 +28,16 @@ resource "aws_security_group_rule" "rds_inbound" { source_security_group_id = module.db_access_service.security_group_id } +resource "aws_security_group_rule" "rds_inbound_grafana" { + type = "ingress" + from_port = 5432 + to_port = 5432 + protocol = "tcp" + security_group_id = aws_security_group.rds.id + cidr_blocks = [aws_vpc.vpc.cidr_block] + description = "Allow Grafana workspace access to PostgreSQL" +} + resource "aws_rds_cluster" "cluster" { cluster_identifier = "${local.name_prefix}-rds-${formatdate("hh-mm-ss", timestamp())}" engine = "aurora-postgresql" diff --git a/terraform/data_replication/ssm_parameters.tf b/terraform/data_replication/ssm_parameters.tf index b344279bc3..a17726af43 100644 --- a/terraform/data_replication/ssm_parameters.tf +++ b/terraform/data_replication/ssm_parameters.tf @@ -14,8 +14,7 @@ resource "aws_secretsmanager_secret" "ro_db_password" { Name = "${local.name_prefix}-ro-db-password" } lifecycle { - ignore_changes = [name] - replace_triggered_by = [aws_rds_cluster.cluster] + ignore_changes = [name] } } diff --git a/terraform/data_replication/variables.tf b/terraform/data_replication/variables.tf index 983580a189..45cb3c0cf6 100644 --- a/terraform/data_replication/variables.tf +++ b/terraform/data_replication/variables.tf @@ -82,9 +82,8 @@ variable "rails_master_key_path" { } locals { - name_prefix = "mavis-${var.environment}-data-replication" - subnet_list = [aws_subnet.subnet_a.id, aws_subnet.subnet_b.id] - shared_egress_infrastructure_count = min(length(var.allowed_egress_cidr_blocks), 1) + name_prefix = "mavis-${var.environment}-data-replication" + subnet_list = [aws_subnet.subnet_a.id, aws_subnet.subnet_b.id] task_envs = [ { diff --git a/terraform/monitoring/aws/variables.tf b/terraform/monitoring/aws/variables.tf index 906aa9a696..37e9dbc006 100644 --- a/terraform/monitoring/aws/variables.tf +++ b/terraform/monitoring/aws/variables.tf @@ -34,5 +34,7 @@ locals { AWS-Mavis-ReadOnly = "16b29214-60a1-7008-ff52-0ccd29b7e2d4" } } - bucket_name = "nhse-mavis-grafana-${var.environment}" + bucket_name = "nhse-mavis-grafana-${var.environment}" + prefix_environment = var.environment == "development" ? "qa" : var.environment + data_replication_prefix = "mavis-${local.prefix_environment}-data-replication" } diff --git a/terraform/monitoring/aws/workspace.tf b/terraform/monitoring/aws/workspace.tf index 71d9abd911..d165e77cc1 100644 --- a/terraform/monitoring/aws/workspace.tf +++ b/terraform/monitoring/aws/workspace.tf @@ -14,6 +14,10 @@ resource "aws_grafana_workspace" "this" { } }) data_sources = ["CLOUDWATCH"] + vpc_configuration { + security_group_ids = [aws_security_group.grafana_workspace.id] + subnet_ids = data.aws_subnets.data_replication.ids + } } resource "aws_grafana_role_association" "role" { @@ -28,3 +32,60 @@ resource "aws_grafana_workspace_service_account" "grafana_provider" { grafana_role = "ADMIN" workspace_id = aws_grafana_workspace.this.id } + +locals { +} + +resource "aws_security_group" "grafana_workspace" { + name_prefix = "grafana-workspace-${var.environment}" + description = "Security group for Grafana workspace" + vpc_id = data.aws_vpcs.data_replication.ids[0] + tags = { + Name = "grafana-workspace-sg-${var.environment}" + } +} + +resource "aws_security_group_rule" "egress_rds" { + type = "egress" + from_port = 5432 + to_port = 5432 + protocol = "tcp" + security_group_id = aws_security_group.grafana_workspace.id + cidr_blocks = ["10.0.0.0/16"] +} + +resource "aws_security_group_rule" "egress_443" { + type = "egress" + from_port = 443 + to_port = 443 + protocol = "tcp" + security_group_id = aws_security_group.grafana_workspace.id + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_security_group_rule" "ingress_443" { + type = "ingress" + from_port = 443 + to_port = 443 + protocol = "tcp" + security_group_id = aws_security_group.grafana_workspace.id + cidr_blocks = ["0.0.0.0/0"] +} + +data "aws_vpcs" "data_replication" { + filter { + name = "tag:Environment" + values = [local.data_replication_prefix] + } +} + +data "aws_subnets" "data_replication" { + filter { + name = "vpc-id" + values = data.aws_vpcs.data_replication.ids + } + filter { + name = "tag:Private" + values = [true] + } +} diff --git a/terraform/monitoring/grafana/main.tf b/terraform/monitoring/grafana/main.tf index c7d58cc979..29155d9c22 100644 --- a/terraform/monitoring/grafana/main.tf +++ b/terraform/monitoring/grafana/main.tf @@ -29,3 +29,5 @@ resource "grafana_data_source" "cloudwatch" { }) uid = "cloudwatch" } + +# resouce "grafana_data_source" "postgres" {} From e30021d298f133c32b2aee07359b5e3596971f30 Mon Sep 17 00:00:00 2001 From: Brage Gording Date: Mon, 22 Sep 2025 09:23:37 +0100 Subject: [PATCH 3/7] Add necessary permissions for data replication deployment - Permissions are generic enough to go into general deploy permission set --- terraform/account/resources/iam_policy_DeployMavisResources.json | 1 + 1 file changed, 1 insertion(+) diff --git a/terraform/account/resources/iam_policy_DeployMavisResources.json b/terraform/account/resources/iam_policy_DeployMavisResources.json index 84a99802b1..dd9bf5c0c2 100644 --- a/terraform/account/resources/iam_policy_DeployMavisResources.json +++ b/terraform/account/resources/iam_policy_DeployMavisResources.json @@ -136,6 +136,7 @@ "secretsmanager:PutSecretValue", "secretsmanager:UpdateSecret", "secretsmanager:GetSecretValue", + "secretsmanager:GetRandomPassword", "secretsmanager:RotateSecret", "secretsmanager:DeleteSecret", "secretsmanager:CancelRotateSecret", From 7bc48576a180551c19359f3043fe9ad650c37c21 Mon Sep 17 00:00:00 2001 From: Brage Gording Date: Tue, 23 Sep 2025 11:15:06 +0100 Subject: [PATCH 4/7] Ensure grafana can access vpc endpoints - Also ensure ECS service can access s3 endpoint - Due to NAT gateway an s3 gateway is no longer needed --- terraform/app/modules/vpc_endpoint/main.tf | 14 +++++++------- terraform/data_replication/network.tf | 19 +++++++++++-------- terraform/monitoring/aws/workspace.tf | 3 --- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/terraform/app/modules/vpc_endpoint/main.tf b/terraform/app/modules/vpc_endpoint/main.tf index c29afbf391..3bcc278344 100644 --- a/terraform/app/modules/vpc_endpoint/main.tf +++ b/terraform/app/modules/vpc_endpoint/main.tf @@ -30,13 +30,13 @@ resource "aws_security_group" "this" { } resource "aws_security_group_rule" "ingress" { - count = length(var.ingress_ports) - type = "ingress" - from_port = var.ingress_ports[count.index] - to_port = var.ingress_ports[count.index] - protocol = "tcp" - security_group_id = aws_security_group.this.id - source_security_group_id = var.source_security_group + count = length(var.ingress_ports) + type = "ingress" + from_port = var.ingress_ports[count.index] + to_port = var.ingress_ports[count.index] + protocol = "tcp" + security_group_id = aws_security_group.this.id + cidr_blocks = ["0.0.0.0/0"] lifecycle { create_before_destroy = true } diff --git a/terraform/data_replication/network.tf b/terraform/data_replication/network.tf index 7b6344b587..a8160db815 100644 --- a/terraform/data_replication/network.tf +++ b/terraform/data_replication/network.tf @@ -121,12 +121,15 @@ module "vpc_endpoints" { } } -resource "aws_vpc_endpoint" "s3_gateway" { - vpc_id = aws_vpc.vpc.id - service_name = "com.amazonaws.${var.region}.s3" - vpc_endpoint_type = "Gateway" - route_table_ids = [aws_route_table.private.id] - tags = { - Name = "${local.name_prefix}-s3-gw-endpoint" - } +data "aws_prefix_list" "s3" { + name = "com.amazonaws.${var.region}.s3" +} + +resource "aws_security_group_rule" "egress_to_s3" { + type = "egress" + from_port = 443 + to_port = 443 + protocol = "tcp" + prefix_list_ids = [data.aws_prefix_list.s3.id] + security_group_id = module.db_access_service.security_group_id } diff --git a/terraform/monitoring/aws/workspace.tf b/terraform/monitoring/aws/workspace.tf index d165e77cc1..3a5fbf2032 100644 --- a/terraform/monitoring/aws/workspace.tf +++ b/terraform/monitoring/aws/workspace.tf @@ -33,9 +33,6 @@ resource "aws_grafana_workspace_service_account" "grafana_provider" { workspace_id = aws_grafana_workspace.this.id } -locals { -} - resource "aws_security_group" "grafana_workspace" { name_prefix = "grafana-workspace-${var.environment}" description = "Security group for Grafana workspace" From 93931bf86bc9b59436da0fd7f50a5d74040e8e1e Mon Sep 17 00:00:00 2001 From: Brage Gording Date: Tue, 23 Sep 2025 14:21:44 +0100 Subject: [PATCH 5/7] Add postgres data source - Leave password/url as manual steps to fill for now --- terraform/monitoring/grafana/main.tf | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/terraform/monitoring/grafana/main.tf b/terraform/monitoring/grafana/main.tf index 29155d9c22..a03f7da530 100644 --- a/terraform/monitoring/grafana/main.tf +++ b/terraform/monitoring/grafana/main.tf @@ -30,4 +30,30 @@ resource "grafana_data_source" "cloudwatch" { uid = "cloudwatch" } -# resouce "grafana_data_source" "postgres" {} +resource "grafana_data_source" "postgresql" { + name = "postgresql" + type = "grafana-postgresql-datasource" + json_data_encoded = jsonencode({ + sslmode = "require" + connMaxLifetime = 14400 + database = "manage_vaccinations" + maxIdleConns = 5 + maxIdleConnsAuto = true + maxOpenConns = 5 + tlsConfigurationMethod = "file-path" + timescaledb = false + postgresVersion = 1500 + }) + secure_json_data_encoded = jsonencode({ + password = "CHANGE_ME" + }) + url = "CHANGE_ME" + uid = "postgres" + username = "grafana_ro" + lifecycle { + ignore_changes = [ + secure_json_data_encoded, + url, + ] + } +} From ef49ad3414fd36ca254278a5ce9275f4cc14844c Mon Sep 17 00:00:00 2001 From: Brage Gording Date: Wed, 24 Sep 2025 15:12:20 +0100 Subject: [PATCH 6/7] Expand out names - Expand out names for clarity in code - Keep `_ro` suffix as standard indicator for read only permissions on db user --- terraform/data_replication/iam.tf | 2 +- terraform/data_replication/ssm_parameters.tf | 18 +++++++----------- terraform/data_replication/variables.tf | 2 +- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/terraform/data_replication/iam.tf b/terraform/data_replication/iam.tf index 33251ce855..ff4914d862 100644 --- a/terraform/data_replication/iam.tf +++ b/terraform/data_replication/iam.tf @@ -12,7 +12,7 @@ data "aws_iam_policy_document" "ecs_permissions" { actions = ["secretsmanager:GetSecretValue"] resources = [ var.db_secret_arn, - aws_secretsmanager_secret.ro_db_password.arn + aws_secretsmanager_secret.read_only_db_password.arn ] effect = "Allow" } diff --git a/terraform/data_replication/ssm_parameters.tf b/terraform/data_replication/ssm_parameters.tf index a17726af43..9a0aade629 100644 --- a/terraform/data_replication/ssm_parameters.tf +++ b/terraform/data_replication/ssm_parameters.tf @@ -1,25 +1,21 @@ -# Create a password that is automatically populated in secrets manager using the random password generator for aws - -# Generate a random password for the read-only database user -ephemeral "aws_secretsmanager_random_password" "ro_db_password" { +ephemeral "aws_secretsmanager_random_password" "read_only_db_password" { } -# Store the generated password in AWS Secrets Manager -resource "aws_secretsmanager_secret" "ro_db_password" { - name = "${local.name_prefix}-ro-db-password-${substr(uuid(), 0, 4)}" +resource "aws_secretsmanager_secret" "read_only_db_password" { + name = "${local.name_prefix}-grafana-read-only-db-password-${substr(uuid(), 0, 4)}" description = "Read-only database user password for data replication" recovery_window_in_days = 7 tags = { - Name = "${local.name_prefix}-ro-db-password" + Name = "${local.name_prefix}-read-only-db-password" } lifecycle { ignore_changes = [name] } } -resource "aws_secretsmanager_secret_version" "ro_db_password" { - secret_id = aws_secretsmanager_secret.ro_db_password.id - secret_string_wo = ephemeral.aws_secretsmanager_random_password.ro_db_password.random_password +resource "aws_secretsmanager_secret_version" "read_only_db_password" { + secret_id = aws_secretsmanager_secret.read_only_db_password.id + secret_string_wo = ephemeral.aws_secretsmanager_random_password.read_only_db_password.random_password secret_string_wo_version = 1 } diff --git a/terraform/data_replication/variables.tf b/terraform/data_replication/variables.tf index 45cb3c0cf6..2e6b774340 100644 --- a/terraform/data_replication/variables.tf +++ b/terraform/data_replication/variables.tf @@ -126,7 +126,7 @@ locals { }, { name = "READ_ONLY_DB_PASSWORD" - valueFrom = aws_secretsmanager_secret.ro_db_password.arn + valueFrom = aws_secretsmanager_secret.read_only_db_password.arn } ] } From 937ecd20f453842933154234e5d19fe892a93f0c Mon Sep 17 00:00:00 2001 From: Brage Gording Date: Thu, 25 Sep 2025 15:16:14 +0100 Subject: [PATCH 7/7] Expand abbreviation suffix for clarity --- bin/data-replication | 14 +++++++------- terraform/monitoring/grafana/main.tf | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bin/data-replication b/bin/data-replication index 015b2c5a88..e2398f301c 100755 --- a/bin/data-replication +++ b/bin/data-replication @@ -18,20 +18,20 @@ begin ActiveRecord::Base.connection.execute(\" DO \$\$ BEGIN - IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'grafana_ro') THEN - CREATE ROLE grafana_ro WITH LOGIN PASSWORD '#{ENV['READ_ONLY_DB_PASSWORD']}'; + IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'grafana_read_only') THEN + CREATE ROLE grafana_read_only WITH LOGIN PASSWORD '#{ENV['READ_ONLY_DB_PASSWORD']}'; ELSE - ALTER ROLE grafana_ro WITH PASSWORD '#{ENV['READ_ONLY_DB_PASSWORD']}'; + ALTER ROLE grafana_read_only WITH PASSWORD '#{ENV['READ_ONLY_DB_PASSWORD']}'; END IF; END \$\$; \") ActiveRecord::Base.connection.execute(\" - GRANT CONNECT ON DATABASE #{ActiveRecord::Base.connection.current_database} TO grafana_ro; - GRANT USAGE ON SCHEMA public TO grafana_ro; - GRANT SELECT ON ALL TABLES IN SCHEMA public TO grafana_ro; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO grafana_ro; + GRANT CONNECT ON DATABASE #{ActiveRecord::Base.connection.current_database} TO grafana_read_only; + GRANT USAGE ON SCHEMA public TO grafana_read_only; + GRANT SELECT ON ALL TABLES IN SCHEMA public TO grafana_read_only; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO grafana_read_only; \") puts 'Read-only role created/updated successfully' diff --git a/terraform/monitoring/grafana/main.tf b/terraform/monitoring/grafana/main.tf index a03f7da530..c9935480f8 100644 --- a/terraform/monitoring/grafana/main.tf +++ b/terraform/monitoring/grafana/main.tf @@ -49,7 +49,7 @@ resource "grafana_data_source" "postgresql" { }) url = "CHANGE_ME" uid = "postgres" - username = "grafana_ro" + username = "grafana_read_only" lifecycle { ignore_changes = [ secure_json_data_encoded,