Skip to content

Commit e50a8b4

Browse files
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
1 parent 3425508 commit e50a8b4

File tree

7 files changed

+84
-5
lines changed

7 files changed

+84
-5
lines changed

bin/data-replication

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env bash
2+
3+
# Data replication script - creates read-only database role and keeps container running
4+
5+
set -e
6+
7+
echo "Starting data replication setup..."
8+
9+
if [ -z "$READ_ONLY_DB_PASSWORD" ]; then
10+
echo "Error: READ_ONLY_DB_PASSWORD environment variable is not set"
11+
exit 1
12+
fi
13+
14+
echo "Creating read-only database role..."
15+
16+
bundle exec rails runner "
17+
begin
18+
ActiveRecord::Base.connection.execute(\"
19+
DO \$\$
20+
BEGIN
21+
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'grafana_ro') THEN
22+
CREATE ROLE grafana_ro WITH LOGIN PASSWORD '#{ENV['READ_ONLY_DB_PASSWORD']}';
23+
ELSE
24+
ALTER ROLE grafana_ro WITH PASSWORD '#{ENV['READ_ONLY_DB_PASSWORD']}';
25+
END IF;
26+
END
27+
\$\$;
28+
\")
29+
30+
ActiveRecord::Base.connection.execute(\"
31+
GRANT CONNECT ON DATABASE #{ActiveRecord::Base.connection.current_database} TO grafana_ro;
32+
GRANT USAGE ON SCHEMA public TO grafana_ro;
33+
GRANT SELECT ON ALL TABLES IN SCHEMA public TO grafana_ro;
34+
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO grafana_ro;
35+
\")
36+
37+
puts 'Read-only role created/updated successfully'
38+
rescue => e
39+
puts \"Error creating read-only role: \#{e.message}\"
40+
exit 1
41+
end
42+
"
43+
44+
echo "Data replication setup completed. Keeping container running..."
45+
46+
exec tail -f /dev/null

bin/docker-start

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@ elif [ "$SERVER_TYPE" == "good-job" ]; then
1111
elif [ "$SERVER_TYPE" == "sidekiq" ]; then
1212
echo "Starting sidekiq server..."
1313
exec "$BIN_DIR"/sidekiq
14+
elif [ "$SERVER_TYPE" == "data-replication" ]; then
15+
echo "Starting data replication server..."
16+
exec "$BIN_DIR"/data-replication
1417
elif [ "$SERVER_TYPE" == "none" ]; then
1518
echo "No server started"
1619
exec tail -f /dev/null # Keep container running
1720
else
18-
echo "SERVER_TYPE variable: '$SERVER_TYPE' unknown. Allowed values ['web','good-job', 'none']"
21+
echo "SERVER_TYPE variable: '$SERVER_TYPE' unknown. Allowed values ['web','good-job', 'sidekiq', 'data-replication', 'none']"
1922
exit 1
2023
fi

terraform/data_replication/ecs.tf

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ module "db_access_service" {
2626
subnets = local.subnet_list
2727
vpc_id = aws_vpc.vpc.id
2828
}
29-
server_type = "none"
30-
server_type_name = "data-replication"
29+
server_type = "data-replication"
3130
task_config = {
3231
environment = local.task_envs
3332
secrets = local.task_secrets

terraform/data_replication/iam.tf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ data "aws_iam_policy_document" "ecs_permissions" {
1111
sid = "dbSecretSid"
1212
actions = ["secretsmanager:GetSecretValue"]
1313
resources = [
14-
var.db_secret_arn
14+
var.db_secret_arn,
15+
aws_secretsmanager_secret.ro_db_password.arn
1516
]
1617
effect = "Allow"
1718
}

terraform/data_replication/rds.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ resource "aws_rds_cluster" "cluster" {
3636
}
3737

3838
lifecycle {
39-
ignore_changes = [cluster_identifier]
39+
ignore_changes = [cluster_identifier, snapshot_identifier]
4040
}
4141
}
4242

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Create a password that is automatically populated in secrets manager using the random password generator for aws
2+
3+
# Generate a random password for the read-only database user
4+
ephemeral "aws_secretsmanager_random_password" "ro_db_password" {
5+
}
6+
7+
# Store the generated password in AWS Secrets Manager
8+
resource "aws_secretsmanager_secret" "ro_db_password" {
9+
name = "${local.name_prefix}-ro-db-password-${substr(uuid(), 0, 4)}"
10+
description = "Read-only database user password for data replication"
11+
recovery_window_in_days = 7
12+
13+
tags = {
14+
Name = "${local.name_prefix}-ro-db-password"
15+
}
16+
lifecycle {
17+
ignore_changes = [name]
18+
replace_triggered_by = [aws_rds_cluster.cluster]
19+
}
20+
}
21+
22+
resource "aws_secretsmanager_secret_version" "ro_db_password" {
23+
secret_id = aws_secretsmanager_secret.ro_db_password.id
24+
secret_string_wo = ephemeral.aws_secretsmanager_random_password.ro_db_password.random_password
25+
secret_string_wo_version = 1
26+
}

terraform/data_replication/variables.tf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ locals {
124124
{
125125
name = "RAILS_MASTER_KEY"
126126
valueFrom = var.rails_master_key_path
127+
},
128+
{
129+
name = "READ_ONLY_DB_PASSWORD"
130+
valueFrom = aws_secretsmanager_secret.ro_db_password.arn
127131
}
128132
]
129133
}

0 commit comments

Comments
 (0)