Created
November 21, 2024 00:32
-
-
Save jeremiahlukus/fe7cacaa5704a622f5d22e8d5af5f5ff to your computer and use it in GitHub Desktop.
Snowflake Password Rotation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
locals { | |
// AWS Configuration | |
aws_region = "us-east-1" | |
availability_zone = "us-east-1a" | |
subnet_id = "subnet-" | |
// Team Information | |
team_name = "" | |
team_email = "" | |
pipeline_repo = "" | |
portfolio = "" | |
delivery_stream = "" | |
release_train = "" | |
// Snowflake Configuration | |
snowflake_name = "" | |
snowflake_url = "" | |
snowflake_role = "" | |
snowflake_warehouse = "" | |
// Secret Names | |
source_secret_name = "" | |
target_secret_name = "glue-snowflake-connection-secret" | |
// Lambda Configuration | |
lambda_function_name = "snowflake-secret-sync" | |
lambda_role_name = "snowflake_secret_sync_lambda_role" | |
lambda_timeout = 30 | |
} | |
provider "aws" { | |
region = local.aws_region | |
default_tags { | |
tags = { | |
team = local.team_name | |
owner_email = local.team_email | |
email_recipients = local.team_email | |
pipeline_repo = local.pipeline_repo | |
portfolio = local.portfolio | |
delivery_stream = local.delivery_stream | |
release_train = local.release_train | |
} | |
} | |
ignore_tags { | |
key_prefixes = [ | |
"cai:catalog", | |
"coxauto:ssm", | |
] | |
keys = [ | |
"coxauto-ssm-managed-scan", | |
] | |
} | |
} | |
provider "alks" { | |
url = "https://alks.YOUR_DOMAIN.com/rest" | |
} | |
terraform { | |
required_version = "1.8.5" | |
backend "s3" { | |
bucket = "" | |
key = "" | |
region = "us-east-1" | |
} | |
required_providers { | |
aws = { | |
source = "hashicorp/aws" | |
} | |
alks = { | |
source = "Cox-Automotive/alks" | |
version = "~> 2.0" | |
} | |
} | |
} | |
data "aws_secretsmanager_secret" "existing_snowflake_secret" { | |
name = local.source_secret_name | |
} | |
data "aws_secretsmanager_secret_version" "existing_snowflake_secret" { | |
secret_id = data.aws_secretsmanager_secret.existing_snowflake_secret.id | |
} | |
resource "aws_secretsmanager_secret" "glue_snowflake_secret" { | |
name = local.target_secret_name | |
description = "Derived secret for Glue Snowflake connection. Source: ${data.aws_secretsmanager_secret.existing_snowflake_secret.name}" | |
} | |
resource "aws_secretsmanager_secret_version" "glue_snowflake_secret" { | |
secret_id = aws_secretsmanager_secret.glue_snowflake_secret.id | |
secret_string = jsonencode({ | |
sfUser = jsondecode(data.aws_secretsmanager_secret_version.existing_snowflake_secret.secret_string)["USER"] | |
sfPassword = jsondecode(data.aws_secretsmanager_secret_version.existing_snowflake_secret.secret_string)["PASSWORD"] | |
sfWarehouse = local.snowflake_warehouse | |
}) | |
} | |
resource "alks_iamrole" "lambda_role" { | |
name = local.lambda_role_name | |
type = "AWS Lambda" | |
include_default_policies = false | |
} | |
resource "aws_iam_role_policy" "lambda_policy" { | |
name = "snowflake_secret_sync_lambda_policy" | |
role = alks_iamrole.lambda_role.id | |
policy = jsonencode({ | |
Version = "2012-10-17" | |
Statement = [ | |
{ | |
Effect = "Allow" | |
Action = [ | |
"secretsmanager:GetSecretValue", | |
"secretsmanager:PutSecretValue" | |
] | |
Resource = [ | |
data.aws_secretsmanager_secret.existing_snowflake_secret.arn, | |
aws_secretsmanager_secret.glue_snowflake_secret.arn | |
] | |
}, | |
{ | |
Effect = "Allow" | |
Action = [ | |
"logs:CreateLogGroup", | |
"logs:CreateLogStream", | |
"logs:PutLogEvents" | |
] | |
Resource = ["arn:aws:logs:*:*:*"] | |
} | |
] | |
}) | |
} | |
data "archive_file" "lambda_zip" { | |
type = "zip" | |
output_path = "${path.module}/secret_sync.zip" | |
source { | |
content = <<EOF | |
import { SecretsManager } from '@aws-sdk/client-secrets-manager'; | |
const secretsManager = new SecretsManager(); | |
// Helper function to sleep | |
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); | |
export const handler = async (event, context) => { | |
try { | |
// Wait for 10 seconds | |
console.log('Waiting for 10 seconds to ensure event processing...'); | |
await sleep(10000); // 10000 milliseconds = 10 seconds | |
// Get source secret | |
const sourceSecret = await secretsManager.getSecretValue({ | |
SecretId: process.env.SOURCE_SECRET_ARN | |
}); | |
// Parse source secret | |
const sourceData = JSON.parse(sourceSecret.SecretString); | |
// Create new secret value | |
const newSecretValue = { | |
sfUser: sourceData.USER, | |
sfPassword: sourceData.PASSWORD, | |
sfWarehouse: process.env.WAREHOUSE_NAME, | |
lastUpdated: new Date().toISOString() | |
}; | |
// Update target secret | |
await secretsManager.putSecretValue({ | |
SecretId: process.env.TARGET_SECRET_ARN, | |
SecretString: JSON.stringify(newSecretValue) | |
}); | |
console.log('Secret synchronized successfully'); | |
return { | |
statusCode: 200, | |
body: 'Secret synchronized successfully' | |
}; | |
} catch (error) { | |
console.error('Error:', error); | |
throw error; | |
} | |
}; | |
EOF | |
filename = "index.mjs" | |
} | |
} | |
resource "aws_lambda_function" "secret_sync" { | |
filename = data.archive_file.lambda_zip.output_path | |
source_code_hash = data.archive_file.lambda_zip.output_base64sha256 | |
function_name = local.lambda_function_name | |
role = alks_iamrole.lambda_role.arn | |
handler = "index.handler" | |
runtime = "nodejs20.x" | |
timeout = local.lambda_timeout | |
environment { | |
variables = { | |
SOURCE_SECRET_ARN = data.aws_secretsmanager_secret.existing_snowflake_secret.arn | |
TARGET_SECRET_ARN = aws_secretsmanager_secret.glue_snowflake_secret.arn | |
WAREHOUSE_NAME = local.snowflake_warehouse | |
} | |
} | |
} | |
resource "aws_cloudwatch_event_rule" "secret_changed" { | |
name = "track-snowflake-secret-changes" | |
description = "Track changes to the source Snowflake secret" | |
event_pattern = jsonencode({ | |
source = ["aws.secretsmanager"] | |
detail-type = ["AWS API Call via CloudTrail"] | |
detail = { | |
eventSource = ["secretsmanager.amazonaws.com"] | |
eventName = ["PutSecretValue", "UpdateSecret", "RotationSucceeded", "CreateSecret"] | |
requestParameters = { | |
secretId = [ | |
data.aws_secretsmanager_secret.existing_snowflake_secret.name, | |
data.aws_secretsmanager_secret.existing_snowflake_secret.arn | |
] | |
} | |
} | |
}) | |
} | |
resource "aws_cloudwatch_event_target" "lambda_target" { | |
rule = aws_cloudwatch_event_rule.secret_changed.name | |
target_id = "SyncSnowflakeSecrets" | |
arn = aws_lambda_function.secret_sync.arn | |
} | |
resource "aws_lambda_permission" "allow_eventbridge" { | |
statement_id = "AllowEventBridgeInvoke" | |
action = "lambda:InvokeFunction" | |
function_name = aws_lambda_function.secret_sync.function_name | |
principal = "events.amazonaws.com" | |
source_arn = aws_cloudwatch_event_rule.secret_changed.arn | |
} | |
resource "aws_glue_connection" "snowflake_connection" { | |
name = local.snowflake_name | |
connection_type = "SNOWFLAKE" | |
connection_properties = { | |
SparkProperties = jsonencode({ | |
sfUrl = local.snowflake_url | |
secretId = aws_secretsmanager_secret.glue_snowflake_secret.name | |
sfRole = local.snowflake_role | |
}) | |
} | |
physical_connection_requirements { | |
subnet_id = local.subnet_id | |
availability_zone = local.availability_zone | |
} | |
depends_on = [aws_secretsmanager_secret_version.glue_snowflake_secret] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment