|
variable "database_password" { |
|
type = "string" |
|
} |
|
variable "subnet_ids" { |
|
description = "Comma-delimited string of subnet ids" |
|
type = "string" |
|
} |
|
variable "security_group_id" { |
|
type = "string" |
|
} |
|
|
|
provider "archive" { |
|
version = "~> 1.1" |
|
} |
|
|
|
provider "aws" { |
|
version = "~> 1.23" |
|
} |
|
|
|
locals { |
|
# Solution from this comment to open issue on non-relative paths |
|
# https://github.com/hashicorp/terraform/issues/8204#issuecomment-332239294 |
|
|
|
# +1 for removing the "/" |
|
creation_filename = "${substr(data.archive_file.rds_creation_zip.output_path, length(path.cwd) + 1, -1)}" |
|
setup_filename = "${substr(data.archive_file.rds_setup_zip.output_path, length(path.cwd) + 1, -1)}" |
|
} |
|
|
|
data "archive_file" "rds_creation_zip" { |
|
type = "zip" |
|
output_path = "${path.module}/rds_creation.zip" |
|
source_dir = "${path.module}/rds_creation/" |
|
} |
|
|
|
data "archive_file" "rds_setup_zip" { |
|
type = "zip" |
|
output_path = "${path.module}/rds_setup.zip" |
|
source_dir = "${path.module}/rds_setup/" |
|
} |
|
|
|
# The SQL script that gets evaluated on new database instances. |
|
# Clearly, you will want to change this! |
|
data "template_file" "sql_script" { |
|
template = <<SQL |
|
SELECT 1; |
|
SQL |
|
} |
|
|
|
# Subscribe to all new database creation notifications |
|
resource "aws_db_event_subscription" "creation" { |
|
name = "rds-creation" |
|
sns_topic = "${aws_sns_topic.rds.arn}" |
|
source_type = "db-instance" |
|
event_categories = ["creation"] |
|
} |
|
|
|
# 'External' Lambda function that gets the new database SNS notification |
|
# and queries the AWS API to obtain further details about this. |
|
# |
|
# It then sends those details off to another SNS notification, which is |
|
# picked up by the 'internal' Lambda function. |
|
resource "aws_lambda_function" "rds_creation" { |
|
function_name = "rds-creation" |
|
handler = "index.handler" |
|
filename = "${local.creation_filename}" |
|
source_code_hash = "${base64sha256(file("${local.creation_filename}"))}" |
|
|
|
role = "${aws_iam_role.rds_external_lambda.arn}" |
|
runtime = "nodejs8.10" |
|
timeout = 10 |
|
|
|
environment { |
|
variables { |
|
SNS_TOPIC_ARN = "${aws_sns_topic.internal.arn}" |
|
} |
|
} |
|
} |
|
|
|
# 'Internal' Lambda function which receives database information from |
|
# the external function (via SNS) and then connects to the database |
|
# and evaluates the script against it. |
|
# |
|
# This operates within the VPC, and hence does not have access to the |
|
# internet or AWS APIs. |
|
resource "aws_lambda_function" "rds_setup" { |
|
function_name = "rds-setup" |
|
handler = "index.handler" |
|
filename = "${local.setup_filename}" |
|
source_code_hash = "${base64sha256(file("${local.setup_filename}"))}" |
|
|
|
role = "${aws_iam_role.rds_internal_lambda.arn}" |
|
runtime = "nodejs8.10" |
|
timeout = 10 |
|
|
|
vpc_config { |
|
subnet_ids = ["${split(",", var.subnet_ids)}"] |
|
security_group_ids = ["${var.security_group_id}"] |
|
} |
|
|
|
environment { |
|
variables = { |
|
PGPASSWORD = "${var.database_password}" |
|
SQL_SCRIPT = "${replace(trimspace(data.template_file.sql_script.rendered), "/\n/", " ")}" |
|
} |
|
} |
|
} |
|
|
|
resource "aws_lambda_permission" "rds_creation" { |
|
statement_id = "AllowExecutionFromSNS" |
|
action = "lambda:InvokeFunction" |
|
function_name = "${aws_lambda_function.rds_creation.function_name}" |
|
principal = "sns.amazonaws.com" |
|
source_arn = "${aws_sns_topic.rds.arn}" |
|
} |
|
|
|
resource "aws_lambda_permission" "rds_setup" { |
|
statement_id = "AllowExecutionFromSNS" |
|
action = "lambda:InvokeFunction" |
|
function_name = "${aws_lambda_function.rds_setup.function_name}" |
|
principal = "sns.amazonaws.com" |
|
source_arn = "${aws_sns_topic.internal.arn}" |
|
} |
|
|
|
# SNS Topic for new database creations (via RDS events) |
|
resource "aws_sns_topic" "rds" { |
|
name = "rds-creation" |
|
} |
|
|
|
# SNS Topic for database credentials (via the external lambda) |
|
resource "aws_sns_topic" "internal" { |
|
name = "rds-setup" |
|
} |
|
|
|
# Subscriptions connecting topics to lambdas |
|
resource "aws_sns_topic_subscription" "rds" { |
|
topic_arn = "${aws_sns_topic.rds.arn}" |
|
protocol = "lambda" |
|
endpoint = "${aws_lambda_function.rds_creation.arn}" |
|
} |
|
|
|
resource "aws_sns_topic_subscription" "rds_internal" { |
|
topic_arn = "${aws_sns_topic.internal.arn}" |
|
protocol = "lambda" |
|
endpoint = "${aws_lambda_function.rds_setup.arn}" |
|
} |
|
|
|
# IAM Role for 'External' lambda which has access to |
|
# CloudWatch, SNS, and RDS. |
|
resource "aws_iam_role" "rds_external_lambda" { |
|
name = "RDSExternal" |
|
|
|
assume_role_policy = <<EOF |
|
{ |
|
"Version": "2012-10-17", |
|
"Statement": [ |
|
{ |
|
"Action": "sts:AssumeRole", |
|
"Principal": { |
|
"Service": "lambda.amazonaws.com" |
|
}, |
|
"Effect": "Allow" |
|
} |
|
] |
|
} |
|
EOF |
|
} |
|
|
|
# IAM Role for 'Internal' lambda which has access to |
|
# CloudWatch and VPC behaviour. |
|
resource "aws_iam_role" "rds_internal_lambda" { |
|
name = "RDSInternal" |
|
|
|
assume_role_policy = <<EOF |
|
{ |
|
"Version": "2012-10-17", |
|
"Statement": [ |
|
{ |
|
"Action": "sts:AssumeRole", |
|
"Principal": { |
|
"Service": "lambda.amazonaws.com" |
|
}, |
|
"Effect": "Allow" |
|
} |
|
] |
|
} |
|
EOF |
|
} |
|
|
|
resource "aws_iam_role_policy" "rds_internal" { |
|
name = "RDSInternalNotifications" |
|
role = "${aws_iam_role.rds_internal_lambda.id}" |
|
policy = <<EOF |
|
{ |
|
"Version": "2012-10-17", |
|
"Statement": [{ |
|
"Effect": "Allow", |
|
"Action": [ |
|
"logs:CreateLogGroup", |
|
"logs:CreateLogStream", |
|
"logs:PutLogEvents" |
|
], |
|
"Resource": "*" |
|
}] |
|
} |
|
EOF |
|
} |
|
|
|
resource "aws_iam_role_policy" "rds_external" { |
|
name = "RDSExternalNotifications" |
|
role = "${aws_iam_role.rds_external_lambda.id}" |
|
policy = <<EOF |
|
{ |
|
"Version": "2012-10-17", |
|
"Statement": [{ |
|
"Effect": "Allow", |
|
"Action": [ |
|
"logs:CreateLogGroup", |
|
"logs:CreateLogStream", |
|
"logs:PutLogEvents", |
|
"rds:DescribeDBInstances", |
|
"sns:Publish" |
|
], |
|
"Resource": "*" |
|
}] |
|
} |
|
EOF |
|
} |
|
|
|
resource "aws_iam_role_policy_attachment" "rds_lambda_vpc" { |
|
role = "${aws_iam_role.rds_internal_lambda.id}" |
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole" |
|
} |
Hello Pat! The code on this gist is brilliant but we can't use it because there is no license. Having no license does not allow people to use your open source code in any way (commercial, private, etc). For more information see here. Would you consider adding a license to the gist (e.g. MIT, Apache, etc)?