From 6b3588c9356a8134d64d145981c386df1bc09b7b Mon Sep 17 00:00:00 2001 From: charles-nava <127765344+charles-nava@users.noreply.github.com> Date: Thu, 9 Nov 2023 12:25:14 -0600 Subject: [PATCH] Add secret manager vpc endpoint (#469) ## Ticket No ticket, hotfix ## Changes Taken from platform-test pr Add commented out option for using secret manager Includes json handling fix ## Testing Testing in [platform test](https://github.com/navapbc/platform-test/pull/63) --- .gitignore | 3 ++ infra/modules/database/backups.tf | 4 +- infra/modules/database/main.tf | 2 +- infra/modules/database/monitoring.tf | 2 +- infra/modules/database/role-manager.tf | 45 +++++++++++++++---- .../database/role_manager/requirements.txt | 2 +- .../database/role_manager/role_manager.py | 9 +++- infra/networks/main.tf | 2 +- 8 files changed, 52 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index ce1dd69e0..8fc4637e2 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ # Ignore local environment variables which can contain environment secrets .env .envrc + +# Python testing stuff +*__pycache__* diff --git a/infra/modules/database/backups.tf b/infra/modules/database/backups.tf index bdf8afe61..b3f725c25 100644 --- a/infra/modules/database/backups.tf +++ b/infra/modules/database/backups.tf @@ -30,7 +30,7 @@ data "aws_kms_key" "backup_vault_key" { # See https://docs.aws.amazon.com/aws-backup/latest/devguide/assigning-resources.html # and https://docs.aws.amazon.com/aws-backup/latest/devguide/API_BackupSelection.html resource "aws_backup_selection" "db_backup" { - name = "${var.name}-db-backup" + name = "${local.name}-db-backup" plan_id = aws_backup_plan.backup_plan.id iam_role_arn = aws_iam_role.db_backup_role.arn @@ -41,7 +41,7 @@ resource "aws_backup_selection" "db_backup" { # Role that AWS Backup uses to authenticate when backing up the target resource resource "aws_iam_role" "db_backup_role" { - name_prefix = "${var.name}-db-backup-role-" + name_prefix = "${local.name}-db-backup-role-" assume_role_policy = data.aws_iam_policy_document.db_backup_policy.json } diff --git a/infra/modules/database/main.tf b/infra/modules/database/main.tf index b5913d75a..fc7b98c0c 100644 --- a/infra/modules/database/main.tf +++ b/infra/modules/database/main.tf @@ -6,7 +6,7 @@ locals { primary_instance_name = "${var.name}-primary" role_manager_name = "${var.name}-role-manager" role_manager_package = "${path.root}/role_manager.zip" - + name = substr(var.name, 0, 12) # The ARN that represents the users accessing the database are of the format: "arn:aws:rds-db:::dbuser:/"" # See https://aws.amazon.com/blogs/database/using-iam-authentication-to-connect-with-pgadmin-amazon-aurora-postgresql-or-amazon-rds-for-postgresql/ db_user_arn_prefix = "arn:aws:rds-db:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:dbuser:${aws_rds_cluster.db.cluster_resource_id}" diff --git a/infra/modules/database/monitoring.tf b/infra/modules/database/monitoring.tf index ee5a6ca0b..271553161 100644 --- a/infra/modules/database/monitoring.tf +++ b/infra/modules/database/monitoring.tf @@ -3,7 +3,7 @@ #----------------------------------# resource "aws_iam_role" "rds_enhanced_monitoring" { - name_prefix = "${var.name}-enhanced-monitoring-" + name_prefix = "${local.name}-enhanced-monitoring-" assume_role_policy = data.aws_iam_policy_document.rds_enhanced_monitoring.json } diff --git a/infra/modules/database/role-manager.tf b/infra/modules/database/role-manager.tf index 1b29abf33..4dca5d19d 100644 --- a/infra/modules/database/role-manager.tf +++ b/infra/modules/database/role-manager.tf @@ -5,6 +5,10 @@ # This includes creating and granting permissions to roles # as well as viewing existing roles +locals { + ssm_password_name = length(aws_rds_cluster.db.master_user_secret) == 1 ? "/aws/reference/secretsmanager/${data.aws_secretsmanager_secret.db_pass[0].name}" : "" +} + resource "aws_lambda_function" "role_manager" { function_name = local.role_manager_name @@ -29,7 +33,8 @@ resource "aws_lambda_function" "role_manager" { DB_PORT = aws_rds_cluster.db.port DB_USER = local.master_username DB_NAME = aws_rds_cluster.db.database_name - DB_PASSWORD_PARAM_NAME = "/aws/reference/secretsmanager/${data.aws_secretsmanager_secret.db_pass.name}" + DB_PASSWORD_PARAM_NAME = local.ssm_password_name + DB_PASSWORD_SECRET_ARN = aws_rds_cluster.db.master_user_secret[0].secret_arn DB_SCHEMA = var.schema_name APP_USER = var.app_username MIGRATOR_USER = var.migrator_username @@ -42,7 +47,7 @@ resource "aws_lambda_function" "role_manager" { tracing_config { mode = "Active" } - + timeout = 30 # checkov:skip=CKV_AWS_272:TODO(https://github.com/navapbc/template-infra/issues/283) # checkov:skip=CKV_AWS_116:Dead letter queue (DLQ) configuration is only relevant for asynchronous invocations @@ -79,6 +84,11 @@ resource "aws_kms_key" "role_manager" { enable_key_rotation = true } +data "aws_secretsmanager_secret" "db_pass" { + count = length(aws_rds_cluster.db.master_user_secret) + arn = aws_rds_cluster.db.master_user_secret[0].secret_arn +} + # IAM for Role Manager lambda function resource "aws_iam_role" "role_manager" { name = "${var.name}-manager" @@ -94,26 +104,43 @@ resource "aws_iam_role" "role_manager" { ] } -data "aws_secretsmanager_secret" "db_pass" { - arn = aws_rds_cluster.db.master_user_secret[0].secret_arn -} + resource "aws_iam_role_policy" "ssm_access" { name = "${var.name}-role-manager-ssm-access" role = aws_iam_role.role_manager.id + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = ["kms:Decrypt"] + Resource = [data.aws_kms_key.default_ssm_key.arn] + } + ] + }) +} + +resource "aws_iam_role_policy" "database_credential_tool" { + count = length(aws_rds_cluster.db.master_user_secret) + name = "${var.name}-role-manager-rds-ssm-access" + role = aws_iam_role.role_manager.id + policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = ["secretsmanager:GetSecretValue"] - Resource = [data.aws_secretsmanager_secret.db_pass.arn] + Resource = [data.aws_secretsmanager_secret.db_pass[0].arn] }, { - Effect = "Allow" - Action = ["kms:Decrypt"] - Resource = [data.aws_kms_key.default_ssm_key.arn] + Effect = "Allow" + Action = ["ssm:GetParameter"] + Resource = [ + "arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.id}:parameter${local.ssm_password_name}" + ] } ] }) diff --git a/infra/modules/database/role_manager/requirements.txt b/infra/modules/database/role_manager/requirements.txt index 94345bbec..a63f23746 100644 --- a/infra/modules/database/role_manager/requirements.txt +++ b/infra/modules/database/role_manager/requirements.txt @@ -1 +1 @@ -pg8000 +pg8000 \ No newline at end of file diff --git a/infra/modules/database/role_manager/role_manager.py b/infra/modules/database/role_manager/role_manager.py index 373c3218e..9f8fe8be8 100644 --- a/infra/modules/database/role_manager/role_manager.py +++ b/infra/modules/database/role_manager/role_manager.py @@ -2,9 +2,14 @@ import itertools from operator import itemgetter import os +import json import logging from pg8000.native import Connection, identifier +logging.basicConfig() +logging.getLogger('botocore').setLevel(logging.DEBUG) +logging.getLogger('boto3').setLevel(logging.DEBUG) + logger = logging.getLogger() logger.setLevel(logging.INFO) @@ -115,9 +120,9 @@ def connect_using_iam(user: str) -> Connection: return Connection(user=user, host=host, port=port, database=database, password=token, ssl_context=True) def get_password() -> str: - ssm = boto3.client("ssm") + ssm = boto3.client("ssm",region_name=os.environ["AWS_REGION"]) param_name = os.environ["DB_PASSWORD_PARAM_NAME"] - logger.info("Fetching password from parameter store") + logger.info("Fetching password from parameter store:\n%s"%param_name) result = json.loads(ssm.get_parameter( Name=param_name, WithDecryption=True, diff --git a/infra/networks/main.tf b/infra/networks/main.tf index af6c505a1..5c50ce498 100644 --- a/infra/networks/main.tf +++ b/infra/networks/main.tf @@ -18,7 +18,7 @@ locals { # # The database module requires VPC access from private networks to SSM, KMS, and RDS aws_service_integrations = toset( - module.app_config.has_database ? ["ssm", "kms"] : [] + module.app_config.has_database ? ["ssm", "kms", "secretsmanager"] : [] ) }