Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.96.1
rev: v1.99.5
hooks:
- id: terraform_fmt
- id: terraform_wrapper_module_for_each
Expand Down
26 changes: 12 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,14 @@ Examples codified under the [`examples`](https://github.com/terraform-aws-module

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 5.0 |
| <a name="requirement_random"></a> [random](#requirement\_random) | >= 3.0 |
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.11 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 6.0 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 5.0 |
| <a name="provider_random"></a> [random](#provider\_random) | >= 3.0 |
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 6.0 |

## Modules

Expand All @@ -147,7 +145,6 @@ No modules.
| [aws_secretsmanager_secret_rotation.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_rotation) | resource |
| [aws_secretsmanager_secret_version.ignore_changes](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource |
| [aws_secretsmanager_secret_version.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource |
| [random_password.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource |
| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |

## Inputs
Expand All @@ -157,7 +154,6 @@ No modules.
| <a name="input_block_public_policy"></a> [block\_public\_policy](#input\_block\_public\_policy) | Makes an optional API call to Zelkova to validate the Resource Policy to prevent broad access to your secret | `bool` | `null` | no |
| <a name="input_create"></a> [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no |
| <a name="input_create_policy"></a> [create\_policy](#input\_create\_policy) | Determines whether a policy will be created | `bool` | `false` | no |
| <a name="input_create_random_password"></a> [create\_random\_password](#input\_create\_random\_password) | Determines whether a random password will be generated | `bool` | `false` | no |
| <a name="input_description"></a> [description](#input\_description) | A description of the secret | `string` | `null` | no |
| <a name="input_enable_rotation"></a> [enable\_rotation](#input\_enable\_rotation) | Determines whether secret rotation is enabled | `bool` | `false` | no |
| <a name="input_force_overwrite_replica_secret"></a> [force\_overwrite\_replica\_secret](#input\_force\_overwrite\_replica\_secret) | Accepts boolean value to specify whether to overwrite a secret with the same name in the destination Region | `bool` | `null` | no |
Expand All @@ -166,15 +162,17 @@ No modules.
| <a name="input_name"></a> [name](#input\_name) | Friendly name of the new secret. The secret name can consist of uppercase letters, lowercase letters, digits, and any of the following characters: `/_+=.@-` | `string` | `null` | no |
| <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | Creates a unique name beginning with the specified prefix | `string` | `null` | no |
| <a name="input_override_policy_documents"></a> [override\_policy\_documents](#input\_override\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid` | `list(string)` | `[]` | no |
| <a name="input_policy_statements"></a> [policy\_statements](#input\_policy\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `map(any)` | `{}` | no |
| <a name="input_random_password_length"></a> [random\_password\_length](#input\_random\_password\_length) | The length of the generated random password | `number` | `32` | no |
| <a name="input_random_password_override_special"></a> [random\_password\_override\_special](#input\_random\_password\_override\_special) | Supply your own list of special characters to use for string generation. This overrides the default character list in the special argument | `string` | `"!@#$%&*()-_=+[]{}<>:?"` | no |
| <a name="input_policy_statements"></a> [policy\_statements](#input\_policy\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | <pre>list(object({<br/> sid = optional(string)<br/> actions = optional(list(string))<br/> not_actions = optional(list(string))<br/> effect = optional(string)<br/> resources = optional(list(string))<br/> not_resources = optional(list(string))<br/> principals = optional(list(object({<br/> type = string<br/> identifiers = list(string)<br/> })))<br/> not_principals = optional(list(object({<br/> type = string<br/> identifiers = list(string)<br/> })))<br/> condition = optional(list(object({<br/> test = string<br/> values = list(string)<br/> variable = string<br/> })))<br/> }))</pre> | `null` | no |
| <a name="input_recovery_window_in_days"></a> [recovery\_window\_in\_days](#input\_recovery\_window\_in\_days) | Number of days that AWS Secrets Manager waits before it can delete the secret. This value can be `0` to force deletion without recovery or range from `7` to `30` days. The default value is `30` | `number` | `null` | no |
| <a name="input_replica"></a> [replica](#input\_replica) | Configuration block to support secret replication | `map(any)` | `{}` | no |
| <a name="input_region"></a> [region](#input\_region) | Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration | `string` | `null` | no |
| <a name="input_replica"></a> [replica](#input\_replica) | Configuration block to support secret replication | <pre>map(object({<br/> kms_key_id = optional(string)<br/> region = optional(string) # will default to the key name<br/> }))</pre> | `null` | no |
| <a name="input_rotate_immediately"></a> [rotate\_immediately](#input\_rotate\_immediately) | Specifies whether to rotate the secret immediately or wait until the next scheduled rotation window. The rotation schedule is defined in `rotation_rules` | `bool` | `null` | no |
| <a name="input_rotation_lambda_arn"></a> [rotation\_lambda\_arn](#input\_rotation\_lambda\_arn) | Specifies the ARN of the Lambda function that can rotate the secret | `string` | `""` | no |
| <a name="input_rotation_rules"></a> [rotation\_rules](#input\_rotation\_rules) | A structure that defines the rotation configuration for this secret | `map(any)` | `{}` | no |
| <a name="input_secret_binary"></a> [secret\_binary](#input\_secret\_binary) | Specifies binary data that you want to encrypt and store in this version of the secret. This is required if `secret_string` is not set. Needs to be encoded to base64 | `string` | `null` | no |
| <a name="input_secret_string"></a> [secret\_string](#input\_secret\_string) | Specifies text data that you want to encrypt and store in this version of the secret. This is required if `secret_binary` is not set | `string` | `null` | no |
| <a name="input_rotation_rules"></a> [rotation\_rules](#input\_rotation\_rules) | A structure that defines the rotation configuration for this secret | <pre>object({<br/> automatically_after_days = optional(number)<br/> duration = optional(string)<br/> schedule_expression = optional(string)<br/> })</pre> | `null` | no |
| <a name="input_secret_binary"></a> [secret\_binary](#input\_secret\_binary) | Specifies binary data that you want to encrypt and store in this version of the secret. This is required if `secret_string` or `secret_string_wo` is not set. Needs to be encoded to base64 | `string` | `null` | no |
| <a name="input_secret_string"></a> [secret\_string](#input\_secret\_string) | Specifies text data that you want to encrypt and store in this version of the secret. This is required if `secret_binary` or `secret_string_wo` is not set | `string` | `null` | no |
| <a name="input_secret_string_wo"></a> [secret\_string\_wo](#input\_secret\_string\_wo) | Specifies text data that you want to encrypt and store in this version of the secret. This is required if `secret_binary` or `secret_string` is not set | `string` | `null` | no |
| <a name="input_secret_string_wo_version"></a> [secret\_string\_wo\_version](#input\_secret\_string\_wo\_version) | Used together with `secret_string_wo` to trigger an update. Increment this value when an update to `secret_string_wo` is required | `string` | `null` | no |
| <a name="input_source_policy_documents"></a> [source\_policy\_documents](#input\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s | `list(string)` | `[]` | no |
| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no |
| <a name="input_version_stages"></a> [version\_stages](#input\_version\_stages) | Specifies a list of staging labels that are attached to this version of the secret. A staging label must be unique to a single version of the secret | `list(string)` | `null` | no |
Expand Down
9 changes: 5 additions & 4 deletions examples/complete/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,21 @@ Note that this example may create resources which will incur monetary charges on

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 5.0 |
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.11 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 6.0 |
| <a name="requirement_random"></a> [random](#requirement\_random) | >= 3.7 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 5.0 |
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 6.0 |

## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_lambda"></a> [lambda](#module\_lambda) | terraform-aws-modules/lambda/aws | ~> 6.0 |
| <a name="module_lambda"></a> [lambda](#module\_lambda) | terraform-aws-modules/lambda/aws | ~> 8.0 |
| <a name="module_secrets_manager"></a> [secrets\_manager](#module\_secrets\_manager) | ../.. | n/a |
| <a name="module_secrets_manager_disabled"></a> [secrets\_manager\_disabled](#module\_secrets\_manager\_disabled) | ../.. | n/a |
| <a name="module_secrets_manager_rotate"></a> [secrets\_manager\_rotate](#module\_secrets\_manager\_rotate) | ../.. | n/a |
Expand Down
33 changes: 19 additions & 14 deletions examples/complete/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ module "secrets_manager" {
# Policy
create_policy = true
block_public_policy = true
policy_statements = {
read = {
policy_statements = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the reason for the change from map to list? I like maps, because keys there are like comments. Also, *_statements are maps in other modules, I guess.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

crap, looks like I've mixed up and some are maps some are lists in the other updated modules - I'll move this back to a map

Copy link
Member

@bryantbiggs bryantbiggs Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in f5ac140

{
sid = "AllowAccountRead"
principals = [{
type = "AWS"
Expand All @@ -48,12 +48,11 @@ module "secrets_manager" {
actions = ["secretsmanager:GetSecretValue"]
resources = ["*"]
}
}
]

# Version
create_random_password = true
random_password_length = 64
random_password_override_special = "!@#$%^&*()_+"
secret_string_wo = ephemeral.random_password.password.result
secret_string_wo_version = 1

tags = local.tags
}
Expand All @@ -69,8 +68,8 @@ module "secrets_manager_rotate" {
# Policy
create_policy = true
block_public_policy = true
policy_statements = {
lambda = {
policy_statements = [
{
sid = "LambdaReadWrite"
principals = [{
type = "AWS"
Expand All @@ -83,8 +82,8 @@ module "secrets_manager_rotate" {
"secretsmanager:UpdateSecretVersionStage",
]
resources = ["*"]
}
account = {
},
{
sid = "AccountDescribe"
principals = [{
type = "AWS"
Expand All @@ -93,15 +92,15 @@ module "secrets_manager_rotate" {
actions = ["secretsmanager:DescribeSecret"]
resources = ["*"]
}
}
]

# Version
ignore_secret_changes = true
secret_string = jsonencode({
engine = "mariadb",
host = "mydb.cluster-123456789012.us-east-1.rds.amazonaws.com",
username = "Bill",
password = "ThisIsMySuperSecretString12356!"
password = "ThisIsMySuperSecretString12356!",
dbname = "mydb",
port = 3306
})
Expand All @@ -127,6 +126,12 @@ module "secrets_manager_disabled" {
# Supporting Resources
################################################################################

ephemeral "random_password" "password" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ephemeral outside of the module is fine, but I think we should have ephemeral within the module as well. This way, users can just call the module without any external resources/ephemerals.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like OpenTofu doesn't support ephemeral variables yet, so if we add ephemeral variables into the module, it will break it for OpenTofu users.

https://opentofu.org/docs/language/values/variables/#arguments

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re-added using ephemeral resource 9682e45

length = 16
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
}

# https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-required-permissions-function.html
data "aws_iam_policy_document" "this" {
statement {
Expand All @@ -152,13 +157,13 @@ data "aws_iam_policy_document" "this" {

module "lambda" {
source = "terraform-aws-modules/lambda/aws"
version = "~> 6.0"
version = "~> 8.0"

function_name = local.name
description = "Example Secrets Manager secret rotation lambda function"

handler = "function.lambda_handler"
runtime = "python3.10"
runtime = "python3.12"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for being lazy for another more year, let's put it to python3.13 :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 9682e45

timeout = 60
memory_size = 512
source_path = "${path.module}/function.py"
Expand Down
8 changes: 6 additions & 2 deletions examples/complete/versions.tf
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
terraform {
required_version = ">= 1.0"
required_version = ">= 1.11"

required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
version = ">= 6.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.7"
}
}
}
77 changes: 42 additions & 35 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
resource "aws_secretsmanager_secret" "this" {
count = var.create ? 1 : 0

region = var.region

description = var.description
force_overwrite_replica_secret = var.force_overwrite_replica_secret
kms_key_id = var.kms_key_id
Expand All @@ -14,11 +16,11 @@ resource "aws_secretsmanager_secret" "this" {
recovery_window_in_days = var.recovery_window_in_days

dynamic "replica" {
for_each = var.replica
for_each = var.replica != null ? var.replica : {}

content {
kms_key_id = try(replica.value.kms_key_id, null)
region = try(replica.value.region, replica.key)
kms_key_id = replica.value.kms_key_id
region = coalesce(replica.value.region, replica.key)
}
}

Expand All @@ -36,18 +38,18 @@ data "aws_iam_policy_document" "this" {
override_policy_documents = var.override_policy_documents

dynamic "statement" {
for_each = var.policy_statements
for_each = var.policy_statements != null ? var.policy_statements : []

content {
sid = try(statement.value.sid, null)
actions = try(statement.value.actions, null)
not_actions = try(statement.value.not_actions, null)
effect = try(statement.value.effect, null)
resources = try(statement.value.resources, null)
not_resources = try(statement.value.not_resources, null)
sid = statement.value.sid
actions = statement.value.actions
not_actions = statement.value.not_actions
effect = statement.value.effect
resources = statement.value.resources
not_resources = statement.value.not_resources

dynamic "principals" {
for_each = try(statement.value.principals, [])
for_each = statement.value.principals != null ? statement.value.principals : []

content {
type = principals.value.type
Expand All @@ -56,7 +58,7 @@ data "aws_iam_policy_document" "this" {
}

dynamic "not_principals" {
for_each = try(statement.value.not_principals, [])
for_each = statement.value.not_principals != null ? statement.value.not_principals : []

content {
type = not_principals.value.type
Expand All @@ -65,7 +67,7 @@ data "aws_iam_policy_document" "this" {
}

dynamic "condition" {
for_each = try(statement.value.conditions, [])
for_each = statement.value.condition != null ? statement.value.condition : []

content {
test = condition.value.test
Expand All @@ -80,9 +82,11 @@ data "aws_iam_policy_document" "this" {
resource "aws_secretsmanager_secret_policy" "this" {
count = var.create && var.create_policy ? 1 : 0

secret_arn = aws_secretsmanager_secret.this[0].arn
policy = data.aws_iam_policy_document.this[0].json
region = var.region

block_public_policy = var.block_public_policy
policy = data.aws_iam_policy_document.this[0].json
secret_arn = aws_secretsmanager_secret.this[0].arn
}

################################################################################
Expand All @@ -92,19 +96,27 @@ resource "aws_secretsmanager_secret_policy" "this" {
resource "aws_secretsmanager_secret_version" "this" {
count = var.create && !(var.enable_rotation || var.ignore_secret_changes) ? 1 : 0

secret_id = aws_secretsmanager_secret.this[0].id
secret_string = var.create_random_password ? random_password.this[0].result : var.secret_string
secret_binary = var.secret_binary
version_stages = var.version_stages
region = var.region

secret_id = aws_secretsmanager_secret.this[0].id
secret_binary = var.secret_binary
secret_string = var.secret_string
secret_string_wo = var.secret_string_wo
secret_string_wo_version = var.secret_string_wo_version
version_stages = var.version_stages
}

resource "aws_secretsmanager_secret_version" "ignore_changes" {
count = var.create && (var.enable_rotation || var.ignore_secret_changes) ? 1 : 0

secret_id = aws_secretsmanager_secret.this[0].id
secret_string = var.create_random_password ? random_password.this[0].result : var.secret_string
secret_binary = var.secret_binary
version_stages = var.version_stages
region = var.region

secret_id = aws_secretsmanager_secret.this[0].id
secret_binary = var.secret_binary
secret_string = var.secret_string
secret_string_wo = var.secret_string_wo
secret_string_wo_version = var.secret_string_wo_version
version_stages = var.version_stages

lifecycle {
ignore_changes = [
Expand All @@ -115,30 +127,25 @@ resource "aws_secretsmanager_secret_version" "ignore_changes" {
}
}

resource "random_password" "this" {
count = var.create && var.create_random_password ? 1 : 0

length = var.random_password_length
special = true
override_special = var.random_password_override_special
}

################################################################################
# Rotation
################################################################################

resource "aws_secretsmanager_secret_rotation" "this" {
count = var.create && var.enable_rotation ? 1 : 0

region = var.region

rotate_immediately = var.rotate_immediately
rotation_lambda_arn = var.rotation_lambda_arn

dynamic "rotation_rules" {
for_each = [var.rotation_rules]
for_each = var.rotation_rules != null ? [var.rotation_rules] : []

content {
automatically_after_days = try(rotation_rules.value.automatically_after_days, null)
duration = try(rotation_rules.value.duration, null)
schedule_expression = try(rotation_rules.value.schedule_expression, null)
automatically_after_days = rotation_rules.value.automatically_after_days
duration = rotation_rules.value.duration
schedule_expression = rotation_rules.value.schedule_expression
}
}

Expand Down
Loading