diff --git a/modules/runtime_container_engine_config/database_config.tf b/modules/runtime_container_engine_config/database_config.tf index 3ccba0c..d89c284 100644 --- a/modules/runtime_container_engine_config/database_config.tf +++ b/modules/runtime_container_engine_config/database_config.tf @@ -4,7 +4,7 @@ locals { database = { TFE_DATABASE_USER = var.database_user - TFE_DATABASE_PASSWORD = var.database_password + TFE_DATABASE_PASSWORD = "" TFE_DATABASE_HOST = var.database_host TFE_DATABASE_NAME = var.database_name TFE_DATABASE_PARAMETERS = var.database_parameters @@ -14,8 +14,12 @@ 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 + # 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 } database_configuration = local.disk ? {} : local.database + explorer_database = { TFE_EXPLORER_DATABASE_HOST = var.explorer_database_host TFE_EXPLORER_DATABASE_NAME = var.explorer_database_name diff --git a/modules/runtime_container_engine_config/variables.tf b/modules/runtime_container_engine_config/variables.tf index 445afe5..c904b4c 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 diff --git a/modules/tfe_init/main.tf b/modules/tfe_init/main.tf index e0f6126..fc2af8b 100644 --- a/modules/tfe_init/main.tf +++ b/modules/tfe_init/main.tf @@ -100,7 +100,22 @@ 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 + 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/aws.ubuntu.docker.tfe.sh.tpl b/modules/tfe_init/templates/aws.ubuntu.docker.tfe.sh.tpl index 38bea0d..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,4 +183,21 @@ mkdir -p $tfe_dir echo ${docker_compose} | base64 -d > $tfe_dir/compose.yaml +%{ 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}" +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 +%{ endif ~} + docker compose -f /etc/tfe/compose.yaml up -d diff --git a/modules/tfe_init/templates/tfe.sh.tpl b/modules/tfe_init/templates/tfe.sh.tpl index f7a4fbc..76f30cb 100644 --- a/modules/tfe_init/templates/tfe.sh.tpl +++ b/modules/tfe_init/templates/tfe.sh.tpl @@ -187,6 +187,95 @@ 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 + +# 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 + 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 6c2918e..7435c2d 100644 --- a/modules/tfe_init/variables.tf +++ b/modules/tfe_init/variables.tf @@ -218,3 +218,21 @@ 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." +} + +variable "database_iam_username" { + default = null + type = string + description = "PostgreSQL IAM user for AWS IAM authentication." +}