diff --git a/.gitignore b/.gitignore
index 7f041e9b..7b4139cf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,6 @@ override.tf.json
# Ignore CLI configuration files
.terraformrc
terraform.rc
+
+# Lambda directories
+builds/
diff --git a/README.md b/README.md
index 81ac06dc..b243943b 100644
--- a/README.md
+++ b/README.md
@@ -220,6 +220,7 @@ No providers.
| Name | Source | Version |
|------|--------|---------|
| [db\_instance](#module\_db\_instance) | ./modules/db_instance | n/a |
+| [db\_instance\_role\_association](#module\_db\_instance\_role\_association) | ./modules/db_instance_role_association | n/a |
| [db\_option\_group](#module\_db\_option\_group) | ./modules/db_option_group | n/a |
| [db\_parameter\_group](#module\_db\_parameter\_group) | ./modules/db_parameter_group | n/a |
| [db\_subnet\_group](#module\_db\_subnet\_group) | ./modules/db_subnet_group | n/a |
@@ -252,6 +253,7 @@ No resources.
| [create\_db\_subnet\_group](#input\_create\_db\_subnet\_group) | Whether to create a database subnet group | `bool` | `false` | no |
| [create\_monitoring\_role](#input\_create\_monitoring\_role) | Create IAM role with a defined name that permits RDS to send enhanced monitoring metrics to CloudWatch Logs | `bool` | `false` | no |
| [custom\_iam\_instance\_profile](#input\_custom\_iam\_instance\_profile) | RDS custom iam instance profile | `string` | `null` | no |
+| [db\_instance\_role\_associations](#input\_db\_instance\_role\_associations) | A map of DB instance supported feature name to role association ARNs. | `map(any)` | `{}` | no |
| [db\_instance\_tags](#input\_db\_instance\_tags) | Additional tags for the DB instance | `map(string)` | `{}` | no |
| [db\_name](#input\_db\_name) | The DB name to create. If omitted, no database is created initially | `string` | `null` | no |
| [db\_option\_group\_tags](#input\_db\_option\_group\_tags) | Additional tags for the DB option group | `map(string)` | `{}` | no |
@@ -342,6 +344,7 @@ No resources.
| [db\_instance\_name](#output\_db\_instance\_name) | The database name |
| [db\_instance\_port](#output\_db\_instance\_port) | The database port |
| [db\_instance\_resource\_id](#output\_db\_instance\_resource\_id) | The RDS Resource ID of this instance |
+| [db\_instance\_role\_associations](#output\_db\_instance\_role\_associations) | A map of DB Instance Identifiers and IAM Role ARNs separated by a comma |
| [db\_instance\_status](#output\_db\_instance\_status) | The RDS instance status |
| [db\_instance\_username](#output\_db\_instance\_username) | The master username for the database |
| [db\_listener\_endpoint](#output\_db\_listener\_endpoint) | Specifies the listener connection endpoint for SQL Server Always On |
diff --git a/examples/role-association-postgres/README.md b/examples/role-association-postgres/README.md
new file mode 100644
index 00000000..1af47b7b
--- /dev/null
+++ b/examples/role-association-postgres/README.md
@@ -0,0 +1,70 @@
+# RDS DB instance role association example for PostgreSQL
+
+Configuration in this directory creates a DB instance role association to invoke a lambda function.
+
+Further database configurations for creating extension and invoking from postgres: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/PostgreSQL-Lambda.html
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | >= 1.0 |
+| [aws](#requirement\_aws) | >= 5.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | >= 5.0 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [db](#module\_db) | ../../ | n/a |
+| [lambda](#module\_lambda) | terraform-aws-modules/lambda/aws | ~> 6.0 |
+| [rds\_invoke\_lambda\_policy](#module\_rds\_invoke\_lambda\_policy) | terraform-aws-modules/iam/aws//modules/iam-policy | ~> 5.28.0 |
+| [rds\_invoke\_lambda\_role](#module\_rds\_invoke\_lambda\_role) | terraform-aws-modules/iam/aws//modules/iam-assumable-role | ~> 5.28.0 |
+| [security\_group](#module\_security\_group) | terraform-aws-modules/security-group/aws | ~> 4.0 |
+| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source |
+| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
+| [aws_iam_policy_document.rds_invoke_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.rds_invoke_lambda_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+
+## Inputs
+
+No inputs.
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [db\_enhanced\_monitoring\_iam\_role\_arn](#output\_db\_enhanced\_monitoring\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the monitoring role |
+| [db\_instance\_address](#output\_db\_instance\_address) | The address of the RDS instance |
+| [db\_instance\_arn](#output\_db\_instance\_arn) | The ARN of the RDS instance |
+| [db\_instance\_availability\_zone](#output\_db\_instance\_availability\_zone) | The availability zone of the RDS instance |
+| [db\_instance\_cloudwatch\_log\_groups](#output\_db\_instance\_cloudwatch\_log\_groups) | Map of CloudWatch log groups created and their attributes |
+| [db\_instance\_endpoint](#output\_db\_instance\_endpoint) | The connection endpoint |
+| [db\_instance\_engine](#output\_db\_instance\_engine) | The database engine |
+| [db\_instance\_engine\_version\_actual](#output\_db\_instance\_engine\_version\_actual) | The running version of the database |
+| [db\_instance\_hosted\_zone\_id](#output\_db\_instance\_hosted\_zone\_id) | The canonical hosted zone ID of the DB instance (to be used in a Route 53 Alias record) |
+| [db\_instance\_identifier](#output\_db\_instance\_identifier) | The RDS instance identifier |
+| [db\_instance\_master\_user\_secret\_arn](#output\_db\_instance\_master\_user\_secret\_arn) | The ARN of the master user secret (Only available when manage\_master\_user\_password is set to true) |
+| [db\_instance\_name](#output\_db\_instance\_name) | The database name |
+| [db\_instance\_port](#output\_db\_instance\_port) | The database port |
+| [db\_instance\_resource\_id](#output\_db\_instance\_resource\_id) | The RDS Resource ID of this instance |
+| [db\_instance\_role\_associations](#output\_db\_instance\_role\_associations) | The outputs for the role associations |
+| [db\_instance\_status](#output\_db\_instance\_status) | The RDS instance status |
+| [db\_instance\_username](#output\_db\_instance\_username) | The master username for the database |
+| [db\_parameter\_group\_arn](#output\_db\_parameter\_group\_arn) | The ARN of the db parameter group |
+| [db\_parameter\_group\_id](#output\_db\_parameter\_group\_id) | The db parameter group id |
+| [db\_subnet\_group\_arn](#output\_db\_subnet\_group\_arn) | The ARN of the db subnet group |
+| [db\_subnet\_group\_id](#output\_db\_subnet\_group\_id) | The db subnet group name |
+
diff --git a/examples/role-association-postgres/fixtures/lambda_function.py b/examples/role-association-postgres/fixtures/lambda_function.py
new file mode 100644
index 00000000..337b133b
--- /dev/null
+++ b/examples/role-association-postgres/fixtures/lambda_function.py
@@ -0,0 +1,3 @@
+def lambda_handler(event, context):
+
+ return "Triggered by RDS Lambda!"
diff --git a/examples/role-association-postgres/main.tf b/examples/role-association-postgres/main.tf
new file mode 100644
index 00000000..07d7202e
--- /dev/null
+++ b/examples/role-association-postgres/main.tf
@@ -0,0 +1,195 @@
+provider "aws" {
+ region = local.region
+}
+
+data "aws_caller_identity" "current" {}
+data "aws_availability_zones" "available" {}
+
+locals {
+ name = "role-association-invoke-lambda"
+ region = "eu-west-1"
+
+ vpc_cidr = "10.0.0.0/16"
+ azs = slice(data.aws_availability_zones.available.names, 0, 3)
+
+ tags = {
+ Name = local.name
+ Example = local.name
+ Repository = "https://github.com/terraform-aws-modules/terraform-aws-rds"
+ }
+}
+
+################################################################################
+# RDS Module
+################################################################################
+
+module "db" {
+ source = "../../"
+
+ identifier = local.name
+
+ # All available versions: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html#PostgreSQL.Concepts
+ engine = "postgres"
+ engine_version = "14"
+ family = "postgres14" # DB parameter group
+ major_engine_version = "14" # DB option group
+ instance_class = "db.t4g.large"
+
+ allocated_storage = 20
+
+ # NOTE: Do NOT use 'user' as the value for 'username' as it throws:
+ # "Error creating DB Instance: InvalidParameterValue: MasterUsername
+ # user cannot be used as it is a reserved word used by the engine"
+ db_name = "RoleAssociationInvokeLambda"
+ username = "role_association_invoke_lambda"
+ port = 5432
+
+ multi_az = true
+ db_subnet_group_name = module.vpc.database_subnet_group
+ vpc_security_group_ids = [module.security_group.security_group_id]
+
+ maintenance_window = "Mon:00:00-Mon:03:00"
+ backup_window = "03:00-06:00"
+ backup_retention_period = 0
+
+ deletion_protection = false
+
+ # https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/PostgreSQL-Lambda.html
+ db_instance_role_associations = {
+ Lambda = module.rds_invoke_lambda_role.iam_role_arn
+ }
+
+ parameters = [
+ {
+ name = "rds.custom_dns_resolution"
+ value = 1
+ apply_method = "pending-reboot"
+ },
+ ]
+
+ tags = local.tags
+}
+
+################################################################################
+# Supporting Resources
+################################################################################
+
+module "vpc" {
+ source = "terraform-aws-modules/vpc/aws"
+ version = "~> 5.0"
+
+ name = local.name
+ cidr = local.vpc_cidr
+
+ azs = local.azs
+ public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k)]
+ private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 3)]
+ database_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 6)]
+
+ create_database_subnet_group = true
+ enable_nat_gateway = true
+
+ tags = local.tags
+}
+
+module "security_group" {
+ source = "terraform-aws-modules/security-group/aws"
+ version = "~> 4.0"
+
+ name = local.name
+ description = "Complete PostgreSQL example security group"
+ vpc_id = module.vpc.vpc_id
+
+ # ingress
+ ingress_with_cidr_blocks = [
+ {
+ from_port = 5432
+ to_port = 5432
+ protocol = "tcp"
+ description = "PostgreSQL access from within VPC"
+ cidr_blocks = module.vpc.vpc_cidr_block
+ },
+ ]
+
+ # egress
+ egress_with_cidr_blocks = [
+ {
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ description = "Egress to AWS Lambda VPC"
+ cidr_blocks = "0.0.0.0/0"
+ }
+ ]
+
+ tags = local.tags
+}
+
+module "rds_invoke_lambda_role" {
+ source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role"
+ version = "~> 5.28.0"
+
+ create_role = true
+ role_requires_mfa = false
+
+ role_name_prefix = local.name
+
+ custom_role_policy_arns = [
+ module.rds_invoke_lambda_policy.arn
+ ]
+ custom_role_trust_policy = data.aws_iam_policy_document.rds_invoke_lambda_assume_role.json
+}
+
+module "rds_invoke_lambda_policy" {
+ source = "terraform-aws-modules/iam/aws//modules/iam-policy"
+ version = "~> 5.28.0"
+
+ name = "${local.name}-policy"
+ path = "/"
+ description = "Invoke Lambda from RDS Postgresql policy"
+
+ policy = data.aws_iam_policy_document.rds_invoke_lambda.json
+}
+
+data "aws_iam_policy_document" "rds_invoke_lambda" {
+ statement {
+ sid = "InvokeLambda"
+ actions = [
+ "lambda:InvokeFunction"
+ ]
+ resources = [
+ module.lambda.lambda_function_arn
+ ]
+ }
+}
+
+data "aws_iam_policy_document" "rds_invoke_lambda_assume_role" {
+ statement {
+ sid = "AssumeRole"
+
+ principals {
+ type = "Service"
+ identifiers = ["rds.amazonaws.com"]
+ }
+
+ condition {
+ test = "StringEquals"
+ values = [data.aws_caller_identity.current.id]
+ variable = "aws:SourceAccount"
+ }
+
+ effect = "Allow"
+
+ actions = ["sts:AssumeRole"]
+ }
+}
+
+module "lambda" {
+ source = "terraform-aws-modules/lambda/aws"
+ version = "~> 6.0"
+
+ function_name = local.name
+ handler = "lambda_function.lambda_handler"
+ runtime = "python3.10"
+ source_path = "${path.module}/fixtures/lambda_function.py"
+}
diff --git a/examples/role-association-postgres/outputs.tf b/examples/role-association-postgres/outputs.tf
new file mode 100644
index 00000000..3021e301
--- /dev/null
+++ b/examples/role-association-postgres/outputs.tf
@@ -0,0 +1,105 @@
+output "db_instance_address" {
+ description = "The address of the RDS instance"
+ value = module.db.db_instance_address
+}
+
+output "db_instance_arn" {
+ description = "The ARN of the RDS instance"
+ value = module.db.db_instance_arn
+}
+
+output "db_instance_availability_zone" {
+ description = "The availability zone of the RDS instance"
+ value = module.db.db_instance_availability_zone
+}
+
+output "db_instance_endpoint" {
+ description = "The connection endpoint"
+ value = module.db.db_instance_endpoint
+}
+
+output "db_instance_engine" {
+ description = "The database engine"
+ value = module.db.db_instance_engine
+}
+
+output "db_instance_engine_version_actual" {
+ description = "The running version of the database"
+ value = module.db.db_instance_engine_version_actual
+}
+
+output "db_instance_hosted_zone_id" {
+ description = "The canonical hosted zone ID of the DB instance (to be used in a Route 53 Alias record)"
+ value = module.db.db_instance_hosted_zone_id
+}
+
+output "db_instance_identifier" {
+ description = "The RDS instance identifier"
+ value = module.db.db_instance_identifier
+}
+
+output "db_instance_resource_id" {
+ description = "The RDS Resource ID of this instance"
+ value = module.db.db_instance_resource_id
+}
+
+output "db_instance_status" {
+ description = "The RDS instance status"
+ value = module.db.db_instance_status
+}
+
+output "db_instance_name" {
+ description = "The database name"
+ value = module.db.db_instance_name
+}
+
+output "db_instance_username" {
+ description = "The master username for the database"
+ value = module.db.db_instance_username
+ sensitive = true
+}
+
+output "db_instance_port" {
+ description = "The database port"
+ value = module.db.db_instance_port
+}
+
+output "db_subnet_group_id" {
+ description = "The db subnet group name"
+ value = module.db.db_subnet_group_id
+}
+
+output "db_subnet_group_arn" {
+ description = "The ARN of the db subnet group"
+ value = module.db.db_subnet_group_arn
+}
+
+output "db_parameter_group_id" {
+ description = "The db parameter group id"
+ value = module.db.db_parameter_group_id
+}
+
+output "db_parameter_group_arn" {
+ description = "The ARN of the db parameter group"
+ value = module.db.db_parameter_group_arn
+}
+
+output "db_enhanced_monitoring_iam_role_arn" {
+ description = "The Amazon Resource Name (ARN) specifying the monitoring role"
+ value = module.db.enhanced_monitoring_iam_role_arn
+}
+
+output "db_instance_cloudwatch_log_groups" {
+ description = "Map of CloudWatch log groups created and their attributes"
+ value = module.db.db_instance_cloudwatch_log_groups
+}
+
+output "db_instance_master_user_secret_arn" {
+ description = "The ARN of the master user secret (Only available when manage_master_user_password is set to true)"
+ value = module.db.db_instance_master_user_secret_arn
+}
+
+output "db_instance_role_associations" {
+ description = "The outputs for the role associations"
+ value = module.db.db_instance_role_associations
+}
diff --git a/examples/role-association-postgres/variables.tf b/examples/role-association-postgres/variables.tf
new file mode 100644
index 00000000..e69de29b
diff --git a/examples/role-association-postgres/versions.tf b/examples/role-association-postgres/versions.tf
new file mode 100644
index 00000000..ddfcb0e0
--- /dev/null
+++ b/examples/role-association-postgres/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.0"
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 5.0"
+ }
+ }
+}
diff --git a/main.tf b/main.tf
index d3662668..522d6315 100644
--- a/main.tf
+++ b/main.tf
@@ -143,3 +143,13 @@ module "db_instance" {
tags = merge(var.tags, var.db_instance_tags)
}
+
+module "db_instance_role_association" {
+ source = "./modules/db_instance_role_association"
+
+ for_each = { for k, v in var.db_instance_role_associations : k => v if var.create_db_instance }
+
+ feature_name = each.key
+ role_arn = each.value
+ db_instance_identifier = module.db_instance.db_instance_identifier
+}
diff --git a/modules/db_instance_role_association/README.md b/modules/db_instance_role_association/README.md
new file mode 100644
index 00000000..9f428d8e
--- /dev/null
+++ b/modules/db_instance_role_association/README.md
@@ -0,0 +1,41 @@
+# aws_db_instance_role_association
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | >= 1.0 |
+| [aws](#requirement\_aws) | >= 5.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | >= 5.0 |
+
+## Modules
+
+No modules.
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_db_instance_role_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance_role_association) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [create](#input\_create) | Determines whether to create a DB instance role association | `bool` | `true` | no |
+| [db\_instance\_identifier](#input\_db\_instance\_identifier) | The database instance identifier to associate the role | `string` | `null` | no |
+| [feature\_name](#input\_feature\_name) | Name of the feature for association | `string` | `null` | no |
+| [role\_arn](#input\_role\_arn) | Amazon Resource Name (ARN) of the IAM Role to associate with the DB Instance | `string` | `null` | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [db\_instance\_role\_association\_id](#output\_db\_instance\_role\_association\_id) | DB Instance Identifier and IAM Role ARN separated by a comma |
+
diff --git a/modules/db_instance_role_association/main.tf b/modules/db_instance_role_association/main.tf
new file mode 100644
index 00000000..0a11a4c2
--- /dev/null
+++ b/modules/db_instance_role_association/main.tf
@@ -0,0 +1,7 @@
+resource "aws_db_instance_role_association" "this" {
+ count = var.create ? 1 : 0
+
+ db_instance_identifier = var.db_instance_identifier
+ feature_name = var.feature_name
+ role_arn = var.role_arn
+}
diff --git a/modules/db_instance_role_association/outputs.tf b/modules/db_instance_role_association/outputs.tf
new file mode 100644
index 00000000..9152a0c0
--- /dev/null
+++ b/modules/db_instance_role_association/outputs.tf
@@ -0,0 +1,4 @@
+output "db_instance_role_association_id" {
+ description = "DB Instance Identifier and IAM Role ARN separated by a comma"
+ value = try(aws_db_instance_role_association.this[0].id, "")
+}
diff --git a/modules/db_instance_role_association/variables.tf b/modules/db_instance_role_association/variables.tf
new file mode 100644
index 00000000..d548d7fd
--- /dev/null
+++ b/modules/db_instance_role_association/variables.tf
@@ -0,0 +1,23 @@
+variable "create" {
+ description = "Determines whether to create a DB instance role association"
+ type = bool
+ default = true
+}
+
+variable "feature_name" {
+ description = "Name of the feature for association"
+ type = string
+ default = null
+}
+
+variable "role_arn" {
+ description = "Amazon Resource Name (ARN) of the IAM Role to associate with the DB Instance"
+ type = string
+ default = null
+}
+
+variable "db_instance_identifier" {
+ description = "The database instance identifier to associate the role"
+ type = string
+ default = null
+}
diff --git a/modules/db_instance_role_association/versions.tf b/modules/db_instance_role_association/versions.tf
new file mode 100644
index 00000000..ddfcb0e0
--- /dev/null
+++ b/modules/db_instance_role_association/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.0"
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 5.0"
+ }
+ }
+}
diff --git a/outputs.tf b/outputs.tf
index f9c771ec..cb0ee1cb 100644
--- a/outputs.tf
+++ b/outputs.tf
@@ -138,3 +138,12 @@ output "db_instance_cloudwatch_log_groups" {
description = "Map of CloudWatch log groups created and their attributes"
value = module.db_instance.db_instance_cloudwatch_log_groups
}
+
+################################################################################
+# DB Instance Role Association
+################################################################################
+
+output "db_instance_role_associations" {
+ description = "A map of DB Instance Identifiers and IAM Role ARNs separated by a comma"
+ value = module.db_instance_role_association
+}
diff --git a/variables.tf b/variables.tf
index c882721c..db9375da 100644
--- a/variables.tf
+++ b/variables.tf
@@ -544,3 +544,13 @@ variable "putin_khuylo" {
type = bool
default = true
}
+
+################################################################################
+# DB Instance Role Association
+################################################################################
+
+variable "db_instance_role_associations" {
+ description = "A map of DB instance supported feature name to role association ARNs."
+ type = map(any)
+ default = {}
+}