diff --git a/.gitignore b/.gitignore
index 788c463..97336aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,3 +35,6 @@ override.tf.json
# Ignore CLI configuration files
.terraformrc
terraform.rc
+
+# Lambda
+builds/
diff --git a/README.md b/README.md
index 0762c9f..96dc6b6 100644
--- a/README.md
+++ b/README.md
@@ -2,26 +2,110 @@
Terraform module which creates AWS Secrets Manager resources.
+[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)
+
## Usage
-See [`examples`](https://github.com/clowdhaus/terraform-aws-secrets-manager/tree/main/examples) directory for working examples to reference:
+See [`examples`](https://github.com/terraform-aws-modules/terraform-aws-secrets-manager/tree/master/examples) directory for working examples to reference:
+
+### Standard
+
+```hcl
+module "secrets_manager" {
+ source = "terraform-aws-modules/secrets-manager/aws"
+
+ # Secret
+ name_prefix = "example"
+ description = "Example Secrets Manager secret"
+ recovery_window_in_days = 30
+
+ # Policy
+ create_policy = true
+ block_public_policy = true
+ policy_statements = {
+ read = {
+ sid = "AllowAccountRead"
+ principals = [{
+ type = "AWS"
+ identifiers = ["arn:aws:iam::1234567890:root"]
+ }]
+ actions = ["secretsmanager:GetSecretValue"]
+ resources = ["*"]
+ }
+ }
+
+ # Version
+ secret_string = "ThisIsMySuperSecretString12356!&*()"
+
+ tags = {
+ Environment = "Development"
+ Project = "Example"
+ }
+}
+```
+
+### w/ Rotation
```hcl
module "secrets_manager" {
- source = "clowdhaus/secrets-manager/aws"
+ source = "terraform-aws-modules/secrets-manager/aws"
+
+ # Secret
+ name_prefix = "rotated-example"
+ description = "Rotated example Secrets Manager secret"
+ recovery_window_in_days = 7
+
+ # Policy
+ create_policy = true
+ block_public_policy = true
+ policy_statements = {
+ read = {
+ sid = "LambdaReadWrite"
+ principals = [{
+ type = "AWS"
+ identifiers = ["arn:aws:lambda:us-east-1:123456789012:function:my-function"]
+ }]
+ actions = [
+ "secretsmanager:DescribeSecret",
+ "secretsmanager:GetSecretValue",
+ "secretsmanager:PutSecretValue",
+ "secretsmanager:UpdateSecretVersionStage",
+ ]
+ resources = ["*"]
+ }
+ }
+
+ # Version
+ ignore_secret_changes = true
+ secret_string = jsonencode({
+ engine = "mariadb",
+ host = "mydb.cluster-123456789012.us-east-1.rds.amazonaws.com",
+ username = "Bill",
+ password = "Initial"
+ dbname = "ThisIsMySuperSecretString12356!&*()",
+ port = 3306
+ })
+
+ # Rotation
+ enable_rotation = true
+ rotation_lambda_arn = "arn:aws:lambda:us-east-1:123456789012:function:my-function"
+ rotation_rules = {
+ # This should be more sensible in production
+ schedule_expression = "rate(1 minute)"
+ }
tags = {
- Terraform = "true"
- Environment = "dev"
+ Environment = "Development"
+ Project = "Example"
}
}
```
## Examples
-Examples codified under the [`examples`](https://github.com/clowdhaus/terraform-aws-secrets-manager/tree/main/examples) are intended to give users references for how to use the module(s) as well as testing/validating changes to the source code of the module. If contributing to the project, please be sure to make any appropriate updates to the relevant examples to allow maintainers to test your changes and to keep the examples up to date for users. Thank you!
+Examples codified under the [`examples`](https://github.com/terraform-aws-modules/terraform-aws-secrets-manager/tree/master/examples) are intended to give users references for how to use the module(s) as well as testing/validating changes to the source code of the module. If contributing to the project, please be sure to make any appropriate updates to the relevant examples to allow maintainers to test your changes and to keep the examples up to date for users. Thank you!
-- [Complete](https://github.com/clowdhaus/terraform-aws-secrets-manager/tree/main/examples/complete)
+- [Complete](https://github.com/terraform-aws-modules/terraform-aws-secrets-manager/tree/master/examples/complete)
## Requirements
@@ -29,11 +113,13 @@ Examples codified under the [`examples`](https://github.com/clowdhaus/terraform-
| Name | Version |
|------|---------|
| [terraform](#requirement\_terraform) | >= 1.0 |
-| [aws](#requirement\_aws) | >= 4.0 |
+| [aws](#requirement\_aws) | >= 5.0 |
## Providers
-No providers.
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | >= 5.0 |
## Modules
@@ -41,20 +127,51 @@ No modules.
## Resources
-No resources.
+| Name | Type |
+|------|------|
+| [aws_secretsmanager_secret.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource |
+| [aws_secretsmanager_secret_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_policy) | resource |
+| [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 |
+| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
+| [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 |
| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no |
+| [create\_policy](#input\_create\_policy) | Determines whether a policy will be created | `bool` | `false` | no |
+| [description](#input\_description) | A description of the secret | `string` | `null` | no |
+| [enable\_rotation](#input\_enable\_rotation) | Determines whether secret rotation is enabled | `bool` | `false` | no |
+| [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 |
+| [ignore\_secret\_changes](#input\_ignore\_secret\_changes) | Determines whether or not Terraform will ignore changes made externally to `secret_string` or `secret_binary`. Changing this value after creation is a destructive operation | `bool` | `false` | no |
+| [kms\_key\_id](#input\_kms\_key\_id) | ARN or Id of the AWS KMS key to be used to encrypt the secret values in the versions stored in this secret. If you need to reference a CMK in a different account, you can use only the key ARN. If you don't specify this value, then Secrets Manager defaults to using the AWS account's default KMS key (the one named `aws/secretsmanager` | `string` | `null` | no |
+| [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 |
+| [name\_prefix](#input\_name\_prefix) | Creates a unique name beginning with the specified prefix | `string` | `null` | no |
+| [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 |
+| [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 | `any` | `{}` | no |
+| [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 |
+| [replica](#input\_replica) | Configuration block to support secret replication | `any` | `{}` | no |
+| [rotation\_lambda\_arn](#input\_rotation\_lambda\_arn) | Specifies the ARN of the Lambda function that can rotate the secret | `string` | `""` | no |
+| [rotation\_rules](#input\_rotation\_rules) | A structure that defines the rotation configuration for this secret | `any` | `{}` | no |
+| [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 |
+| [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 |
+| [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 |
| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no |
+| [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 |
## Outputs
-No outputs.
+| Name | Description |
+|------|-------------|
+| [secret\_arn](#output\_secret\_arn) | The ARN of the secret |
+| [secret\_id](#output\_secret\_id) | The ID of the secret |
+| [secret\_replica](#output\_secret\_replica) | Attributes of the replica created |
+| [secret\_version\_id](#output\_secret\_version\_id) | The unique identifier of the version of the secret |
## License
-Apache-2.0 Licensed. See [LICENSE](https://github.com/clowdhaus/terraform-aws-secrets-manager/blob/main/LICENSE).
+Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-secrets-manager/blob/master/LICENSE).
diff --git a/examples/complete/README.md b/examples/complete/README.md
index bc99ab4..fef48e4 100644
--- a/examples/complete/README.md
+++ b/examples/complete/README.md
@@ -2,7 +2,8 @@
Configuration in this directory creates:
--
+- Standard Secrets Manager Secret
+- Secrets Manager Secret with rotation enabled
## Usage
@@ -14,6 +15,12 @@ $ terraform plan
$ terraform apply
```
+If you wish to test the rotated secret, after provisioning the resources you can go into the console and under the rotated secret click `Rotate secret immediately`. This will trigger the lambda function to rotate the secret. You can then go to the `Secret value` tab and click `Retrieve secret value` to see the new secret value.
+
+After rotating the secret, you can run `terraform plan` and see that there are no detected changes.
+
+:warning: Replicated secrets are not cleaned up by Terraform. You will need to manually delete these secrets. Ref: https://github.com/hashicorp/terraform-provider-aws/issues/23316
+
Note that this example may create resources which will incur monetary charges on your AWS bill. Run `terraform destroy` when you no longer need these resources.
@@ -26,18 +33,25 @@ Note that this example may create resources which will incur monetary charges on
## Providers
-No providers.
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | >= 5.0 |
## Modules
| Name | Source | Version |
|------|--------|---------|
+| [lambda](#module\_lambda) | terraform-aws-modules/lambda/aws | ~> 5.0 |
| [secrets\_manager](#module\_secrets\_manager) | ../.. | n/a |
| [secrets\_manager\_disabled](#module\_secrets\_manager\_disabled) | ../.. | n/a |
+| [secrets\_manager\_rotate](#module\_secrets\_manager\_rotate) | ../.. | n/a |
## Resources
-No resources.
+| Name | Type |
+|------|------|
+| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
+| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
## Inputs
@@ -45,7 +59,16 @@ No inputs.
## Outputs
-No outputs.
+| Name | Description |
+|------|-------------|
+| [rotate\_secret\_arn](#output\_rotate\_secret\_arn) | The ARN of the secret |
+| [rotate\_secret\_id](#output\_rotate\_secret\_id) | The ID of the secret |
+| [rotate\_secret\_replica](#output\_rotate\_secret\_replica) | Attributes of the replica created |
+| [rotate\_secret\_version\_id](#output\_rotate\_secret\_version\_id) | The unique identifier of the version of the secret |
+| [standard\_secret\_arn](#output\_standard\_secret\_arn) | The ARN of the secret |
+| [standard\_secret\_id](#output\_standard\_secret\_id) | The ID of the secret |
+| [standard\_secret\_replica](#output\_standard\_secret\_replica) | Attributes of the replica created |
+| [standard\_secret\_version\_id](#output\_standard\_secret\_version\_id) | The unique identifier of the version of the secret |
-Apache-2.0 Licensed. See [LICENSE](https://github.com/clowdhaus/terraform-aws-secrets-manager/blob/main/LICENSE).
+Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-secrets-manager/blob/master/LICENSE).
diff --git a/examples/complete/function.py b/examples/complete/function.py
new file mode 100644
index 0000000..562bef0
--- /dev/null
+++ b/examples/complete/function.py
@@ -0,0 +1,158 @@
+# Credit: https://github.com/aws-samples/aws-secrets-manager-rotation-lambdas/blob/master/SecretsManagerRDSMariaDBRotationSingleUser/lambda_function.py
+
+import boto3
+import json
+import logging
+import os
+
+logger = logging.getLogger()
+logger.setLevel(logging.INFO)
+
+CLIENT = boto3.client('secretsmanager')
+
+def lambda_handler(event, context):
+ """Secrets Manager RDS MariaDB Handler
+
+ This handler uses the single-user rotation scheme to rotate an RDS MariaDB user credential. This rotation scheme
+ logs into the database as the user and rotates the user's own password, immediately invalidating the user's
+ previous password.
+
+ The Secret SecretString is expected to be a JSON string with the following format:
+ {
+ 'engine': ,
+ 'host': ,
+ 'username': ,
+ 'password': ,
+ 'dbname': ,
+ 'port':
+ }
+ """
+ arn = event['SecretId']
+ token = event['ClientRequestToken']
+ step = event['Step']
+
+ # Make sure the version is staged correctly
+ metadata = CLIENT.describe_secret(SecretId=arn)
+ if "RotationEnabled" in metadata and not metadata['RotationEnabled']:
+ logger.error("Secret %s is not enabled for rotation" % arn)
+ raise ValueError("Secret %s is not enabled for rotation" % arn)
+ versions = metadata['VersionIdsToStages']
+ if token not in versions:
+ logger.error("Secret version %s has no stage for rotation of secret %s." % (token, arn))
+ raise ValueError("Secret version %s has no stage for rotation of secret %s." % (token, arn))
+ if "AWSCURRENT" in versions[token]:
+ logger.info("Secret version %s already set as AWSCURRENT for secret %s." % (token, arn))
+ return
+ elif "AWSPENDING" not in versions[token]:
+ logger.error("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn))
+ raise ValueError("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn))
+
+ # Call the appropriate step
+ if step == "createSecret":
+ create_secret(CLIENT, arn, token)
+
+ elif step == "setSecret":
+ # Just an example, not setting secret
+ pass
+
+ elif step == "testSecret":
+ # Just an example, not testing secret
+ pass
+
+ elif step == "finishSecret":
+ finish_secret(CLIENT, arn, token)
+
+ else:
+ logger.error("lambda_handler: Invalid step parameter %s for secret %s" % (step, arn))
+ raise ValueError("Invalid step parameter %s for secret %s" % (step, arn))
+
+
+def create_secret(service_client, arn, token):
+ """Generate a new secret
+
+ This method first checks for the existence of a secret for the passed in token. If one does not exist, it will generate a
+ new secret and put it with the passed in token.
+
+ Args:
+ service_client (client): The secrets manager service client
+ arn (string): The secret ARN or other identifier
+ token (string): The ClientRequestToken associated with the secret version
+ """
+ # Make sure the current secret exists
+ current_dict = get_secret_dict(service_client, arn, "AWSCURRENT")
+
+ # Now try to get the secret version, if that fails, put a new secret
+ try:
+ get_secret_dict(service_client, arn, "AWSPENDING", token)
+ logger.info("createSecret: Successfully retrieved secret for %s." % arn)
+ except service_client.exceptions.ResourceNotFoundException:
+ # Get exclude characters from environment variable
+ exclude_characters = os.environ.get('EXCLUDE_CHARACTERS', '/@"\'\\')
+ # Generate a random password
+ passwd = service_client.get_random_password(ExcludeCharacters=exclude_characters)
+ current_dict['password'] = passwd['RandomPassword']
+
+ # Put the secret
+ service_client.put_secret_value(SecretId=arn, ClientRequestToken=token, SecretString=json.dumps(current_dict), VersionStages=['AWSPENDING'])
+ logger.info("createSecret: Successfully put secret for ARN %s and version %s." % (arn, token))
+
+
+def finish_secret(service_client, arn, token):
+ """Finish the rotation by marking the pending secret as current
+
+ This method finishes the secret rotation by staging the secret staged AWSPENDING with the AWSCURRENT stage.
+
+ Args:
+ service_client (client): The secrets manager service client
+ arn (string): The secret ARN or other identifier
+ token (string): The ClientRequestToken associated with the secret version
+ """
+ # First describe the secret to get the current version
+ metadata = service_client.describe_secret(SecretId=arn)
+ current_version = None
+ for version in metadata["VersionIdsToStages"]:
+ if "AWSCURRENT" in metadata["VersionIdsToStages"][version]:
+ if version == token:
+ # The correct version is already marked as current, return
+ logger.info("finishSecret: Version %s already marked as AWSCURRENT for %s" % (version, arn))
+ return
+ current_version = version
+ break
+
+ # Finalize by staging the secret version current
+ service_client.update_secret_version_stage(SecretId=arn, VersionStage="AWSCURRENT", MoveToVersionId=token, RemoveFromVersionId=current_version)
+ logger.info("finishSecret: Successfully set AWSCURRENT stage to version %s for secret %s." % (token, arn))
+
+
+def get_secret_dict(service_client, arn, stage, token=None):
+ """Gets the secret dictionary corresponding for the secret arn, stage, and token
+
+ This helper function gets credentials for the arn and stage passed in and returns the dictionary by parsing the JSON string
+
+ Args:
+ service_client (client): The secrets manager service client
+ arn (string): The secret ARN or other identifier
+ token (string): The ClientRequestToken associated with the secret version, or None if no validation is desired
+ stage (string): The stage identifying the secret version
+ Returns:
+ SecretDictionary: Secret dictionary
+ """
+ required_fields = ['host', 'username', 'password']
+
+ # Only do VersionId validation against the stage if a token is passed in
+ if token:
+ secret = service_client.get_secret_value(SecretId=arn, VersionId=token, VersionStage=stage)
+ else:
+ secret = service_client.get_secret_value(SecretId=arn, VersionStage=stage)
+ plaintext = secret['SecretString']
+ secret_dict = json.loads(plaintext)
+
+ # Run validations against the secret
+ if 'engine' not in secret_dict or secret_dict['engine'] != 'mariadb':
+ raise KeyError("Database engine must be set to 'mariadb' in order to use this rotation lambda")
+ for field in required_fields:
+ if field not in secret_dict:
+ raise KeyError("%s key is missing from secret JSON" % field)
+
+ # Parse and return the secret JSON string
+ return secret_dict
diff --git a/examples/complete/main.tf b/examples/complete/main.tf
index 7a5e859..5367caa 100644
--- a/examples/complete/main.tf
+++ b/examples/complete/main.tf
@@ -2,25 +2,115 @@ provider "aws" {
region = local.region
}
+data "aws_caller_identity" "current" {}
+
locals {
- region = "us-east-1"
+ region = "eu-west-1"
name = "secrets-manager-ex-${basename(path.cwd)}"
tags = {
Name = local.name
Example = local.name
- Repository = "https://github.com/clowdhaus/terraform-aws-secrets-manager"
+ Repository = "https://github.com/terraform-aws-modules/terraform-aws-secrets-manager"
}
}
################################################################################
-# secrets manager Module
+# Secrets Manager
################################################################################
module "secrets_manager" {
source = "../.."
- create = false
+ # Secret
+ name_prefix = local.name
+ description = "Example Secrets Manager secret"
+ recovery_window_in_days = 0
+ replica = {
+ # Can set region as key
+ us-east-1 = {}
+ another = {
+ # Or as attribute
+ region = "us-west-2"
+ }
+ }
+
+ # Policy
+ create_policy = true
+ block_public_policy = true
+ policy_statements = {
+ read = {
+ sid = "AllowAccountRead"
+ principals = [{
+ type = "AWS"
+ identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
+ }]
+ actions = ["secretsmanager:GetSecretValue"]
+ resources = ["*"]
+ }
+ }
+
+ # Version
+ secret_string = "ThisIsMySuperSecretString12356!&*()"
+
+ tags = local.tags
+}
+
+module "secrets_manager_rotate" {
+ source = "../.."
+
+ # Secret
+ name_prefix = local.name
+ description = "Rotated example Secrets Manager secret"
+ recovery_window_in_days = 0
+
+ # Policy
+ create_policy = true
+ block_public_policy = true
+ policy_statements = {
+ lambda = {
+ sid = "LambdaReadWrite"
+ principals = [{
+ type = "AWS"
+ identifiers = [module.lambda.lambda_role_arn]
+ }]
+ actions = [
+ "secretsmanager:DescribeSecret",
+ "secretsmanager:GetSecretValue",
+ "secretsmanager:PutSecretValue",
+ "secretsmanager:UpdateSecretVersionStage",
+ ]
+ resources = ["*"]
+ }
+ account = {
+ sid = "AccountDescribe"
+ principals = [{
+ type = "AWS"
+ identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
+ }]
+ 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!"
+ dbname = "mydb",
+ port = 3306
+ })
+
+ # Rotation
+ enable_rotation = true
+ rotation_lambda_arn = module.lambda.lambda_function_arn
+ rotation_rules = {
+ # This should be more sensible in production
+ schedule_expression = "rate(6 hours)"
+ }
tags = local.tags
}
@@ -30,3 +120,58 @@ module "secrets_manager_disabled" {
create = false
}
+
+################################################################################
+# Supporting Resources
+################################################################################
+
+# https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-required-permissions-function.html
+data "aws_iam_policy_document" "this" {
+ statement {
+ actions = [
+ "secretsmanager:DescribeSecret",
+ "secretsmanager:GetSecretValue",
+ "secretsmanager:PutSecretValue",
+ "secretsmanager:UpdateSecretVersionStage",
+ ]
+ resources = [module.secrets_manager.secret_arn]
+ }
+
+ statement {
+ actions = ["secretsmanager:GetRandomPassword"]
+ resources = ["*"]
+ }
+
+ statement {
+ actions = ["secretsmanager:GetRandomPassword"]
+ resources = ["*"]
+ }
+}
+
+module "lambda" {
+ source = "terraform-aws-modules/lambda/aws"
+ version = "~> 5.0"
+
+ function_name = local.name
+ description = "Example Secrets Manager secret rotation lambda function"
+
+ handler = "function.lambda_handler"
+ runtime = "python3.10"
+ timeout = 60
+ memory_size = 512
+ source_path = "${path.module}/function.py"
+
+ attach_policy_json = true
+ policy_json = data.aws_iam_policy_document.this.json
+
+ publish = true
+ allowed_triggers = {
+ secrets = {
+ principal = "secretsmanager.amazonaws.com"
+ }
+ }
+
+ cloudwatch_logs_retention_in_days = 7
+
+ tags = local.tags
+}
diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf
index e69de29..d140b79 100644
--- a/examples/complete/outputs.tf
+++ b/examples/complete/outputs.tf
@@ -0,0 +1,47 @@
+################################################################################
+# Standard
+################################################################################
+
+output "standard_secret_arn" {
+ description = "The ARN of the secret"
+ value = module.secrets_manager.secret_arn
+}
+
+output "standard_secret_id" {
+ description = "The ID of the secret"
+ value = module.secrets_manager.secret_id
+}
+
+output "standard_secret_replica" {
+ description = "Attributes of the replica created"
+ value = module.secrets_manager.secret_replica
+}
+
+output "standard_secret_version_id" {
+ description = "The unique identifier of the version of the secret"
+ value = module.secrets_manager.secret_version_id
+}
+
+################################################################################
+# Rotate
+################################################################################
+
+output "rotate_secret_arn" {
+ description = "The ARN of the secret"
+ value = module.secrets_manager_rotate.secret_arn
+}
+
+output "rotate_secret_id" {
+ description = "The ID of the secret"
+ value = module.secrets_manager_rotate.secret_id
+}
+
+output "rotate_secret_replica" {
+ description = "Attributes of the replica created"
+ value = module.secrets_manager_rotate.secret_replica
+}
+
+output "rotate_secret_version_id" {
+ description = "The unique identifier of the version of the secret"
+ value = module.secrets_manager_rotate.secret_version_id
+}
diff --git a/main.tf b/main.tf
index 066066c..9681aed 100644
--- a/main.tf
+++ b/main.tf
@@ -1 +1,138 @@
-locals {}
+
+################################################################################
+# Secret
+################################################################################
+
+resource "aws_secretsmanager_secret" "this" {
+ count = var.create ? 1 : 0
+
+ description = var.description
+ force_overwrite_replica_secret = var.force_overwrite_replica_secret
+ kms_key_id = var.kms_key_id
+ name = var.name
+ name_prefix = var.name_prefix
+ recovery_window_in_days = var.recovery_window_in_days
+
+ dynamic "replica" {
+ for_each = var.replica
+
+ content {
+ kms_key_id = try(replica.value.kms_key_id, null)
+ region = try(replica.value.region, replica.key)
+ }
+ }
+
+ tags = var.tags
+}
+
+################################################################################
+# Policy
+################################################################################
+
+data "aws_iam_policy_document" "this" {
+ count = var.create && var.create_policy ? 1 : 0
+
+ source_policy_documents = var.source_policy_documents
+ override_policy_documents = var.override_policy_documents
+
+ dynamic "statement" {
+ for_each = 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)
+
+ dynamic "principals" {
+ for_each = try(statement.value.principals, [])
+
+ content {
+ type = principals.value.type
+ identifiers = principals.value.identifiers
+ }
+ }
+
+ dynamic "not_principals" {
+ for_each = try(statement.value.not_principals, [])
+
+ content {
+ type = not_principals.value.type
+ identifiers = not_principals.value.identifiers
+ }
+ }
+
+ dynamic "condition" {
+ for_each = try(statement.value.conditions, [])
+
+ content {
+ test = condition.value.test
+ values = condition.value.values
+ variable = condition.value.variable
+ }
+ }
+ }
+ }
+}
+
+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
+ block_public_policy = var.block_public_policy
+}
+
+################################################################################
+# Version
+################################################################################
+
+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.secret_string
+ secret_binary = var.secret_binary
+ 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.secret_string
+ secret_binary = var.secret_binary
+ version_stages = var.version_stages
+
+ lifecycle {
+ ignore_changes = [
+ secret_string,
+ secret_binary,
+ version_stages,
+ ]
+ }
+}
+
+################################################################################
+# Rotation
+################################################################################
+
+resource "aws_secretsmanager_secret_rotation" "this" {
+ count = var.create && var.enable_rotation ? 1 : 0
+
+ rotation_lambda_arn = var.rotation_lambda_arn
+
+ dynamic "rotation_rules" {
+ for_each = [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)
+ }
+ }
+
+ secret_id = aws_secretsmanager_secret.this[0].id
+}
diff --git a/outputs.tf b/outputs.tf
index e69de29..6d04956 100644
--- a/outputs.tf
+++ b/outputs.tf
@@ -0,0 +1,27 @@
+################################################################################
+# Secret
+################################################################################
+
+output "secret_arn" {
+ description = "The ARN of the secret"
+ value = try(aws_secretsmanager_secret.this[0].arn, null)
+}
+
+output "secret_id" {
+ description = "The ID of the secret"
+ value = try(aws_secretsmanager_secret.this[0].id, null)
+}
+
+output "secret_replica" {
+ description = "Attributes of the replica created"
+ value = try(aws_secretsmanager_secret.this[0].replica, null)
+}
+
+################################################################################
+# Version
+################################################################################
+
+output "secret_version_id" {
+ description = "The unique identifier of the version of the secret"
+ value = try(aws_secretsmanager_secret_version.this[0].version_id, aws_secretsmanager_secret_version.ignore_changes[0].version_id, null)
+}
diff --git a/variables.tf b/variables.tf
index 8466739..27bbb53 100644
--- a/variables.tf
+++ b/variables.tf
@@ -9,3 +9,133 @@ variable "tags" {
type = map(string)
default = {}
}
+
+################################################################################
+# Secret
+################################################################################
+
+variable "description" {
+ description = "A description of the secret"
+ type = string
+ default = null
+}
+
+variable "force_overwrite_replica_secret" {
+ description = "Accepts boolean value to specify whether to overwrite a secret with the same name in the destination Region"
+ type = bool
+ default = null
+}
+
+variable "kms_key_id" {
+ description = "ARN or Id of the AWS KMS key to be used to encrypt the secret values in the versions stored in this secret. If you need to reference a CMK in a different account, you can use only the key ARN. If you don't specify this value, then Secrets Manager defaults to using the AWS account's default KMS key (the one named `aws/secretsmanager`"
+ type = string
+ default = null
+}
+
+variable "name" {
+ description = "Friendly name of the new secret. The secret name can consist of uppercase letters, lowercase letters, digits, and any of the following characters: `/_+=.@-`"
+ type = string
+ default = null
+}
+
+variable "name_prefix" {
+ description = "Creates a unique name beginning with the specified prefix"
+ type = string
+ default = null
+}
+
+variable "recovery_window_in_days" {
+ description = "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`"
+ type = number
+ default = null
+}
+
+variable "replica" {
+ description = "Configuration block to support secret replication"
+ type = any
+ default = {}
+}
+
+################################################################################
+# Policy
+################################################################################
+
+variable "create_policy" {
+ description = "Determines whether a policy will be created"
+ type = bool
+ default = false
+}
+
+variable "source_policy_documents" {
+ description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s"
+ type = list(string)
+ default = []
+}
+
+variable "override_policy_documents" {
+ description = "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`"
+ type = list(string)
+ default = []
+}
+
+variable "policy_statements" {
+ description = "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"
+ type = any
+ default = {}
+}
+
+variable "block_public_policy" {
+ description = "Makes an optional API call to Zelkova to validate the Resource Policy to prevent broad access to your secret"
+ type = bool
+ default = null
+}
+
+################################################################################
+# Version
+################################################################################
+
+variable "ignore_secret_changes" {
+ description = "Determines whether or not Terraform will ignore changes made externally to `secret_string` or `secret_binary`. Changing this value after creation is a destructive operation"
+ type = bool
+ default = false
+}
+
+variable "secret_string" {
+ description = "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"
+ type = string
+ default = null
+}
+
+variable "secret_binary" {
+ description = "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"
+ type = string
+ default = null
+}
+
+variable "version_stages" {
+ description = "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"
+ type = list(string)
+ default = null
+}
+
+################################################################################
+# Rotation
+################################################################################
+
+variable "enable_rotation" {
+ description = "Determines whether secret rotation is enabled"
+ type = bool
+ default = false
+}
+
+variable "rotation_lambda_arn" {
+ description = "Specifies the ARN of the Lambda function that can rotate the secret"
+ type = string
+ default = ""
+}
+
+variable "rotation_rules" {
+ description = "A structure that defines the rotation configuration for this secret"
+ type = any
+ default = {}
+}
diff --git a/versions.tf b/versions.tf
index d8dd1a4..ddfcb0e 100644
--- a/versions.tf
+++ b/versions.tf
@@ -4,7 +4,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
- version = ">= 4.0"
+ version = ">= 5.0"
}
}
}