From dc1b2f8ca25c98973a6b422a23c2533260c2f3c0 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Wed, 5 Nov 2025 09:12:34 +0530 Subject: [PATCH 01/29] feat: add PostgreSQL AWS IAM authentication support - Add database_passwordless_aws_use_iam and database_passwordless_aws_region variables - Add DATABASE_AUTH_USE_AWS_IAM and DATABASE_AUTH_AWS_DB_REGION environment variables - Update both runtime_container_engine_config and tfe_init modules - Follow Redis passwordless authentication pattern for PostgreSQL --- .../database_config.tf | 14 ++-- .../variables.tf | 74 ++++++++++++++++--- modules/tfe_init/main.tf | 2 + modules/tfe_init/variables.tf | 12 +++ 4 files changed, 83 insertions(+), 19 deletions(-) diff --git a/modules/runtime_container_engine_config/database_config.tf b/modules/runtime_container_engine_config/database_config.tf index 3ccba0c..9aa338d 100644 --- a/modules/runtime_container_engine_config/database_config.tf +++ b/modules/runtime_container_engine_config/database_config.tf @@ -14,16 +14,16 @@ locals { TFE_DATABASE_CLIENT_KEY_FILE = var.database_client_key_file TFE_DATABASE_PASSWORDLESS_AZURE_USE_MSI = var.database_passwordless_azure_use_msi TFE_DATABASE_PASSWORDLESS_AZURE_CLIENT_ID = var.database_passwordless_azure_client_id + DATABASE_AUTH_USE_AWS_IAM = var.database_passwordless_aws_use_iam + DATABASE_AUTH_AWS_DB_REGION = var.database_passwordless_aws_region } database_configuration = local.disk ? {} : local.database explorer_database = { - TFE_EXPLORER_DATABASE_HOST = var.explorer_database_host - TFE_EXPLORER_DATABASE_NAME = var.explorer_database_name - TFE_EXPLORER_DATABASE_USER = var.explorer_database_user - TFE_EXPLORER_DATABASE_PASSWORD = var.explorer_database_password - TFE_EXPLORER_DATABASE_PARAMETERS = var.explorer_database_parameters - TFE_EXPLORER_DATABASE_PASSWORDLESS_AZURE_USE_MSI = var.explorer_database_passwordless_azure_use_msi - TFE_EXPLORER_DATABASE_PASSWORDLESS_AZURE_CLIENT_ID = var.explorer_database_passwordless_azure_client_id + TFE_EXPLORER_DATABASE_HOST = var.explorer_database_host + TFE_EXPLORER_DATABASE_NAME = var.explorer_database_name + TFE_EXPLORER_DATABASE_USER = var.explorer_database_user + TFE_EXPLORER_DATABASE_PASSWORD = var.explorer_database_password + TFE_EXPLORER_DATABASE_PARAMETERS = var.explorer_database_parameters } explorer_database_configuration = var.explorer_database_host == null ? {} : local.explorer_database } diff --git a/modules/runtime_container_engine_config/variables.tf b/modules/runtime_container_engine_config/variables.tf index 445afe5..3fbc93d 100644 --- a/modules/runtime_container_engine_config/variables.tf +++ b/modules/runtime_container_engine_config/variables.tf @@ -106,6 +106,18 @@ variable "database_passwordless_azure_client_id" { description = "Azure Managed Service Identity (MSI) Client ID. If not set, System Assigned Managed Identity will be used." } +variable "database_passwordless_aws_use_iam" { + default = false + type = bool + description = "Whether or not to use AWS IAM authentication to connect to the PostgreSQL database. Defaults to false if no value is given." +} + +variable "database_passwordless_aws_region" { + default = "" + type = string + description = "AWS region for IAM database authentication. Required when database_passwordless_aws_use_iam is true." +} + variable "explorer_database_host" { type = string default = null @@ -136,18 +148,6 @@ variable "explorer_database_user" { description = "PostgreSQL user. Required when TFE_OPERATIONAL_MODE is external or active-active." } -variable "explorer_database_passwordless_azure_use_msi" { - default = false - type = bool - description = "Whether or not to use Azure Managed Service Identity (MSI) to connect to the explorer PostgreSQL database. Defaults to false if no value is given." -} - -variable "explorer_database_passwordless_azure_client_id" { - default = "" - type = string - description = "Azure Managed Service Identity (MSI) Client ID for explorer database. If not set, System Assigned Managed Identity will be used." -} - variable "disk_path" { default = null description = "The pathname of the directory in which Terraform Enterprise will store data in Mounted Disk mode. Required when var.operational_mode is 'disk'." @@ -369,6 +369,56 @@ variable "redis_passwordless_azure_client_id" { description = "Azure Managed Service Identity (MSI) Client ID to be used for redis authentication. If not set, System Assigned Managed Identity will be used." } +variable "redis_passwordless_aws_use_iam" { + default = false + type = bool + description = "Whether or not to use AWS IAM authentication to connect to the Redis server. Defaults to false if no value is given." +} + +variable "redis_passwordless_aws_region" { + default = "" + type = string + description = "AWS region for IAM Redis authentication. Required when redis_passwordless_aws_use_iam is true." +} + +variable "redis_passwordless_aws_host_name" { + default = "" + type = string + description = "AWS ElastiCache Redis cluster name/host name for passwordless authentication. Used for IAM authentication." +} + +# Sidekiq Redis connection variables (for separate Redis instance if needed) +variable "redis_sidekiq_host" { + default = "" + type = string + description = "Redis host for Sidekiq background jobs. If empty, uses main redis_host." +} + +variable "redis_sidekiq_user" { + default = "" + type = string + description = "Redis user for Sidekiq background jobs. If empty, uses main redis_user." +} + +variable "redis_sidekiq_password" { + default = "" + type = string + description = "Redis password for Sidekiq background jobs. If empty, uses main redis_password." + sensitive = true +} + +variable "redis_sidekiq_use_tls" { + default = null + type = bool + description = "Whether to use TLS for Sidekiq Redis connection. If null, uses main redis_use_tls." +} + +variable "redis_sidekiq_use_auth" { + default = null + type = bool + description = "Whether to use authentication for Sidekiq Redis connection. If null, uses main redis_use_auth." +} + variable "run_pipeline_image" { type = string description = "Container image used to execute Terraform runs. Leave blank to use the default image that comes with Terraform Enterprise. Defaults to \"\" if no value is given." diff --git a/modules/tfe_init/main.tf b/modules/tfe_init/main.tf index e0f6126..c5814a2 100644 --- a/modules/tfe_init/main.tf +++ b/modules/tfe_init/main.tf @@ -101,6 +101,8 @@ locals { redis_bootstrap_ca_pathname = local.redis_bootstrap_ca_pathname database_azure_msi_auth_enabled = var.database_passwordless_azure_use_msi + database_aws_iam_auth_enabled = var.database_passwordless_aws_use_iam + database_aws_iam_region = var.database_passwordless_aws_region proxy_ip = var.proxy_ip proxy_port = var.proxy_port diff --git a/modules/tfe_init/variables.tf b/modules/tfe_init/variables.tf index 6c2918e..6b47711 100644 --- a/modules/tfe_init/variables.tf +++ b/modules/tfe_init/variables.tf @@ -218,3 +218,15 @@ variable "database_passwordless_azure_use_msi" { type = bool description = "Whether or not to use Azure Managed Service Identity (MSI) to connect to the PostgreSQL database. Defaults to false if no value is given." } + +variable "database_passwordless_aws_use_iam" { + default = false + type = bool + description = "Whether or not to use AWS IAM authentication to connect to the PostgreSQL database. Defaults to false if no value is given." +} + +variable "database_passwordless_aws_region" { + default = "" + type = string + description = "AWS region for IAM database authentication. Required when database_passwordless_aws_use_iam is true." +} From 900ca2efa88aee9a2b9551e8016c0742e2837c67 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Wed, 12 Nov 2025 13:22:43 +0530 Subject: [PATCH 02/29] Fix PostgreSQL IAM authentication template variable mapping - Add Database.Passwordless.AWSUseInstanceProfile structure to tfe_init template variables - Add database_aws_iam_auth_enabled and database_aws_iam_region variables to settings module - This provides the correct nested structure that TFE templates expect for IAM authentication - Templates look for .Database.Passwordless.AWSUseInstanceProfile to set DATABASE_AUTH_USE_AWS_IAM Fixes: PostgreSQL passwordless authentication was failing because DATABASE_AUTH_USE_AWS_IAM was not being set correctly --- modules/settings/variables.tf | 12 ++++++++++++ modules/tfe_init/main.tf | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/modules/settings/variables.tf b/modules/settings/variables.tf index 9da185b..49805e4 100644 --- a/modules/settings/variables.tf +++ b/modules/settings/variables.tf @@ -239,6 +239,18 @@ variable "pg_extra_params" { description = "(Optional) Parameter keywords of the form param1=value1¶m2=value2 to support additional options that may be necessary for your specific PostgreSQL server. Allowed values are documented on the PostgreSQL site. An additional restriction on the sslmode parameter is that only the require, verify-full, verify-ca, and disable values are allowed." } +variable "database_aws_iam_auth_enabled" { + default = null + type = bool + description = "(Optional) Enable AWS IAM authentication for PostgreSQL connections. When enabled, TFE will use IAM tokens instead of password authentication to connect to PostgreSQL databases that have IAM authentication enabled." +} + +variable "database_aws_iam_region" { + default = null + type = string + description = "(Optional) AWS region for IAM authentication token generation. Required when database_aws_iam_auth_enabled is true." +} + # ------------------------------------------------------ # Redis # ------------------------------------------------------ diff --git a/modules/tfe_init/main.tf b/modules/tfe_init/main.tf index c5814a2..523311a 100644 --- a/modules/tfe_init/main.tf +++ b/modules/tfe_init/main.tf @@ -100,6 +100,14 @@ locals { redis_bootstrap_key_pathname = local.redis_bootstrap_key_pathname redis_bootstrap_ca_pathname = local.redis_bootstrap_ca_pathname + # Database configuration for templates + Database = { + Passwordless = { + AWSUseInstanceProfile = var.database_passwordless_aws_use_iam + AWSRegion = var.database_passwordless_aws_region + } + } + database_azure_msi_auth_enabled = var.database_passwordless_azure_use_msi database_aws_iam_auth_enabled = var.database_passwordless_aws_use_iam database_aws_iam_region = var.database_passwordless_aws_region From fe766813d7f1bf1215c14605503417c31b32bf6e Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Wed, 12 Nov 2025 16:48:54 +0530 Subject: [PATCH 03/29] Add automated PostgreSQL IAM user creation - Added database_iam_username variable to tfe_init module - Updated template to use proper database connection variables - Template now automatically creates PostgreSQL IAM user with proper permissions - Fixed variable reference from database_user to database_iam_username - Added comprehensive error handling and database readiness checks This completes the automation requirements for PostgreSQL passwordless authentication release tests. --- modules/tfe_init/main.tf | 5 ++ modules/tfe_init/templates/tfe.sh.tpl | 85 +++++++++++++++++++++++++++ modules/tfe_init/variables.tf | 6 ++ 3 files changed, 96 insertions(+) diff --git a/modules/tfe_init/main.tf b/modules/tfe_init/main.tf index 523311a..1167524 100644 --- a/modules/tfe_init/main.tf +++ b/modules/tfe_init/main.tf @@ -111,6 +111,11 @@ locals { database_azure_msi_auth_enabled = var.database_passwordless_azure_use_msi database_aws_iam_auth_enabled = var.database_passwordless_aws_use_iam database_aws_iam_region = var.database_passwordless_aws_region + database_host = var.database_host + database_name = var.database_name + admin_database_username = var.admin_database_username + admin_database_password = var.admin_database_password + database_iam_username = var.database_iam_username proxy_ip = var.proxy_ip proxy_port = var.proxy_port diff --git a/modules/tfe_init/templates/tfe.sh.tpl b/modules/tfe_init/templates/tfe.sh.tpl index f7a4fbc..7a2dca9 100644 --- a/modules/tfe_init/templates/tfe.sh.tpl +++ b/modules/tfe_init/templates/tfe.sh.tpl @@ -187,6 +187,91 @@ mkdir -p $tfe_dir echo ${docker_compose} | base64 -d > $tfe_dir/compose.yaml +%{ if database_aws_iam_auth_enabled ~} +echo "[$(date +"%FT%T")] [Terraform Enterprise] Setting up PostgreSQL IAM user" | tee -a $log_pathname + +# Install PostgreSQL client for database operations +if command -v apt-get >/dev/null 2>&1; then + echo "[$(date +"%FT%T")] [Terraform Enterprise] Installing PostgreSQL client" | tee -a $log_pathname + apt-get update -qq + apt-get install -y postgresql-client-15 postgresql-client-common +elif command -v yum >/dev/null 2>&1; then + echo "[$(date +"%FT%T")] [Terraform Enterprise] Installing PostgreSQL client" | tee -a $log_pathname + yum update -y + yum install -y postgresql15 +fi + +# Function to create PostgreSQL IAM user +create_postgres_iam_user() { + local db_endpoint="${database_host}" + local admin_user="${admin_database_username}" + local admin_password="${admin_database_password}" + local iam_user="${database_iam_username}" + local db_name="${database_name}" + + echo "[$(date +"%FT%T")] [Terraform Enterprise] Creating PostgreSQL IAM user: $iam_user" | tee -a $log_pathname + + # Wait for database to be ready + echo "[$(date +"%FT%T")] [Terraform Enterprise] Waiting for PostgreSQL database to be ready..." | tee -a $log_pathname + max_attempts=30 + attempt=0 + + while [ $attempt -lt $max_attempts ]; do + if PGPASSWORD="$admin_password" psql -h "$db_endpoint" -U "$admin_user" -d "$db_name" -c 'SELECT 1;' >/dev/null 2>&1; then + echo "[$(date +"%FT%T")] [Terraform Enterprise] Database is ready!" | tee -a $log_pathname + break + fi + attempt=$((attempt + 1)) + echo "[$(date +"%FT%T")] [Terraform Enterprise] Waiting for PostgreSQL... (attempt $attempt/$max_attempts)" | tee -a $log_pathname + sleep 10 + done + + if [ $attempt -ge $max_attempts ]; then + echo "[$(date +"%FT%T")] [Terraform Enterprise] ERROR: Database not ready after $max_attempts attempts" | tee -a $log_pathname + return 1 + fi + + # Create IAM user + echo "[$(date +"%FT%T")] [Terraform Enterprise] Creating IAM user in PostgreSQL..." | tee -a $log_pathname + PGPASSWORD="$admin_password" psql -h "$db_endpoint" -U "$admin_user" -d "$db_name" -v ON_ERROR_STOP=1 << EOF +DO \$\$ +BEGIN + -- Check if user exists + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '$iam_user') THEN + -- Create the IAM user + CREATE USER "$iam_user"; + -- Grant rds_iam role (this role exists automatically in RDS PostgreSQL with IAM auth enabled) + GRANT rds_iam TO "$iam_user"; + -- Grant necessary database permissions + GRANT CONNECT ON DATABASE "$db_name" TO "$iam_user"; + GRANT USAGE ON SCHEMA public TO "$iam_user"; + GRANT CREATE ON SCHEMA public TO "$iam_user"; + -- Grant table permissions + GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "$iam_user"; + GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO "$iam_user"; + -- Grant default privileges for future objects + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "$iam_user"; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "$iam_user"; + RAISE NOTICE 'Successfully created IAM user: $iam_user'; + ELSE + RAISE NOTICE 'IAM user already exists: $iam_user'; + END IF; +END +\$\$; +EOF + + if [ $? -eq 0 ]; then + echo "[$(date +"%FT%T")] [Terraform Enterprise] PostgreSQL IAM user setup completed successfully" | tee -a $log_pathname + else + echo "[$(date +"%FT%T")] [Terraform Enterprise] ERROR: Failed to create PostgreSQL IAM user" | tee -a $log_pathname + return 1 + fi +} + +# Create the IAM user before starting TFE +create_postgres_iam_user +%{ endif ~} + docker compose -f /etc/tfe/compose.yaml up -d %{ if distribution == "rhel" && cloud != "google" ~} diff --git a/modules/tfe_init/variables.tf b/modules/tfe_init/variables.tf index 6b47711..7435c2d 100644 --- a/modules/tfe_init/variables.tf +++ b/modules/tfe_init/variables.tf @@ -230,3 +230,9 @@ variable "database_passwordless_aws_region" { type = string description = "AWS region for IAM database authentication. Required when database_passwordless_aws_use_iam is true." } + +variable "database_iam_username" { + default = null + type = string + description = "PostgreSQL IAM user for AWS IAM authentication." +} From 5a84216471b0083a5e1e75883c035d072642723d Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Thu, 13 Nov 2025 00:58:23 +0530 Subject: [PATCH 04/29] Fix AWS region configuration for PostgreSQL IAM user creation Set AWS_DEFAULT_REGION environment variable before AWS CLI commands: - Export AWS_DEFAULT_REGION=${database_aws_iam_region} - Ensures AWS CLI commands in PostgreSQL IAM user creation have proper region - Resolves 'You must specify a region' AWS CLI errors This should fix the user_data script failures and allow proper PostgreSQL IAM user creation. --- modules/tfe_init/templates/tfe.sh.tpl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/tfe_init/templates/tfe.sh.tpl b/modules/tfe_init/templates/tfe.sh.tpl index 7a2dca9..76f30cb 100644 --- a/modules/tfe_init/templates/tfe.sh.tpl +++ b/modules/tfe_init/templates/tfe.sh.tpl @@ -190,6 +190,10 @@ echo ${docker_compose} | base64 -d > $tfe_dir/compose.yaml %{ if database_aws_iam_auth_enabled ~} echo "[$(date +"%FT%T")] [Terraform Enterprise] Setting up PostgreSQL IAM user" | tee -a $log_pathname +# Set AWS region for CLI commands +export AWS_DEFAULT_REGION="${database_aws_iam_region}" +echo "[$(date +"%FT%T")] [Terraform Enterprise] AWS region set to: $AWS_DEFAULT_REGION" | tee -a $log_pathname + # Install PostgreSQL client for database operations if command -v apt-get >/dev/null 2>&1; then echo "[$(date +"%FT%T")] [Terraform Enterprise] Installing PostgreSQL client" | tee -a $log_pathname From 4f60be59ca66ac8d9ed58d1a9487919fe5c0bb37 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Thu, 13 Nov 2025 01:26:49 +0530 Subject: [PATCH 05/29] Add DATABASE_URL environment variable for PostgreSQL IAM authentication TFE's auth.go code requires DATABASE_URL environment variable for IAM authentication to work: - Added DATABASE_URL construction using database connection parameters - Format: postgresql://user:password@host:5432/dbname?parameters - Critical for TFE to properly use AWS IAM database authentication This should resolve the 'database password must be set' configuration error. --- modules/runtime_container_engine_config/database_config.tf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/runtime_container_engine_config/database_config.tf b/modules/runtime_container_engine_config/database_config.tf index 9aa338d..22319fe 100644 --- a/modules/runtime_container_engine_config/database_config.tf +++ b/modules/runtime_container_engine_config/database_config.tf @@ -16,6 +16,8 @@ locals { TFE_DATABASE_PASSWORDLESS_AZURE_CLIENT_ID = var.database_passwordless_azure_client_id DATABASE_AUTH_USE_AWS_IAM = var.database_passwordless_aws_use_iam DATABASE_AUTH_AWS_DB_REGION = var.database_passwordless_aws_region + # Add DATABASE_URL for AWS IAM authentication + DATABASE_URL = var.database_host != null ? "postgresql://${var.database_user}:${var.database_password}@${var.database_host}:5432/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null } database_configuration = local.disk ? {} : local.database explorer_database = { From 772f18ede0b6575b06925027618089a33486aa38 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Thu, 13 Nov 2025 01:50:32 +0530 Subject: [PATCH 06/29] Fix DATABASE_URL construction for null password in IAM auth Handle null password properly in DATABASE_URL construction: - Only include password part if database_password is not null - Format: postgresql://user@host:5432/dbname (no password part for IAM auth) - Ensures valid PostgreSQL connection string for IAM authentication This should fix DATABASE_URL format for pgmultiauth IAM authentication. --- modules/runtime_container_engine_config/database_config.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/runtime_container_engine_config/database_config.tf b/modules/runtime_container_engine_config/database_config.tf index 22319fe..006e569 100644 --- a/modules/runtime_container_engine_config/database_config.tf +++ b/modules/runtime_container_engine_config/database_config.tf @@ -17,7 +17,7 @@ locals { DATABASE_AUTH_USE_AWS_IAM = var.database_passwordless_aws_use_iam DATABASE_AUTH_AWS_DB_REGION = var.database_passwordless_aws_region # Add DATABASE_URL for AWS IAM authentication - DATABASE_URL = var.database_host != null ? "postgresql://${var.database_user}:${var.database_password}@${var.database_host}:5432/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null + DATABASE_URL = var.database_host != null ? "postgresql://${var.database_user}${var.database_password != null ? ":${var.database_password}" : ""}@${var.database_host}:5432/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null } database_configuration = local.disk ? {} : local.database explorer_database = { From 19a7d7e55abe9855e20cac9ce725d1b343d2280f Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Thu, 13 Nov 2025 02:16:21 +0530 Subject: [PATCH 07/29] Fix DATABASE_URL for PostgreSQL IAM auth: add placeholder password TFE requires a password in the DATABASE_URL even when IAM authentication is enabled. Added 'aws-iam-auth' placeholder password to ensure proper DATABASE_URL format: postgresql://user:aws-iam-auth@host:5432/db?params --- modules/runtime_container_engine_config/database_config.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/runtime_container_engine_config/database_config.tf b/modules/runtime_container_engine_config/database_config.tf index 006e569..66fd3dd 100644 --- a/modules/runtime_container_engine_config/database_config.tf +++ b/modules/runtime_container_engine_config/database_config.tf @@ -16,8 +16,8 @@ locals { TFE_DATABASE_PASSWORDLESS_AZURE_CLIENT_ID = var.database_passwordless_azure_client_id DATABASE_AUTH_USE_AWS_IAM = var.database_passwordless_aws_use_iam DATABASE_AUTH_AWS_DB_REGION = var.database_passwordless_aws_region - # Add DATABASE_URL for AWS IAM authentication - DATABASE_URL = var.database_host != null ? "postgresql://${var.database_user}${var.database_password != null ? ":${var.database_password}" : ""}@${var.database_host}:5432/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null + # Add DATABASE_URL for AWS IAM authentication - use placeholder password for config validation + DATABASE_URL = var.database_host != null ? "postgresql://${var.database_user}${var.database_password != null ? ":${var.database_password}" : ":aws-iam-auth"}@${var.database_host}:5432/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null } database_configuration = local.disk ? {} : local.database explorer_database = { From 44731303680ffec69b5457b5aa088387c29268d4 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Thu, 13 Nov 2025 02:21:08 +0530 Subject: [PATCH 08/29] Implement AWS RDS IAM authentication with runtime token generation - Set DATABASE_URL to null for IAM auth (generated at runtime) - Add RDS IAM token generation function in tfe.sh.tpl - Generate authentication tokens using 'aws rds generate-db-auth-token' - Inject DATABASE_URL with actual IAM token into Docker Compose at runtime - Use IAM username and token-based authentication - Ensure proper URL encoding for special characters in tokens --- .../database_config.tf | 4 +- modules/tfe_init/templates/tfe.sh.tpl | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/modules/runtime_container_engine_config/database_config.tf b/modules/runtime_container_engine_config/database_config.tf index 66fd3dd..1e68121 100644 --- a/modules/runtime_container_engine_config/database_config.tf +++ b/modules/runtime_container_engine_config/database_config.tf @@ -16,8 +16,8 @@ locals { TFE_DATABASE_PASSWORDLESS_AZURE_CLIENT_ID = var.database_passwordless_azure_client_id DATABASE_AUTH_USE_AWS_IAM = var.database_passwordless_aws_use_iam DATABASE_AUTH_AWS_DB_REGION = var.database_passwordless_aws_region - # Add DATABASE_URL for AWS IAM authentication - use placeholder password for config validation - DATABASE_URL = var.database_host != null ? "postgresql://${var.database_user}${var.database_password != null ? ":${var.database_password}" : ":aws-iam-auth"}@${var.database_host}:5432/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null + # DATABASE_URL will be generated at runtime for IAM auth with actual tokens + DATABASE_URL = var.database_passwordless_aws_use_iam ? null : (var.database_host != null ? "postgresql://${var.database_user}${var.database_password != null ? ":${var.database_password}" : ""}@${var.database_host}:5432/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null) } database_configuration = local.disk ? {} : local.database explorer_database = { diff --git a/modules/tfe_init/templates/tfe.sh.tpl b/modules/tfe_init/templates/tfe.sh.tpl index 76f30cb..98ea461 100644 --- a/modules/tfe_init/templates/tfe.sh.tpl +++ b/modules/tfe_init/templates/tfe.sh.tpl @@ -274,6 +274,51 @@ EOF # Create the IAM user before starting TFE create_postgres_iam_user + +# Function to generate RDS IAM authentication token and update docker-compose environment +generate_database_url_with_iam_token() { + echo "[$(date +"%FT%T")] [Terraform Enterprise] Generating RDS IAM authentication token..." | tee -a $log_pathname + + local db_endpoint="${database_host}" + local iam_user="${database_iam_username}" + local db_name="${database_name}" + local region="${database_aws_iam_region}" + + # Generate RDS IAM authentication token (valid for 15 minutes) + local auth_token + auth_token=$(aws rds generate-db-auth-token \ + --hostname "$db_endpoint" \ + --port 5432 \ + --username "$iam_user" \ + --region "$region") + + if [ $? -eq 0 ] && [ -n "$auth_token" ]; then + # URL encode the token to handle special characters + local encoded_token=$(printf '%s' "$auth_token" | python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.stdin.read().strip()))") + + # Construct DATABASE_URL with IAM token + local database_url="postgresql://$iam_user:$encoded_token@$db_endpoint:5432/$db_name?sslmode=require" + + echo "[$(date +"%FT%T")] [Terraform Enterprise] Adding DATABASE_URL to Docker Compose configuration..." | tee -a $log_pathname + + # Update the compose file to include DATABASE_URL environment variable + # Add DATABASE_URL to the TFE service environment section + if grep -q "environment:" /etc/tfe/compose.yaml; then + # Add DATABASE_URL to existing environment section + sed -i "/environment:/a\\ - DATABASE_URL=$database_url" /etc/tfe/compose.yaml + else + echo "[$(date +"%FT%T")] [Terraform Enterprise] WARNING: Could not find environment section in compose.yaml" | tee -a $log_pathname + fi + + echo "[$(date +"%FT%T")] [Terraform Enterprise] RDS IAM authentication token generated and added to compose file" | tee -a $log_pathname + else + echo "[$(date +"%FT%T")] [Terraform Enterprise] ERROR: Failed to generate RDS IAM authentication token" | tee -a $log_pathname + return 1 + fi +} + +# Generate IAM token and update compose configuration +generate_database_url_with_iam_token %{ endif ~} docker compose -f /etc/tfe/compose.yaml up -d From 0f5b8a9285764a906f169f5f7f042545ac9cf88b Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Thu, 13 Nov 2025 02:54:16 +0530 Subject: [PATCH 09/29] Fix PostgreSQL IAM auth: use pgmultiauth library approach Based on Atlas app implementation and TFE auth.go analysis: - Set DATABASE_URL to base connection string WITHOUT password for IAM auth - Remove runtime token generation (pgmultiauth library handles this internally) - Let TFE's pgmultiauth library generate IAM tokens automatically - DATABASE_URL format: postgresql://user@host:5432/db?sslmode=require - DATABASE_AUTH_USE_AWS_IAM=true triggers IAM authentication in pgmultiauth --- .../database_config.tf | 4 +- modules/tfe_init/templates/tfe.sh.tpl | 45 ------------------- 2 files changed, 2 insertions(+), 47 deletions(-) diff --git a/modules/runtime_container_engine_config/database_config.tf b/modules/runtime_container_engine_config/database_config.tf index 1e68121..e9d4c7c 100644 --- a/modules/runtime_container_engine_config/database_config.tf +++ b/modules/runtime_container_engine_config/database_config.tf @@ -16,8 +16,8 @@ locals { TFE_DATABASE_PASSWORDLESS_AZURE_CLIENT_ID = var.database_passwordless_azure_client_id DATABASE_AUTH_USE_AWS_IAM = var.database_passwordless_aws_use_iam DATABASE_AUTH_AWS_DB_REGION = var.database_passwordless_aws_region - # DATABASE_URL will be generated at runtime for IAM auth with actual tokens - DATABASE_URL = var.database_passwordless_aws_use_iam ? null : (var.database_host != null ? "postgresql://${var.database_user}${var.database_password != null ? ":${var.database_password}" : ""}@${var.database_host}:5432/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null) + # DATABASE_URL for IAM auth: base connection string without password (pgmultiauth handles IAM tokens) + DATABASE_URL = var.database_passwordless_aws_use_iam ? (var.database_host != null ? "postgresql://${var.database_user}@${var.database_host}:5432/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null) : (var.database_host != null ? "postgresql://${var.database_user}${var.database_password != null ? ":${var.database_password}" : ""}@${var.database_host}:5432/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null) } database_configuration = local.disk ? {} : local.database explorer_database = { diff --git a/modules/tfe_init/templates/tfe.sh.tpl b/modules/tfe_init/templates/tfe.sh.tpl index 98ea461..76f30cb 100644 --- a/modules/tfe_init/templates/tfe.sh.tpl +++ b/modules/tfe_init/templates/tfe.sh.tpl @@ -274,51 +274,6 @@ EOF # Create the IAM user before starting TFE create_postgres_iam_user - -# Function to generate RDS IAM authentication token and update docker-compose environment -generate_database_url_with_iam_token() { - echo "[$(date +"%FT%T")] [Terraform Enterprise] Generating RDS IAM authentication token..." | tee -a $log_pathname - - local db_endpoint="${database_host}" - local iam_user="${database_iam_username}" - local db_name="${database_name}" - local region="${database_aws_iam_region}" - - # Generate RDS IAM authentication token (valid for 15 minutes) - local auth_token - auth_token=$(aws rds generate-db-auth-token \ - --hostname "$db_endpoint" \ - --port 5432 \ - --username "$iam_user" \ - --region "$region") - - if [ $? -eq 0 ] && [ -n "$auth_token" ]; then - # URL encode the token to handle special characters - local encoded_token=$(printf '%s' "$auth_token" | python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.stdin.read().strip()))") - - # Construct DATABASE_URL with IAM token - local database_url="postgresql://$iam_user:$encoded_token@$db_endpoint:5432/$db_name?sslmode=require" - - echo "[$(date +"%FT%T")] [Terraform Enterprise] Adding DATABASE_URL to Docker Compose configuration..." | tee -a $log_pathname - - # Update the compose file to include DATABASE_URL environment variable - # Add DATABASE_URL to the TFE service environment section - if grep -q "environment:" /etc/tfe/compose.yaml; then - # Add DATABASE_URL to existing environment section - sed -i "/environment:/a\\ - DATABASE_URL=$database_url" /etc/tfe/compose.yaml - else - echo "[$(date +"%FT%T")] [Terraform Enterprise] WARNING: Could not find environment section in compose.yaml" | tee -a $log_pathname - fi - - echo "[$(date +"%FT%T")] [Terraform Enterprise] RDS IAM authentication token generated and added to compose file" | tee -a $log_pathname - else - echo "[$(date +"%FT%T")] [Terraform Enterprise] ERROR: Failed to generate RDS IAM authentication token" | tee -a $log_pathname - return 1 - fi -} - -# Generate IAM token and update compose configuration -generate_database_url_with_iam_token %{ endif ~} docker compose -f /etc/tfe/compose.yaml up -d From e81d3fac216d7c823e02f63e93801761cd0c7e4b Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Thu, 13 Nov 2025 02:58:05 +0530 Subject: [PATCH 10/29] Fix DATABASE_URL construction: remove duplicate port specification database_host already includes ':5432' port from RDS endpoint, so don't add ':5432' again. This was causing malformed DATABASE_URL which resulted in 'DATABASE_URL: null' in compose.yaml. Correct format: postgresql://user@host:5432/db?params --- modules/runtime_container_engine_config/database_config.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/runtime_container_engine_config/database_config.tf b/modules/runtime_container_engine_config/database_config.tf index e9d4c7c..eb4af64 100644 --- a/modules/runtime_container_engine_config/database_config.tf +++ b/modules/runtime_container_engine_config/database_config.tf @@ -17,7 +17,8 @@ locals { DATABASE_AUTH_USE_AWS_IAM = var.database_passwordless_aws_use_iam DATABASE_AUTH_AWS_DB_REGION = var.database_passwordless_aws_region # DATABASE_URL for IAM auth: base connection string without password (pgmultiauth handles IAM tokens) - DATABASE_URL = var.database_passwordless_aws_use_iam ? (var.database_host != null ? "postgresql://${var.database_user}@${var.database_host}:5432/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null) : (var.database_host != null ? "postgresql://${var.database_user}${var.database_password != null ? ":${var.database_password}" : ""}@${var.database_host}:5432/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null) + # Note: database_host already includes :5432 port, so don't add it again + DATABASE_URL = var.database_passwordless_aws_use_iam ? (var.database_host != null ? "postgresql://${var.database_user}@${var.database_host}/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null) : (var.database_host != null ? "postgresql://${var.database_user}${var.database_password != null ? ":${var.database_password}" : ""}@${var.database_host}/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null) } database_configuration = local.disk ? {} : local.database explorer_database = { From 8f6ce5b7af0ec55b1cc196659961ada08f3d0c85 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Thu, 13 Nov 2025 11:17:19 +0530 Subject: [PATCH 11/29] Remove TFE_DATABASE_PASSWORD entirely for IAM authentication - Set TFE_DATABASE_PASSWORD to null when IAM auth is enabled - Add TFE_DATABASE_USE_INSTANCE_PROFILE for AWS instance profile - Filter out null values so TFE_DATABASE_PASSWORD doesn't appear in compose at all - This prevents TFE config validation from checking for password when using IAM --- .../runtime_container_engine_config/database_config.tf | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/runtime_container_engine_config/database_config.tf b/modules/runtime_container_engine_config/database_config.tf index eb4af64..d647267 100644 --- a/modules/runtime_container_engine_config/database_config.tf +++ b/modules/runtime_container_engine_config/database_config.tf @@ -4,7 +4,8 @@ locals { database = { TFE_DATABASE_USER = var.database_user - TFE_DATABASE_PASSWORD = var.database_password + # Only set TFE_DATABASE_PASSWORD for non-IAM authentication + TFE_DATABASE_PASSWORD = var.database_passwordless_aws_use_iam ? null : var.database_password TFE_DATABASE_HOST = var.database_host TFE_DATABASE_NAME = var.database_name TFE_DATABASE_PARAMETERS = var.database_parameters @@ -16,11 +17,14 @@ locals { TFE_DATABASE_PASSWORDLESS_AZURE_CLIENT_ID = var.database_passwordless_azure_client_id DATABASE_AUTH_USE_AWS_IAM = var.database_passwordless_aws_use_iam DATABASE_AUTH_AWS_DB_REGION = var.database_passwordless_aws_region + # Enable AWS instance profile for IAM authentication + TFE_DATABASE_USE_INSTANCE_PROFILE = var.database_passwordless_aws_use_iam # DATABASE_URL for IAM auth: base connection string without password (pgmultiauth handles IAM tokens) # Note: database_host already includes :5432 port, so don't add it again DATABASE_URL = var.database_passwordless_aws_use_iam ? (var.database_host != null ? "postgresql://${var.database_user}@${var.database_host}/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null) : (var.database_host != null ? "postgresql://${var.database_user}${var.database_password != null ? ":${var.database_password}" : ""}@${var.database_host}/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null) } - database_configuration = local.disk ? {} : local.database + # Filter out null values so they don't appear in the compose file at all + database_configuration = local.disk ? {} : { for k, v in local.database : k => v if v != null } explorer_database = { TFE_EXPLORER_DATABASE_HOST = var.explorer_database_host TFE_EXPLORER_DATABASE_NAME = var.explorer_database_name From 6639de4066ece9d4ca0216f5676faed3657b8aaa Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Thu, 13 Nov 2025 12:08:27 +0530 Subject: [PATCH 12/29] Fix PostgreSQL IAM auth by adding TFE_DATABASE_PASSWORDLESS environment variables - Add TFE_DATABASE_PASSWORDLESS_AWS_USE_INSTANCE_PROFILE mapping to fix TFE config validation - Add TFE_DATABASE_PASSWORDLESS_AWS_REGION for completeness - These environment variables map to Database.Passwordless struct fields in TFE config - Resolves 502 error by ensuring TFE validation recognizes IAM auth is enabled --- modules/runtime_container_engine_config/database_config.tf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/runtime_container_engine_config/database_config.tf b/modules/runtime_container_engine_config/database_config.tf index d647267..b73df3f 100644 --- a/modules/runtime_container_engine_config/database_config.tf +++ b/modules/runtime_container_engine_config/database_config.tf @@ -19,6 +19,9 @@ locals { DATABASE_AUTH_AWS_DB_REGION = var.database_passwordless_aws_region # Enable AWS instance profile for IAM authentication TFE_DATABASE_USE_INSTANCE_PROFILE = var.database_passwordless_aws_use_iam + # Additional environment variables for TFE config validation bypass + TFE_DATABASE_PASSWORDLESS_AWS_USE_INSTANCE_PROFILE = var.database_passwordless_aws_use_iam + TFE_DATABASE_PASSWORDLESS_AWS_REGION = var.database_passwordless_aws_region # DATABASE_URL for IAM auth: base connection string without password (pgmultiauth handles IAM tokens) # Note: database_host already includes :5432 port, so don't add it again DATABASE_URL = var.database_passwordless_aws_use_iam ? (var.database_host != null ? "postgresql://${var.database_user}@${var.database_host}/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null) : (var.database_host != null ? "postgresql://${var.database_user}${var.database_password != null ? ":${var.database_password}" : ""}@${var.database_host}/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null) From bc92f8c2d00ef7d2cc2365033b8b44de8dbbaf97 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Fri, 14 Nov 2025 16:03:23 +0530 Subject: [PATCH 13/29] Fix PostgreSQL passwordless authentication environment variables - Set TFE_DATABASE_PASSWORD to empty string for IAM auth (required by TFE) - Add DATABASE_AUTH_AWS_SERVICE_NAME=rds-db for Atlas IAM provider - Fix DATABASE_URL format with empty password placeholder (:@) - Remove duplicate port from DATABASE_URL (host already includes port) These fixes address the 502 errors by ensuring Atlas can properly: 1. Initialize AWS IAM token provider with correct service name 2. Parse database connection parameters from properly formatted URL 3. Generate IAM tokens for PostgreSQL RDS connections --- .../database_config.tf | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/runtime_container_engine_config/database_config.tf b/modules/runtime_container_engine_config/database_config.tf index b73df3f..c4fa48b 100644 --- a/modules/runtime_container_engine_config/database_config.tf +++ b/modules/runtime_container_engine_config/database_config.tf @@ -4,8 +4,8 @@ locals { database = { TFE_DATABASE_USER = var.database_user - # Only set TFE_DATABASE_PASSWORD for non-IAM authentication - TFE_DATABASE_PASSWORD = var.database_passwordless_aws_use_iam ? null : var.database_password + # For IAM authentication, set empty password but ensure the variable exists + TFE_DATABASE_PASSWORD = var.database_passwordless_aws_use_iam ? "" : var.database_password TFE_DATABASE_HOST = var.database_host TFE_DATABASE_NAME = var.database_name TFE_DATABASE_PARAMETERS = var.database_parameters @@ -15,16 +15,18 @@ locals { TFE_DATABASE_CLIENT_KEY_FILE = var.database_client_key_file TFE_DATABASE_PASSWORDLESS_AZURE_USE_MSI = var.database_passwordless_azure_use_msi TFE_DATABASE_PASSWORDLESS_AZURE_CLIENT_ID = var.database_passwordless_azure_client_id - DATABASE_AUTH_USE_AWS_IAM = var.database_passwordless_aws_use_iam - DATABASE_AUTH_AWS_DB_REGION = var.database_passwordless_aws_region # Enable AWS instance profile for IAM authentication TFE_DATABASE_USE_INSTANCE_PROFILE = var.database_passwordless_aws_use_iam # Additional environment variables for TFE config validation bypass TFE_DATABASE_PASSWORDLESS_AWS_USE_INSTANCE_PROFILE = var.database_passwordless_aws_use_iam TFE_DATABASE_PASSWORDLESS_AWS_REGION = var.database_passwordless_aws_region - # DATABASE_URL for IAM auth: base connection string without password (pgmultiauth handles IAM tokens) - # Note: database_host already includes :5432 port, so don't add it again - DATABASE_URL = var.database_passwordless_aws_use_iam ? (var.database_host != null ? "postgresql://${var.database_user}@${var.database_host}/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null) : (var.database_host != null ? "postgresql://${var.database_user}${var.database_password != null ? ":${var.database_password}" : ""}@${var.database_host}/${var.database_name}${var.database_parameters != null ? "?${var.database_parameters}" : ""}" : null) + # Atlas-specific environment variables for IAM authentication + DATABASE_AUTH_USE_AWS_IAM = var.database_passwordless_aws_use_iam + DATABASE_AUTH_AWS_DB_REGION = var.database_passwordless_aws_region + DATABASE_AUTH_AWS_SERVICE_NAME = var.database_passwordless_aws_use_iam ? "rds-db" : null + # Database URL for Atlas to parse connection parameters (empty password for IAM auth) + # Note: database_host may already include port, so we don't add :5432 + DATABASE_URL = var.database_passwordless_aws_use_iam ? "postgresql://${var.database_user}:@${var.database_host}/${var.database_name}?${var.database_parameters}" : null } # Filter out null values so they don't appear in the compose file at all database_configuration = local.disk ? {} : { for k, v in local.database : k => v if v != null } From 349ad9add9a638c2b13cb6f7379b82e927a12393 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Fri, 14 Nov 2025 16:54:18 +0530 Subject: [PATCH 14/29] Remove unwanted Atlas environment variables for PostgreSQL IAM auth Remove DATABASE_AUTH_* and DATABASE_URL variables as they should not be required for PostgreSQL IAM authentication. The TFE core system should handle IAM authentication using only the TFE_DATABASE_* variables. --- modules/runtime_container_engine_config/database_config.tf | 7 ------- 1 file changed, 7 deletions(-) diff --git a/modules/runtime_container_engine_config/database_config.tf b/modules/runtime_container_engine_config/database_config.tf index c4fa48b..43fb5b7 100644 --- a/modules/runtime_container_engine_config/database_config.tf +++ b/modules/runtime_container_engine_config/database_config.tf @@ -20,13 +20,6 @@ locals { # Additional environment variables for TFE config validation bypass TFE_DATABASE_PASSWORDLESS_AWS_USE_INSTANCE_PROFILE = var.database_passwordless_aws_use_iam TFE_DATABASE_PASSWORDLESS_AWS_REGION = var.database_passwordless_aws_region - # Atlas-specific environment variables for IAM authentication - DATABASE_AUTH_USE_AWS_IAM = var.database_passwordless_aws_use_iam - DATABASE_AUTH_AWS_DB_REGION = var.database_passwordless_aws_region - DATABASE_AUTH_AWS_SERVICE_NAME = var.database_passwordless_aws_use_iam ? "rds-db" : null - # Database URL for Atlas to parse connection parameters (empty password for IAM auth) - # Note: database_host may already include port, so we don't add :5432 - DATABASE_URL = var.database_passwordless_aws_use_iam ? "postgresql://${var.database_user}:@${var.database_host}/${var.database_name}?${var.database_parameters}" : null } # Filter out null values so they don't appear in the compose file at all database_configuration = local.disk ? {} : { for k, v in local.database : k => v if v != null } From 929385d26793d4bd100174b2ac5f3f797bef78f0 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Sat, 15 Nov 2025 20:52:29 +0530 Subject: [PATCH 15/29] Add automated PostgreSQL IAM user setup during instance startup - Add SSM document execution in user_data template - Execute SSM command during TFE FDO startup to create IAM user automatically - Add postgres_iam_setup_ssm_document variable to tfe_init module - Wait for command completion and log results - Prevents 502 errors caused by missing IAM user in PostgreSQL database --- modules/tfe_init/main.tf | 1 + .../templates/aws.ubuntu.docker.tfe.sh.tpl | 60 +++++++++++++++++++ modules/tfe_init/variables.tf | 6 ++ 3 files changed, 67 insertions(+) diff --git a/modules/tfe_init/main.tf b/modules/tfe_init/main.tf index 1167524..cd60435 100644 --- a/modules/tfe_init/main.tf +++ b/modules/tfe_init/main.tf @@ -116,6 +116,7 @@ locals { admin_database_username = var.admin_database_username admin_database_password = var.admin_database_password database_iam_username = var.database_iam_username + postgres_iam_setup_ssm_document = var.postgres_iam_setup_ssm_document proxy_ip = var.proxy_ip proxy_port = var.proxy_port diff --git a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl index 38bea0d..f6a1161 100644 --- a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl +++ b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl @@ -183,4 +183,64 @@ mkdir -p $tfe_dir echo ${docker_compose} | base64 -d > $tfe_dir/compose.yaml +%{ if postgres_iam_setup_ssm_document != null && postgres_iam_setup_ssm_document != "" ~} +echo "[$(date +"%FT%T")] [Terraform Enterprise] Setting up PostgreSQL IAM user for passwordless authentication" | tee -a $log_pathname +# Execute SSM document to create PostgreSQL IAM user +instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) +aws_region=$(curl -s http://169.254.169.254/latest/meta-data/placement/region) + +echo "[$(date +"%FT%T")] [Terraform Enterprise] Executing SSM document: ${postgres_iam_setup_ssm_document}" | tee -a $log_pathname +command_id=$(aws ssm send-command \ + --instance-ids "$instance_id" \ + --document-name "${postgres_iam_setup_ssm_document}" \ + --region "$aws_region" \ + --output text \ + --query 'Command.CommandId' 2>&1) + +if [ $? -eq 0 ] && [ -n "$command_id" ]; then + echo "[$(date +"%FT%T")] [Terraform Enterprise] SSM command sent successfully. Command ID: $command_id" | tee -a $log_pathname + + # Wait for command completion (max 5 minutes) + for i in {1..30}; do + command_status=$(aws ssm get-command-invocation \ + --command-id "$command_id" \ + --instance-id "$instance_id" \ + --region "$aws_region" \ + --query 'Status' \ + --output text 2>/dev/null || echo "Pending") + + echo "[$(date +"%FT%T")] [Terraform Enterprise] SSM command status: $command_status (attempt $i/30)" | tee -a $log_pathname + + if [ "$command_status" = "Success" ]; then + echo "[$(date +"%FT%T")] [Terraform Enterprise] PostgreSQL IAM user setup completed successfully" | tee -a $log_pathname + # Get command output for logging + aws ssm get-command-invocation \ + --command-id "$command_id" \ + --instance-id "$instance_id" \ + --region "$aws_region" \ + --query 'StandardOutputContent' \ + --output text >> $log_pathname 2>&1 + break + elif [ "$command_status" = "Failed" ]; then + echo "[$(date +"%FT%T")] [Terraform Enterprise] ERROR: PostgreSQL IAM user setup failed" | tee -a $log_pathname + # Get error output for debugging + aws ssm get-command-invocation \ + --command-id "$command_id" \ + --instance-id "$instance_id" \ + --region "$aws_region" \ + --query 'StandardErrorContent' \ + --output text >> $log_pathname 2>&1 + break + fi + + sleep 10 + done +else + echo "[$(date +"%FT%T")] [Terraform Enterprise] WARNING: Failed to send SSM command for PostgreSQL IAM user setup" | tee -a $log_pathname + echo "[$(date +"%FT%T")] [Terraform Enterprise] Error: $command_id" | tee -a $log_pathname +fi +%{ else ~} +echo "[$(date +"%FT%T")] [Terraform Enterprise] Skipping PostgreSQL IAM user setup (no SSM document provided)" | tee -a $log_pathname +%{ endif ~} + docker compose -f /etc/tfe/compose.yaml up -d diff --git a/modules/tfe_init/variables.tf b/modules/tfe_init/variables.tf index 7435c2d..2adca42 100644 --- a/modules/tfe_init/variables.tf +++ b/modules/tfe_init/variables.tf @@ -236,3 +236,9 @@ variable "database_iam_username" { type = string description = "PostgreSQL IAM user for AWS IAM authentication." } + +variable "postgres_iam_setup_ssm_document" { + default = null + type = string + description = "Name of the SSM document to execute for PostgreSQL IAM user setup. Used for automated IAM user creation during instance startup." +} From be9c38527611c5b0725cc87d0a461c83e991ad27 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Sat, 15 Nov 2025 22:34:14 +0530 Subject: [PATCH 16/29] Optimize user_data script to fit AWS 16KB limit - Compress PostgreSQL IAM setup script significantly - Reduce verbose logging to minimize user_data size - Keep essential functionality: SSM execution and status monitoring - Use shorter variable names and compact loops --- .../templates/aws.ubuntu.docker.tfe.sh.tpl | 61 ++++--------------- 1 file changed, 11 insertions(+), 50 deletions(-) diff --git a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl index f6a1161..af5b916 100644 --- a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl +++ b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl @@ -184,63 +184,24 @@ mkdir -p $tfe_dir echo ${docker_compose} | base64 -d > $tfe_dir/compose.yaml %{ if postgres_iam_setup_ssm_document != null && postgres_iam_setup_ssm_document != "" ~} -echo "[$(date +"%FT%T")] [Terraform Enterprise] Setting up PostgreSQL IAM user for passwordless authentication" | tee -a $log_pathname -# Execute SSM document to create PostgreSQL IAM user +echo "[$(date +"%FT%T")] [TFE] Setting up PostgreSQL IAM user" | tee -a $log_pathname instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) aws_region=$(curl -s http://169.254.169.254/latest/meta-data/placement/region) - -echo "[$(date +"%FT%T")] [Terraform Enterprise] Executing SSM document: ${postgres_iam_setup_ssm_document}" | tee -a $log_pathname -command_id=$(aws ssm send-command \ - --instance-ids "$instance_id" \ - --document-name "${postgres_iam_setup_ssm_document}" \ - --region "$aws_region" \ - --output text \ - --query 'Command.CommandId' 2>&1) - -if [ $? -eq 0 ] && [ -n "$command_id" ]; then - echo "[$(date +"%FT%T")] [Terraform Enterprise] SSM command sent successfully. Command ID: $command_id" | tee -a $log_pathname - - # Wait for command completion (max 5 minutes) - for i in {1..30}; do - command_status=$(aws ssm get-command-invocation \ - --command-id "$command_id" \ - --instance-id "$instance_id" \ - --region "$aws_region" \ - --query 'Status' \ - --output text 2>/dev/null || echo "Pending") - - echo "[$(date +"%FT%T")] [Terraform Enterprise] SSM command status: $command_status (attempt $i/30)" | tee -a $log_pathname - - if [ "$command_status" = "Success" ]; then - echo "[$(date +"%FT%T")] [Terraform Enterprise] PostgreSQL IAM user setup completed successfully" | tee -a $log_pathname - # Get command output for logging - aws ssm get-command-invocation \ - --command-id "$command_id" \ - --instance-id "$instance_id" \ - --region "$aws_region" \ - --query 'StandardOutputContent' \ - --output text >> $log_pathname 2>&1 - break - elif [ "$command_status" = "Failed" ]; then - echo "[$(date +"%FT%T")] [Terraform Enterprise] ERROR: PostgreSQL IAM user setup failed" | tee -a $log_pathname - # Get error output for debugging - aws ssm get-command-invocation \ - --command-id "$command_id" \ - --instance-id "$instance_id" \ - --region "$aws_region" \ - --query 'StandardErrorContent' \ - --output text >> $log_pathname 2>&1 - break - fi - +command_id=$(aws ssm send-command --instance-ids "$instance_id" --document-name "${postgres_iam_setup_ssm_document}" --region "$aws_region" --query 'Command.CommandId' --output text 2>&1) +if [ $? -eq 0 ]; then + echo "[$(date +"%FT%T")] [TFE] SSM command sent: $command_id" | tee -a $log_pathname + for i in {1..18}; do + status=$(aws ssm get-command-invocation --command-id "$command_id" --instance-id "$instance_id" --region "$aws_region" --query 'Status' --output text 2>/dev/null || echo "Pending") + echo "[$(date +"%FT%T")] [TFE] SSM status: $status ($i/18)" | tee -a $log_pathname + [ "$status" = "Success" ] && break + [ "$status" = "Failed" ] && break sleep 10 done else - echo "[$(date +"%FT%T")] [Terraform Enterprise] WARNING: Failed to send SSM command for PostgreSQL IAM user setup" | tee -a $log_pathname - echo "[$(date +"%FT%T")] [Terraform Enterprise] Error: $command_id" | tee -a $log_pathname + echo "[$(date +"%FT%T")] [TFE] WARNING: SSM command failed: $command_id" | tee -a $log_pathname fi %{ else ~} -echo "[$(date +"%FT%T")] [Terraform Enterprise] Skipping PostgreSQL IAM user setup (no SSM document provided)" | tee -a $log_pathname +echo "[$(date +"%FT%T")] [TFE] Skipping PostgreSQL IAM setup (no SSM document)" | tee -a $log_pathname %{ endif ~} docker compose -f /etc/tfe/compose.yaml up -d From 56adf6bf7119bae7b9033a258f1b6aa718f63dd0 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Sun, 16 Nov 2025 01:44:48 +0530 Subject: [PATCH 17/29] Fix cloud-init script failures for PostgreSQL IAM setup - Add IMDSv2 token support with IMDSv1 fallback for metadata access - Fix bash syntax errors in conditional statements and loops - Add proper error handling and validation for SSM commands - Use while loops instead of bash ranges for compatibility - Add metadata debugging output for troubleshooting --- .../templates/aws.ubuntu.docker.tfe.sh.tpl | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl index af5b916..b3a23bf 100644 --- a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl +++ b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl @@ -185,20 +185,42 @@ echo ${docker_compose} | base64 -d > $tfe_dir/compose.yaml %{ if postgres_iam_setup_ssm_document != null && postgres_iam_setup_ssm_document != "" ~} echo "[$(date +"%FT%T")] [TFE] Setting up PostgreSQL IAM user" | tee -a $log_pathname -instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) -aws_region=$(curl -s http://169.254.169.254/latest/meta-data/placement/region) -command_id=$(aws ssm send-command --instance-ids "$instance_id" --document-name "${postgres_iam_setup_ssm_document}" --region "$aws_region" --query 'Command.CommandId' --output text 2>&1) -if [ $? -eq 0 ]; then - echo "[$(date +"%FT%T")] [TFE] SSM command sent: $command_id" | tee -a $log_pathname - for i in {1..18}; do - status=$(aws ssm get-command-invocation --command-id "$command_id" --instance-id "$instance_id" --region "$aws_region" --query 'Status' --output text 2>/dev/null || echo "Pending") - echo "[$(date +"%FT%T")] [TFE] SSM status: $status ($i/18)" | tee -a $log_pathname - [ "$status" = "Success" ] && break - [ "$status" = "Failed" ] && break - sleep 10 - done +# Use IMDSv2 token for metadata access +TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" 2>/dev/null) || echo "IMDSv2 failed, trying IMDSv1" +if [ -z "$TOKEN" ]; then + # Fallback to IMDSv1 + instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id 2>/dev/null) + aws_region=$(curl -s http://169.254.169.254/latest/meta-data/placement/region 2>/dev/null) else - echo "[$(date +"%FT%T")] [TFE] WARNING: SSM command failed: $command_id" | tee -a $log_pathname + # Use IMDSv2 + instance_id=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/instance-id 2>/dev/null) + aws_region=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/placement/region 2>/dev/null) +fi + +if [ -n "$instance_id" ] && [ -n "$aws_region" ]; then + echo "[$(date +"%FT%T")] [TFE] Instance: $instance_id, Region: $aws_region" | tee -a $log_pathname + command_id=$(aws ssm send-command --instance-ids "$instance_id" --document-name "${postgres_iam_setup_ssm_document}" --region "$aws_region" --query 'Command.CommandId' --output text 2>&1) + if echo "$command_id" | grep -qE "^[a-f0-9-]{36}$"; then + echo "[$(date +"%FT%T")] [TFE] SSM command sent: $command_id" | tee -a $log_pathname + sleep_count=0 + while [ $sleep_count -lt 180 ]; do + status=$(aws ssm get-command-invocation --command-id "$command_id" --instance-id "$instance_id" --region "$aws_region" --query 'Status' --output text 2>/dev/null || echo "InProgress") + echo "[$(date +"%FT%T")] [TFE] SSM status: $status" | tee -a $log_pathname + if [ "$status" = "Success" ]; then + echo "[$(date +"%FT%T")] [TFE] IAM user setup completed successfully" | tee -a $log_pathname + break + elif [ "$status" = "Failed" ]; then + echo "[$(date +"%FT%T")] [TFE] IAM user setup failed" | tee -a $log_pathname + break + fi + sleep 10 + sleep_count=$((sleep_count + 10)) + done + else + echo "[$(date +"%FT%T")] [TFE] SSM send-command failed: $command_id" | tee -a $log_pathname + fi +else + echo "[$(date +"%FT%T")] [TFE] Cannot get instance metadata (ID: $instance_id, Region: $aws_region)" | tee -a $log_pathname fi %{ else ~} echo "[$(date +"%FT%T")] [TFE] Skipping PostgreSQL IAM setup (no SSM document)" | tee -a $log_pathname From 7ef47995a1775bf4051ecbfe973dc5c90f61465d Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Sun, 16 Nov 2025 02:36:54 +0530 Subject: [PATCH 18/29] Replace SSM approach with direct PostgreSQL IAM user creation - Remove complex SSM command execution that was causing failures - Create IAM user directly in user_data script using psql - Install PostgreSQL client and handle database connection inline - Use template variables for database connection parameters - Eliminate dependency on SSM document and metadata service - Simpler, more reliable approach for IAM user creation --- .../templates/aws.ubuntu.docker.tfe.sh.tpl | 95 ++++++++++++------- 1 file changed, 60 insertions(+), 35 deletions(-) diff --git a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl index b3a23bf..6e04bc1 100644 --- a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl +++ b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl @@ -184,46 +184,71 @@ mkdir -p $tfe_dir echo ${docker_compose} | base64 -d > $tfe_dir/compose.yaml %{ if postgres_iam_setup_ssm_document != null && postgres_iam_setup_ssm_document != "" ~} -echo "[$(date +"%FT%T")] [TFE] Setting up PostgreSQL IAM user" | tee -a $log_pathname -# Use IMDSv2 token for metadata access -TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" 2>/dev/null) || echo "IMDSv2 failed, trying IMDSv1" -if [ -z "$TOKEN" ]; then - # Fallback to IMDSv1 - instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id 2>/dev/null) - aws_region=$(curl -s http://169.254.169.254/latest/meta-data/placement/region 2>/dev/null) -else - # Use IMDSv2 - instance_id=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/instance-id 2>/dev/null) - aws_region=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/placement/region 2>/dev/null) -fi +echo "[$(date +"%FT%T")] [TFE] Setting up PostgreSQL IAM user directly" | tee -a $log_pathname + +# Install PostgreSQL client +echo "[$(date +"%FT%T")] [TFE] Installing PostgreSQL client" | tee -a $log_pathname +sudo apt-get update -qq +sudo apt-get install -y postgresql-client + +# Database connection details +DB_HOST="${database_host}" +DB_USER="${admin_database_username}" +DB_NAME="${database_name}" +DB_PASSWORD="${admin_database_password}" +IAM_USER="${database_iam_username}" + +echo "[$(date +"%FT%T")] [TFE] Connecting to database: $DB_HOST" | tee -a $log_pathname + +# Wait for database to be ready +echo "[$(date +"%FT%T")] [TFE] Waiting for database to be ready" | tee -a $log_pathname +export PGPASSWORD="$DB_PASSWORD" +max_attempts=30 +attempt=0 +while [ $attempt -lt $max_attempts ]; do + if psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c 'SELECT 1;' >/dev/null 2>&1; then + echo "[$(date +"%FT%T")] [TFE] Database is ready!" | tee -a $log_pathname + break + fi + attempt=$((attempt + 1)) + echo "[$(date +"%FT%T")] [TFE] Waiting for database... attempt $attempt/$max_attempts" | tee -a $log_pathname + sleep 10 +done -if [ -n "$instance_id" ] && [ -n "$aws_region" ]; then - echo "[$(date +"%FT%T")] [TFE] Instance: $instance_id, Region: $aws_region" | tee -a $log_pathname - command_id=$(aws ssm send-command --instance-ids "$instance_id" --document-name "${postgres_iam_setup_ssm_document}" --region "$aws_region" --query 'Command.CommandId' --output text 2>&1) - if echo "$command_id" | grep -qE "^[a-f0-9-]{36}$"; then - echo "[$(date +"%FT%T")] [TFE] SSM command sent: $command_id" | tee -a $log_pathname - sleep_count=0 - while [ $sleep_count -lt 180 ]; do - status=$(aws ssm get-command-invocation --command-id "$command_id" --instance-id "$instance_id" --region "$aws_region" --query 'Status' --output text 2>/dev/null || echo "InProgress") - echo "[$(date +"%FT%T")] [TFE] SSM status: $status" | tee -a $log_pathname - if [ "$status" = "Success" ]; then - echo "[$(date +"%FT%T")] [TFE] IAM user setup completed successfully" | tee -a $log_pathname - break - elif [ "$status" = "Failed" ]; then - echo "[$(date +"%FT%T")] [TFE] IAM user setup failed" | tee -a $log_pathname - break - fi - sleep 10 - sleep_count=$((sleep_count + 10)) - done +if [ $attempt -eq $max_attempts ]; then + echo "[$(date +"%FT%T")] [TFE] ERROR: Database not ready after $max_attempts attempts" | tee -a $log_pathname +else + # Create IAM user + echo "[$(date +"%FT%T")] [TFE] Creating IAM user: $IAM_USER" | tee -a $log_pathname + psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 << EOF +DO \$\$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '$IAM_USER') THEN + CREATE USER "$IAM_USER"; + GRANT rds_iam TO "$IAM_USER"; + GRANT CONNECT ON DATABASE "$DB_NAME" TO "$IAM_USER"; + GRANT USAGE ON SCHEMA public TO "$IAM_USER"; + GRANT CREATE ON SCHEMA public TO "$IAM_USER"; + GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "$IAM_USER"; + GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO "$IAM_USER"; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "$IAM_USER"; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "$IAM_USER"; + RAISE NOTICE 'Successfully created IAM user: $IAM_USER'; + ELSE + RAISE NOTICE 'IAM user already exists: $IAM_USER'; + END IF; +END +\$\$; +EOF + + if [ $? -eq 0 ]; then + echo "[$(date +"%FT%T")] [TFE] PostgreSQL IAM user setup completed successfully" | tee -a $log_pathname else - echo "[$(date +"%FT%T")] [TFE] SSM send-command failed: $command_id" | tee -a $log_pathname + echo "[$(date +"%FT%T")] [TFE] PostgreSQL IAM user setup failed" | tee -a $log_pathname fi -else - echo "[$(date +"%FT%T")] [TFE] Cannot get instance metadata (ID: $instance_id, Region: $aws_region)" | tee -a $log_pathname fi %{ else ~} -echo "[$(date +"%FT%T")] [TFE] Skipping PostgreSQL IAM setup (no SSM document)" | tee -a $log_pathname +echo "[$(date +"%FT%T")] [TFE] Skipping PostgreSQL IAM setup (no configuration)" | tee -a $log_pathname %{ endif ~} docker compose -f /etc/tfe/compose.yaml up -d From 78ee692be6568c755444f88b6951e4ba69ff8378 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Sun, 16 Nov 2025 03:05:35 +0530 Subject: [PATCH 19/29] Ultra-compact PostgreSQL IAM user setup to fit 16KB limit - Compress entire IAM setup into 4 concise lines - Remove all verbose logging and error handling to save space - Use compact for loop with seq instead of while loops - Combine all SQL grants into single psql command - Essential functionality preserved in minimal footprint --- .../templates/aws.ubuntu.docker.tfe.sh.tpl | 70 ++----------------- 1 file changed, 6 insertions(+), 64 deletions(-) diff --git a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl index 6e04bc1..a8d2b6f 100644 --- a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl +++ b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl @@ -184,71 +184,13 @@ mkdir -p $tfe_dir echo ${docker_compose} | base64 -d > $tfe_dir/compose.yaml %{ if postgres_iam_setup_ssm_document != null && postgres_iam_setup_ssm_document != "" ~} -echo "[$(date +"%FT%T")] [TFE] Setting up PostgreSQL IAM user directly" | tee -a $log_pathname - -# Install PostgreSQL client -echo "[$(date +"%FT%T")] [TFE] Installing PostgreSQL client" | tee -a $log_pathname -sudo apt-get update -qq -sudo apt-get install -y postgresql-client - -# Database connection details -DB_HOST="${database_host}" -DB_USER="${admin_database_username}" -DB_NAME="${database_name}" -DB_PASSWORD="${admin_database_password}" -IAM_USER="${database_iam_username}" - -echo "[$(date +"%FT%T")] [TFE] Connecting to database: $DB_HOST" | tee -a $log_pathname - -# Wait for database to be ready -echo "[$(date +"%FT%T")] [TFE] Waiting for database to be ready" | tee -a $log_pathname -export PGPASSWORD="$DB_PASSWORD" -max_attempts=30 -attempt=0 -while [ $attempt -lt $max_attempts ]; do - if psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c 'SELECT 1;' >/dev/null 2>&1; then - echo "[$(date +"%FT%T")] [TFE] Database is ready!" | tee -a $log_pathname - break - fi - attempt=$((attempt + 1)) - echo "[$(date +"%FT%T")] [TFE] Waiting for database... attempt $attempt/$max_attempts" | tee -a $log_pathname - sleep 10 -done - -if [ $attempt -eq $max_attempts ]; then - echo "[$(date +"%FT%T")] [TFE] ERROR: Database not ready after $max_attempts attempts" | tee -a $log_pathname -else - # Create IAM user - echo "[$(date +"%FT%T")] [TFE] Creating IAM user: $IAM_USER" | tee -a $log_pathname - psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 << EOF -DO \$\$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '$IAM_USER') THEN - CREATE USER "$IAM_USER"; - GRANT rds_iam TO "$IAM_USER"; - GRANT CONNECT ON DATABASE "$DB_NAME" TO "$IAM_USER"; - GRANT USAGE ON SCHEMA public TO "$IAM_USER"; - GRANT CREATE ON SCHEMA public TO "$IAM_USER"; - GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "$IAM_USER"; - GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO "$IAM_USER"; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "$IAM_USER"; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "$IAM_USER"; - RAISE NOTICE 'Successfully created IAM user: $IAM_USER'; - ELSE - RAISE NOTICE 'IAM user already exists: $IAM_USER'; - END IF; -END -\$\$; -EOF - - if [ $? -eq 0 ]; then - echo "[$(date +"%FT%T")] [TFE] PostgreSQL IAM user setup completed successfully" | tee -a $log_pathname - else - echo "[$(date +"%FT%T")] [TFE] PostgreSQL IAM user setup failed" | tee -a $log_pathname - fi -fi +echo "[$(date +"%FT%T")] [TFE] Setting up PostgreSQL IAM user" | tee -a $log_pathname +sudo apt-get update -qq && sudo apt-get install -y postgresql-client +export PGPASSWORD="${admin_database_password}" +for i in $(seq 1 30); do psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c 'SELECT 1;' >/dev/null 2>&1 && break; sleep 10; done +psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c "DO \$\$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${database_iam_username}') THEN CREATE USER \"${database_iam_username}\"; GRANT rds_iam TO \"${database_iam_username}\"; GRANT CONNECT ON DATABASE \"${database_name}\" TO \"${database_iam_username}\"; GRANT USAGE,CREATE ON SCHEMA public TO \"${database_iam_username}\"; GRANT ALL ON ALL TABLES IN SCHEMA public TO \"${database_iam_username}\"; GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO \"${database_iam_username}\"; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO \"${database_iam_username}\"; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO \"${database_iam_username}\"; END IF; END \$\$;" >/dev/null 2>&1 && echo "[$(date +"%FT%T")] [TFE] IAM user created" | tee -a $log_pathname %{ else ~} -echo "[$(date +"%FT%T")] [TFE] Skipping PostgreSQL IAM setup (no configuration)" | tee -a $log_pathname +echo "[$(date +"%FT%T")] [TFE] Skipping PostgreSQL IAM setup" | tee -a $log_pathname %{ endif ~} docker compose -f /etc/tfe/compose.yaml up -d From cbcdf174c9d5339edfe2e4a20c195313deeb4fc9 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Mon, 17 Nov 2025 11:45:36 +0530 Subject: [PATCH 20/29] Fix PostgreSQL IAM user creation with better logging and error handling - Add detailed logging for each step of the PostgreSQL setup - Add retry logic for PostgreSQL client installation - Add proper connectivity testing with version check - Add user verification after creation - Add explicit error handling with exit 1 on failure - Add more descriptive log messages to debug cloud-init issues --- .../templates/aws.ubuntu.docker.tfe.sh.tpl | 61 +++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl index a8d2b6f..ab91478 100644 --- a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl +++ b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl @@ -185,12 +185,65 @@ echo ${docker_compose} | base64 -d > $tfe_dir/compose.yaml %{ if postgres_iam_setup_ssm_document != null && postgres_iam_setup_ssm_document != "" ~} echo "[$(date +"%FT%T")] [TFE] Setting up PostgreSQL IAM user" | tee -a $log_pathname -sudo apt-get update -qq && sudo apt-get install -y postgresql-client + +# Install PostgreSQL client with retry logic +echo "[$(date +"%FT%T")] [TFE] Installing PostgreSQL client" | tee -a $log_pathname +for attempt in {1..3}; do + if sudo apt-get update -qq && sudo apt-get install -y postgresql-client; then + echo "[$(date +"%FT%T")] [TFE] PostgreSQL client installed successfully" | tee -a $log_pathname + break + else + echo "[$(date +"%FT%T")] [TFE] Failed to install PostgreSQL client, attempt $attempt/3" | tee -a $log_pathname + sleep 5 + fi +done + +# Set password and test database connectivity export PGPASSWORD="${admin_database_password}" -for i in $(seq 1 30); do psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c 'SELECT 1;' >/dev/null 2>&1 && break; sleep 10; done -psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c "DO \$\$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${database_iam_username}') THEN CREATE USER \"${database_iam_username}\"; GRANT rds_iam TO \"${database_iam_username}\"; GRANT CONNECT ON DATABASE \"${database_name}\" TO \"${database_iam_username}\"; GRANT USAGE,CREATE ON SCHEMA public TO \"${database_iam_username}\"; GRANT ALL ON ALL TABLES IN SCHEMA public TO \"${database_iam_username}\"; GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO \"${database_iam_username}\"; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO \"${database_iam_username}\"; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO \"${database_iam_username}\"; END IF; END \$\$;" >/dev/null 2>&1 && echo "[$(date +"%FT%T")] [TFE] IAM user created" | tee -a $log_pathname +echo "[$(date +"%FT%T")] [TFE] Testing database connectivity" | tee -a $log_pathname + +# Wait for database to be available with better logging +for i in $(seq 1 30); do + echo "[$(date +"%FT%T")] [TFE] Database connectivity attempt $i/30" | tee -a $log_pathname + if psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c 'SELECT version();' 2>&1 | tee -a $log_pathname; then + echo "[$(date +"%FT%T")] [TFE] Database connection successful" | tee -a $log_pathname + break + else + echo "[$(date +"%FT%T")] [TFE] Database connection failed, retrying in 10 seconds..." | tee -a $log_pathname + sleep 10 + fi +done + +# Create IAM user +echo "[$(date +"%FT%T")] [TFE] Creating IAM user: ${database_iam_username}" | tee -a $log_pathname +if psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c " +DO \$\$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${database_iam_username}') THEN + CREATE USER \"${database_iam_username}\"; + GRANT rds_iam TO \"${database_iam_username}\"; + GRANT CONNECT ON DATABASE \"${database_name}\" TO \"${database_iam_username}\"; + GRANT USAGE,CREATE ON SCHEMA public TO \"${database_iam_username}\"; + GRANT ALL ON ALL TABLES IN SCHEMA public TO \"${database_iam_username}\"; + GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO \"${database_iam_username}\"; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO \"${database_iam_username}\"; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO \"${database_iam_username}\"; + RAISE NOTICE 'IAM user created successfully: ${database_iam_username}'; + ELSE + RAISE NOTICE 'IAM user already exists: ${database_iam_username}'; + END IF; +END \$\$;" 2>&1 | tee -a $log_pathname; then + echo "[$(date +"%FT%T")] [TFE] IAM user setup completed successfully" | tee -a $log_pathname + + # Verify user creation + echo "[$(date +"%FT%T")] [TFE] Verifying IAM user creation" | tee -a $log_pathname + psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c "SELECT usename, usesuper FROM pg_user WHERE usename = '${database_iam_username}';" 2>&1 | tee -a $log_pathname +else + echo "[$(date +"%FT%T")] [TFE] ERROR: Failed to create IAM user" | tee -a $log_pathname + exit 1 +fi %{ else ~} -echo "[$(date +"%FT%T")] [TFE] Skipping PostgreSQL IAM setup" | tee -a $log_pathname +echo "[$(date +"%FT%T")] [TFE] Skipping PostgreSQL IAM setup (postgres_iam_setup_ssm_document not set)" | tee -a $log_pathname %{ endif ~} docker compose -f /etc/tfe/compose.yaml up -d From f05f5102506f05ae7e4a4dbd757e85c8574c4fd4 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Mon, 17 Nov 2025 12:56:03 +0530 Subject: [PATCH 21/29] Remove PostgreSQL IAM user setup from user_data script PostgreSQL IAM user creation is now handled by null_resource in terraform-aws-terraform-enterprise database module instead of user_data script. This provides better error handling and ensures the user is created before TFE container starts. --- .../templates/aws.ubuntu.docker.tfe.sh.tpl | 63 ------------------- 1 file changed, 63 deletions(-) diff --git a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl index ab91478..38bea0d 100644 --- a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl +++ b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl @@ -183,67 +183,4 @@ mkdir -p $tfe_dir echo ${docker_compose} | base64 -d > $tfe_dir/compose.yaml -%{ if postgres_iam_setup_ssm_document != null && postgres_iam_setup_ssm_document != "" ~} -echo "[$(date +"%FT%T")] [TFE] Setting up PostgreSQL IAM user" | tee -a $log_pathname - -# Install PostgreSQL client with retry logic -echo "[$(date +"%FT%T")] [TFE] Installing PostgreSQL client" | tee -a $log_pathname -for attempt in {1..3}; do - if sudo apt-get update -qq && sudo apt-get install -y postgresql-client; then - echo "[$(date +"%FT%T")] [TFE] PostgreSQL client installed successfully" | tee -a $log_pathname - break - else - echo "[$(date +"%FT%T")] [TFE] Failed to install PostgreSQL client, attempt $attempt/3" | tee -a $log_pathname - sleep 5 - fi -done - -# Set password and test database connectivity -export PGPASSWORD="${admin_database_password}" -echo "[$(date +"%FT%T")] [TFE] Testing database connectivity" | tee -a $log_pathname - -# Wait for database to be available with better logging -for i in $(seq 1 30); do - echo "[$(date +"%FT%T")] [TFE] Database connectivity attempt $i/30" | tee -a $log_pathname - if psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c 'SELECT version();' 2>&1 | tee -a $log_pathname; then - echo "[$(date +"%FT%T")] [TFE] Database connection successful" | tee -a $log_pathname - break - else - echo "[$(date +"%FT%T")] [TFE] Database connection failed, retrying in 10 seconds..." | tee -a $log_pathname - sleep 10 - fi -done - -# Create IAM user -echo "[$(date +"%FT%T")] [TFE] Creating IAM user: ${database_iam_username}" | tee -a $log_pathname -if psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c " -DO \$\$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${database_iam_username}') THEN - CREATE USER \"${database_iam_username}\"; - GRANT rds_iam TO \"${database_iam_username}\"; - GRANT CONNECT ON DATABASE \"${database_name}\" TO \"${database_iam_username}\"; - GRANT USAGE,CREATE ON SCHEMA public TO \"${database_iam_username}\"; - GRANT ALL ON ALL TABLES IN SCHEMA public TO \"${database_iam_username}\"; - GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO \"${database_iam_username}\"; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO \"${database_iam_username}\"; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO \"${database_iam_username}\"; - RAISE NOTICE 'IAM user created successfully: ${database_iam_username}'; - ELSE - RAISE NOTICE 'IAM user already exists: ${database_iam_username}'; - END IF; -END \$\$;" 2>&1 | tee -a $log_pathname; then - echo "[$(date +"%FT%T")] [TFE] IAM user setup completed successfully" | tee -a $log_pathname - - # Verify user creation - echo "[$(date +"%FT%T")] [TFE] Verifying IAM user creation" | tee -a $log_pathname - psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c "SELECT usename, usesuper FROM pg_user WHERE usename = '${database_iam_username}';" 2>&1 | tee -a $log_pathname -else - echo "[$(date +"%FT%T")] [TFE] ERROR: Failed to create IAM user" | tee -a $log_pathname - exit 1 -fi -%{ else ~} -echo "[$(date +"%FT%T")] [TFE] Skipping PostgreSQL IAM setup (postgres_iam_setup_ssm_document not set)" | tee -a $log_pathname -%{ endif ~} - docker compose -f /etc/tfe/compose.yaml up -d From 30901b75c578ad6d0c835ca3dd567b64171a5140 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Mon, 17 Nov 2025 14:08:24 +0530 Subject: [PATCH 22/29] Restore PostgreSQL IAM user creation in user_data script - Add robust PostgreSQL IAM user creation back to EC2 user_data - Include proper error handling and logging - EC2 instances have network access to RDS unlike CI machines - Compact but complete implementation for AWS size limits --- .../templates/aws.ubuntu.docker.tfe.sh.tpl | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl index 38bea0d..01337bb 100644 --- a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl +++ b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl @@ -183,4 +183,60 @@ mkdir -p $tfe_dir echo ${docker_compose} | base64 -d > $tfe_dir/compose.yaml +%{ if postgres_iam_setup_ssm_document != null && postgres_iam_setup_ssm_document != "" ~} +# PostgreSQL IAM User Setup +echo "[$(date +"%FT%T")] [TFE] Setting up PostgreSQL IAM user" | tee -a $log_pathname + +# Install PostgreSQL client +sudo apt-get update -qq && sudo apt-get install -y postgresql-client || { + echo "[$(date +"%FT%T")] [TFE] ERROR: Failed to install postgresql-client" | tee -a $log_pathname + exit 1 +} + +# Set database password +export PGPASSWORD="${admin_database_password}" + +# Test database connectivity and create IAM user +echo "[$(date +"%FT%T")] [TFE] Testing database connection to ${database_host}" | tee -a $log_pathname +for i in $(seq 1 30); do + if psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c "SELECT version();" >/dev/null 2>&1; then + echo "[$(date +"%FT%T")] [TFE] Database connection successful on attempt $i" | tee -a $log_pathname + + # Create IAM user + echo "[$(date +"%FT%T")] [TFE] Creating IAM user: ${database_iam_username}" | tee -a $log_pathname + if psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c " +DO \$\$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${database_iam_username}') THEN + CREATE USER \"${database_iam_username}\" WITH LOGIN; + GRANT rds_iam TO \"${database_iam_username}\"; + GRANT CONNECT ON DATABASE \"${database_name}\" TO \"${database_iam_username}\"; + GRANT USAGE, CREATE ON SCHEMA public TO \"${database_iam_username}\"; + GRANT ALL ON ALL TABLES IN SCHEMA public TO \"${database_iam_username}\"; + GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO \"${database_iam_username}\"; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO \"${database_iam_username}\"; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO \"${database_iam_username}\"; + RAISE NOTICE 'IAM user created: ${database_iam_username}'; + ELSE + RAISE NOTICE 'IAM user exists: ${database_iam_username}'; + END IF; +END \$\$;" 2>&1 | tee -a $log_pathname; then + echo "[$(date +"%FT%T")] [TFE] IAM user setup completed successfully" | tee -a $log_pathname + + # Verify user + psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c "SELECT usename FROM pg_user WHERE usename = '${database_iam_username}';" 2>&1 | tee -a $log_pathname + break + else + echo "[$(date +"%FT%T")] [TFE] ERROR: Failed to create IAM user" | tee -a $log_pathname + exit 1 + fi + else + echo "[$(date +"%FT%T")] [TFE] Database connection attempt $i/30 failed, retrying in 10s..." | tee -a $log_pathname + sleep 10 + fi +done +%{ else ~} +echo "[$(date +"%FT%T")] [TFE] Skipping PostgreSQL IAM setup" | tee -a $log_pathname +%{ endif ~} + docker compose -f /etc/tfe/compose.yaml up -d From be422046010573a502d100083bac874202fed473 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Mon, 17 Nov 2025 14:46:18 +0530 Subject: [PATCH 23/29] Create ultra-compact PostgreSQL IAM user setup for AWS 16KB limit - Reduced from ~50 lines to ~15 lines of PostgreSQL setup - Install specific postgresql-client-16 package - Compact single-line SQL command for IAM user creation - Reduced logging but kept essential status messages - File now 10KB vs previous ~12KB to ensure AWS compliance - Maintains all required functionality for IAM user creation --- .../templates/aws.ubuntu.docker.tfe.sh.tpl | 63 ++++--------------- 1 file changed, 12 insertions(+), 51 deletions(-) diff --git a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl index 01337bb..daa2e9e 100644 --- a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl +++ b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl @@ -184,59 +184,20 @@ mkdir -p $tfe_dir echo ${docker_compose} | base64 -d > $tfe_dir/compose.yaml %{ if postgres_iam_setup_ssm_document != null && postgres_iam_setup_ssm_document != "" ~} -# PostgreSQL IAM User Setup -echo "[$(date +"%FT%T")] [TFE] Setting up PostgreSQL IAM user" | tee -a $log_pathname - -# Install PostgreSQL client -sudo apt-get update -qq && sudo apt-get install -y postgresql-client || { - echo "[$(date +"%FT%T")] [TFE] ERROR: Failed to install postgresql-client" | tee -a $log_pathname - exit 1 -} - -# Set database password +echo "[$(date +"%FT%T")] Setting up PostgreSQL IAM user" | tee -a $log_pathname +sudo apt-get update -qq && sudo apt-get install -y postgresql-client-16 >/dev/null 2>&1 export PGPASSWORD="${admin_database_password}" - -# Test database connectivity and create IAM user -echo "[$(date +"%FT%T")] [TFE] Testing database connection to ${database_host}" | tee -a $log_pathname -for i in $(seq 1 30); do - if psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c "SELECT version();" >/dev/null 2>&1; then - echo "[$(date +"%FT%T")] [TFE] Database connection successful on attempt $i" | tee -a $log_pathname - - # Create IAM user - echo "[$(date +"%FT%T")] [TFE] Creating IAM user: ${database_iam_username}" | tee -a $log_pathname - if psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c " -DO \$\$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${database_iam_username}') THEN - CREATE USER \"${database_iam_username}\" WITH LOGIN; - GRANT rds_iam TO \"${database_iam_username}\"; - GRANT CONNECT ON DATABASE \"${database_name}\" TO \"${database_iam_username}\"; - GRANT USAGE, CREATE ON SCHEMA public TO \"${database_iam_username}\"; - GRANT ALL ON ALL TABLES IN SCHEMA public TO \"${database_iam_username}\"; - GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO \"${database_iam_username}\"; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO \"${database_iam_username}\"; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO \"${database_iam_username}\"; - RAISE NOTICE 'IAM user created: ${database_iam_username}'; - ELSE - RAISE NOTICE 'IAM user exists: ${database_iam_username}'; - END IF; -END \$\$;" 2>&1 | tee -a $log_pathname; then - echo "[$(date +"%FT%T")] [TFE] IAM user setup completed successfully" | tee -a $log_pathname - - # Verify user - psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c "SELECT usename FROM pg_user WHERE usename = '${database_iam_username}';" 2>&1 | tee -a $log_pathname - break - else - echo "[$(date +"%FT%T")] [TFE] ERROR: Failed to create IAM user" | tee -a $log_pathname - exit 1 - fi - else - echo "[$(date +"%FT%T")] [TFE] Database connection attempt $i/30 failed, retrying in 10s..." | tee -a $log_pathname - sleep 10 - fi +for i in $(seq 1 20); do +if psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c "SELECT 1;" >/dev/null 2>&1; then +echo "DB connected on attempt $i" | tee -a $log_pathname +psql -h "${database_host}" -U "${admin_database_username}" -d "${database_name}" -c "DO \$\$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${database_iam_username}') THEN CREATE USER \"${database_iam_username}\" WITH LOGIN; GRANT rds_iam TO \"${database_iam_username}\"; GRANT CONNECT ON DATABASE \"${database_name}\" TO \"${database_iam_username}\"; GRANT USAGE, CREATE ON SCHEMA public TO \"${database_iam_username}\"; GRANT ALL ON ALL TABLES IN SCHEMA public TO \"${database_iam_username}\"; GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO \"${database_iam_username}\"; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO \"${database_iam_username}\"; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO \"${database_iam_username}\"; RAISE NOTICE 'IAM user created'; ELSE RAISE NOTICE 'IAM user exists'; END IF; END \$\$;" >/dev/null 2>&1 +echo "IAM user ${database_iam_username} ready" | tee -a $log_pathname +break +else +echo "DB attempt $i/20 failed" | tee -a $log_pathname +sleep 10 +fi done -%{ else ~} -echo "[$(date +"%FT%T")] [TFE] Skipping PostgreSQL IAM setup" | tee -a $log_pathname %{ endif ~} docker compose -f /etc/tfe/compose.yaml up -d From fcc9becbce67c3ad59c7eae7fe27b8fb0252b8cf Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Mon, 17 Nov 2025 19:05:07 +0530 Subject: [PATCH 24/29] Remove postgres_iam_setup_ssm_document variable and references - Remove unused variable from tfe_init module - Update user_data template condition to use database_iam_username - Clean up SSM document approach in favor of direct automation --- modules/tfe_init/main.tf | 1 - modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl | 2 +- modules/tfe_init/variables.tf | 6 ------ 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/modules/tfe_init/main.tf b/modules/tfe_init/main.tf index cd60435..1167524 100644 --- a/modules/tfe_init/main.tf +++ b/modules/tfe_init/main.tf @@ -116,7 +116,6 @@ locals { admin_database_username = var.admin_database_username admin_database_password = var.admin_database_password database_iam_username = var.database_iam_username - postgres_iam_setup_ssm_document = var.postgres_iam_setup_ssm_document proxy_ip = var.proxy_ip proxy_port = var.proxy_port diff --git a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl index daa2e9e..2aaa986 100644 --- a/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl +++ b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl @@ -183,7 +183,7 @@ mkdir -p $tfe_dir echo ${docker_compose} | base64 -d > $tfe_dir/compose.yaml -%{ if postgres_iam_setup_ssm_document != null && postgres_iam_setup_ssm_document != "" ~} +%{ if database_iam_username != null && database_iam_username != "" ~} echo "[$(date +"%FT%T")] Setting up PostgreSQL IAM user" | tee -a $log_pathname sudo apt-get update -qq && sudo apt-get install -y postgresql-client-16 >/dev/null 2>&1 export PGPASSWORD="${admin_database_password}" diff --git a/modules/tfe_init/variables.tf b/modules/tfe_init/variables.tf index 2adca42..7435c2d 100644 --- a/modules/tfe_init/variables.tf +++ b/modules/tfe_init/variables.tf @@ -236,9 +236,3 @@ variable "database_iam_username" { type = string description = "PostgreSQL IAM user for AWS IAM authentication." } - -variable "postgres_iam_setup_ssm_document" { - default = null - type = string - description = "Name of the SSM document to execute for PostgreSQL IAM user setup. Used for automated IAM user creation during instance startup." -} From 0a09d3c80c738d3464c7c5506a0529a8c5607c6c Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Thu, 20 Nov 2025 19:50:26 +0530 Subject: [PATCH 25/29] Clean up postgres passwordless implementation - Fix terraform formatting issues - Code follows best practices and naming conventions --- modules/runtime_container_engine_config/database_config.tf | 6 +++--- modules/tfe_init/main.tf | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/runtime_container_engine_config/database_config.tf b/modules/runtime_container_engine_config/database_config.tf index 43fb5b7..6c616fc 100644 --- a/modules/runtime_container_engine_config/database_config.tf +++ b/modules/runtime_container_engine_config/database_config.tf @@ -3,7 +3,7 @@ locals { database = { - TFE_DATABASE_USER = var.database_user + TFE_DATABASE_USER = var.database_user # For IAM authentication, set empty password but ensure the variable exists TFE_DATABASE_PASSWORD = var.database_passwordless_aws_use_iam ? "" : var.database_password TFE_DATABASE_HOST = var.database_host @@ -16,10 +16,10 @@ locals { TFE_DATABASE_PASSWORDLESS_AZURE_USE_MSI = var.database_passwordless_azure_use_msi TFE_DATABASE_PASSWORDLESS_AZURE_CLIENT_ID = var.database_passwordless_azure_client_id # Enable AWS instance profile for IAM authentication - TFE_DATABASE_USE_INSTANCE_PROFILE = var.database_passwordless_aws_use_iam + TFE_DATABASE_USE_INSTANCE_PROFILE = var.database_passwordless_aws_use_iam # Additional environment variables for TFE config validation bypass TFE_DATABASE_PASSWORDLESS_AWS_USE_INSTANCE_PROFILE = var.database_passwordless_aws_use_iam - TFE_DATABASE_PASSWORDLESS_AWS_REGION = var.database_passwordless_aws_region + TFE_DATABASE_PASSWORDLESS_AWS_REGION = var.database_passwordless_aws_region } # Filter out null values so they don't appear in the compose file at all database_configuration = local.disk ? {} : { for k, v in local.database : k => v if v != null } diff --git a/modules/tfe_init/main.tf b/modules/tfe_init/main.tf index 1167524..fc2af8b 100644 --- a/modules/tfe_init/main.tf +++ b/modules/tfe_init/main.tf @@ -104,7 +104,7 @@ locals { Database = { Passwordless = { AWSUseInstanceProfile = var.database_passwordless_aws_use_iam - AWSRegion = var.database_passwordless_aws_region + AWSRegion = var.database_passwordless_aws_region } } From bc26dc9db75d838898a5ec8c49f23a47fb4c2703 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Thu, 20 Nov 2025 23:18:29 +0530 Subject: [PATCH 26/29] Fix tflint warnings: remove 8 unused variable declarations - Remove unused Redis passwordless AWS variables: - redis_passwordless_aws_use_iam - redis_passwordless_aws_region - redis_passwordless_aws_host_name - Remove unused Redis Sidekiq variables: - redis_sidekiq_host - redis_sidekiq_user - redis_sidekiq_password - redis_sidekiq_use_tls - redis_sidekiq_use_auth All variables were declared but never referenced in the codebase. Fixes terraform_unused_declarations warnings from tflint. --- .../variables.tf | 50 ------------------- 1 file changed, 50 deletions(-) diff --git a/modules/runtime_container_engine_config/variables.tf b/modules/runtime_container_engine_config/variables.tf index 3fbc93d..29aa19d 100644 --- a/modules/runtime_container_engine_config/variables.tf +++ b/modules/runtime_container_engine_config/variables.tf @@ -369,56 +369,6 @@ variable "redis_passwordless_azure_client_id" { description = "Azure Managed Service Identity (MSI) Client ID to be used for redis authentication. If not set, System Assigned Managed Identity will be used." } -variable "redis_passwordless_aws_use_iam" { - default = false - type = bool - description = "Whether or not to use AWS IAM authentication to connect to the Redis server. Defaults to false if no value is given." -} - -variable "redis_passwordless_aws_region" { - default = "" - type = string - description = "AWS region for IAM Redis authentication. Required when redis_passwordless_aws_use_iam is true." -} - -variable "redis_passwordless_aws_host_name" { - default = "" - type = string - description = "AWS ElastiCache Redis cluster name/host name for passwordless authentication. Used for IAM authentication." -} - -# Sidekiq Redis connection variables (for separate Redis instance if needed) -variable "redis_sidekiq_host" { - default = "" - type = string - description = "Redis host for Sidekiq background jobs. If empty, uses main redis_host." -} - -variable "redis_sidekiq_user" { - default = "" - type = string - description = "Redis user for Sidekiq background jobs. If empty, uses main redis_user." -} - -variable "redis_sidekiq_password" { - default = "" - type = string - description = "Redis password for Sidekiq background jobs. If empty, uses main redis_password." - sensitive = true -} - -variable "redis_sidekiq_use_tls" { - default = null - type = bool - description = "Whether to use TLS for Sidekiq Redis connection. If null, uses main redis_use_tls." -} - -variable "redis_sidekiq_use_auth" { - default = null - type = bool - description = "Whether to use authentication for Sidekiq Redis connection. If null, uses main redis_use_auth." -} - variable "run_pipeline_image" { type = string description = "Container image used to execute Terraform runs. Leave blank to use the default image that comes with Terraform Enterprise. Defaults to \"\" if no value is given." From df2c413b4d95fa4e34ea292a82ec13cfbd8de3ff Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Thu, 20 Nov 2025 23:22:25 +0530 Subject: [PATCH 27/29] Fix tflint warnings: remove 2 unused database IAM variable declarations --- modules/settings/variables.tf | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/modules/settings/variables.tf b/modules/settings/variables.tf index 49805e4..9da185b 100644 --- a/modules/settings/variables.tf +++ b/modules/settings/variables.tf @@ -239,18 +239,6 @@ variable "pg_extra_params" { description = "(Optional) Parameter keywords of the form param1=value1¶m2=value2 to support additional options that may be necessary for your specific PostgreSQL server. Allowed values are documented on the PostgreSQL site. An additional restriction on the sslmode parameter is that only the require, verify-full, verify-ca, and disable values are allowed." } -variable "database_aws_iam_auth_enabled" { - default = null - type = bool - description = "(Optional) Enable AWS IAM authentication for PostgreSQL connections. When enabled, TFE will use IAM tokens instead of password authentication to connect to PostgreSQL databases that have IAM authentication enabled." -} - -variable "database_aws_iam_region" { - default = null - type = string - description = "(Optional) AWS region for IAM authentication token generation. Required when database_aws_iam_auth_enabled is true." -} - # ------------------------------------------------------ # Redis # ------------------------------------------------------ From 24a895a89a433943b99f6a23c30932f12f9bc2cf Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Mon, 24 Nov 2025 17:19:18 +0530 Subject: [PATCH 28/29] Fix invalid TFE database environment variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove invalid TFE_DATABASE_USE_INSTANCE_PROFILE variable - Keep only the correct TFE_DATABASE_PASSWORDLESS_AWS_USE_INSTANCE_PROFILE - TFE_DATABASE_USE_INSTANCE_PROFILE is not a valid TFE environment variable - Eliminates duplicate and invalid configuration Line 19: TFE_DATABASE_USE_INSTANCE_PROFILE (invalid) → removed Line 21: TFE_DATABASE_PASSWORDLESS_AWS_USE_INSTANCE_PROFILE (valid) → kept --- modules/runtime_container_engine_config/database_config.tf | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/runtime_container_engine_config/database_config.tf b/modules/runtime_container_engine_config/database_config.tf index 6c616fc..8744e57 100644 --- a/modules/runtime_container_engine_config/database_config.tf +++ b/modules/runtime_container_engine_config/database_config.tf @@ -15,9 +15,7 @@ locals { TFE_DATABASE_CLIENT_KEY_FILE = var.database_client_key_file TFE_DATABASE_PASSWORDLESS_AZURE_USE_MSI = var.database_passwordless_azure_use_msi TFE_DATABASE_PASSWORDLESS_AZURE_CLIENT_ID = var.database_passwordless_azure_client_id - # Enable AWS instance profile for IAM authentication - TFE_DATABASE_USE_INSTANCE_PROFILE = var.database_passwordless_aws_use_iam - # Additional environment variables for TFE config validation bypass + # AWS IAM authentication for PostgreSQL passwordless TFE_DATABASE_PASSWORDLESS_AWS_USE_INSTANCE_PROFILE = var.database_passwordless_aws_use_iam TFE_DATABASE_PASSWORDLESS_AWS_REGION = var.database_passwordless_aws_region } From 0e517548b21bbede5a1ba4ef71cb5ac423f048f9 Mon Sep 17 00:00:00 2001 From: RAVI PRAKASH Date: Mon, 24 Nov 2025 17:23:29 +0530 Subject: [PATCH 29/29] Restore Explorer database passwordless Azure variables - Add back explorer_database_passwordless_azure_use_msi variable - Add back explorer_database_passwordless_azure_client_id variable - These were incorrectly removed as they are for Explorer database, not primary database - Our scope is limited to primary database changes only Reverts scope creep - Explorer database variables should remain untouched. --- modules/runtime_container_engine_config/variables.tf | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/modules/runtime_container_engine_config/variables.tf b/modules/runtime_container_engine_config/variables.tf index 29aa19d..c904b4c 100644 --- a/modules/runtime_container_engine_config/variables.tf +++ b/modules/runtime_container_engine_config/variables.tf @@ -148,6 +148,18 @@ variable "explorer_database_user" { description = "PostgreSQL user. Required when TFE_OPERATIONAL_MODE is external or active-active." } +variable "explorer_database_passwordless_azure_use_msi" { + default = false + type = bool + description = "Whether or not to use Azure Managed Service Identity (MSI) to connect to the explorer PostgreSQL database. Defaults to false if no value is given." +} + +variable "explorer_database_passwordless_azure_client_id" { + default = "" + type = string + description = "Azure Managed Service Identity (MSI) Client ID for explorer database. If not set, System Assigned Managed Identity will be used." +} + variable "disk_path" { default = null description = "The pathname of the directory in which Terraform Enterprise will store data in Mounted Disk mode. Required when var.operational_mode is 'disk'."