In modern cloud-native architecture, managing sensitive data such as database credentials, API keys, and authentication tokens requires a robust, automated, and auditable approach. AWS Secrets Manager serves as the central repository for these assets, and Terraform provides the declarative framework to provision and manage the underlying infrastructure. However, a significant portion of infrastructure-as-code implementations fail to fully leverage the capabilities of AWS Secrets Manager because practitioners conflate the management of the secret's metadata with the management of the secret's version. This distinction is critical. The aws_secretsmanager_secret resource handles the container, the policy, and the lifecycle metadata, while the aws_secretsmanager_secret_version resource handles the actual payload, the versioning logic, and the specific value being protected. Understanding the mechanics of aws_secretsmanager_secret_version is essential for engineers who need to rotate credentials, enforce least-privilege access to specific versions, or automate the injection of dynamic values into cloud resources without hardcoding sensitive strings into configuration files.
The aws_secretsmanager_secret_version resource provides a mechanism to manage a specific version of a secret, including its secret value. It is distinct from the aws_secretsmanager_secret resource, which manages the secret metadata, such as the name, description, KMS key ID, and tags. To manage the secret value itself, one must explicitly define a version resource. This separation allows for a cleaner separation of concerns in Terraform code, enabling developers to update secret values without modifying the metadata or policy attached to the secret container. This article provides a deep technical analysis of the aws_secretsmanager_secret_version resource, covering its syntax, argument references, handling of the AWSCURRENT staging label, integration with data sources, and best practices for secure secret management in Terraform state.
Architectural Distinction Between Secret Metadata and Secret Versions
A fundamental concept in AWS Secrets Manager is that a secret is composed of multiple versions, each with a specific staging label. The most critical staging label is AWSCURRENT, which indicates the version that will be returned when a GetSecretValue API call is made without specifying a version stage. While the AWS Management Console and CLI commands often create a secret and its initial version in a single logical step, Terraform requires these to be handled as two distinct resources. This architectural difference is a common source of confusion and operational friction.
When a secret is created through the AWS Portal, the system automatically creates an initial version with the AWSCURRENT label. However, when a secret is created using the Terraform resource aws_secretsmanager_secret, the secret version is not created automatically. The aws_secretsmanager_secret resource only provisions the metadata container. To actually store a value in this container, a separate aws_secretsmanager_secret_version resource must be defined and associated with the secret via the secret_id argument. This explicit dependency ensures that Terraform's state file accurately reflects the relationship between the secret container and its contents. Without this explicit version resource, the secret remains empty, and any dependent resources that attempt to read the secret value will fail or retrieve nothing.
The following table outlines the primary differences between the metadata resource and the version resource, highlighting their respective roles in the Terraform configuration.
| Feature | aws_secretsmanager_secret |
aws_secretsmanager_secret_version |
|---|---|---|
| Primary Function | Manages secret metadata (name, policy, KMS key). | Manages the specific value and versioning of the secret. |
| Payload Storage | Does not store the secret value directly. | Stores the actual secret string or binary data. |
| Staging Labels | Does not manage staging labels directly. | Manages versions associated with staging labels like AWSCURRENT. |
| Creation Behavior | Creates an empty secret container. | Adds a new version to an existing secret container. |
| Dependency | Must exist before a version can be created. | Depends on the secret_id of the metadata resource. |
| Deletion Behavior | Deletes the secret and all associated versions. | Deprecates or deletes a specific version. |
This separation allows for granular control. For example, a team might wish to update the password for a database without touching the KMS policy or the tags applied to the secret. By isolating the version in its own resource, Terraform can track changes to the value independently from changes to the metadata. This is crucial for infrastructure code review, as changes to secret values should be treated with higher sensitivity than changes to metadata like descriptions or tags.
Argument Reference and Configuration Syntax
The aws_secretsmanager_secret_version resource supports a specific set of arguments that define the content and context of the secret version. The configuration is straightforward but requires strict adherence to the data types and reference mechanisms available in Terraform.
The following arguments are supported by the resource:
secret_id: (Required) Specifies the secret to which you want to add a new version. You can specify either the Amazon Resource Name (ARN) or the friendly name of the secret. The secret must already exist. This argument is the primary link between the version resource and the metadata resource. In most Terraform configurations, this will reference the ID attribute of anaws_secretsmanager_secretresource.secret_string: Specifies the secret value to be stored. This can be a simple string or a JSON-encoded object containing key-value pairs. This argument is mutually exclusive withsecret_binary.secret_binary: Specifies the secret value in binary format, base64-encoded. This is useful for storing non-UTF-8 data. This argument is mutually exclusive withsecret_string.
The secret_id argument is the most critical component of the configuration. It accepts both the ARN and the friendly name of the secret. However, using the ARN is generally recommended in complex environments where multiple secrets might have similar names or where names are not globally unique. The secret_string argument allows for dynamic content generation, which is a powerful feature for infrastructure-as-code. Instead of hardcoding a static string, engineers can use Terraform functions such as random_password, jsonencode, or data source lookups to generate or retrieve the value at deployment time.
A minimal configuration for a simple string value is shown below:
hcl
resource "aws_secretsmanager_secret_version" "example" {
secret_id = aws_secretsmanager_secret.example.id
secret_string = "example-string-to-protect"
}
For more complex scenarios, such as storing database credentials, the secret_string argument often contains a JSON object. Secrets Manager accepts key-value pairs in JSON format, which allows for structured data retrieval. The map used in the jsonencode function can come from various sources, including local variables, resource attributes, or built-in functions.
```hcl
variable "example" {
default = {
key1 = "value1"
key2 = "value2"
}
type = "map"
}
resource "awssecretsmanagersecretversion" "example" {
secretid = awssecretsmanagersecret.example.id
secret_string = jsonencode(var.example)
}
```
This approach ensures that the secret value is dynamically constructed from other Terraform resources or variables, maintaining consistency across the infrastructure. For instance, if the host address of a database instance changes, the secret version can be updated to reflect the new host without manual intervention.
Managing the AWSCURRENT Staging Label and Deletion Edge Cases
One of the most complex aspects of managing aws_secretsmanager_secret_version in Terraform is the interaction with the AWSCURRENT staging label during resource deletion. The AWSCURRENT label is a special staging label that points to the latest version of the secret. When a secret version is deleted, Terraform attempts to remove the staging labels associated with that version to clean up the state. However, AWS Secrets Manager has specific constraints on how the AWSCURRENT label is handled.
If the AWSCURRENT staging label is present on a version during resource deletion, that label cannot be removed and will be skipped to prevent errors when fully deleting the secret. This behavior is a safeguard against leaving a secret in a state where no version is marked as current, which could break applications relying on the default GetSecretValue call. However, this creates a nuance in Terraform's behavior. If the AWSCURRENT label is skipped during deletion, the secret version remains active even after the resource is deleted from Terraform, unless the secret itself is deleted. This can lead to "zombie" versions that persist in AWS but are no longer tracked by Terraform, causing state drift.
To mitigate this, engineers must carefully manage the AWSCURRENT label before or after deleting the resource from Terraform to fully trigger version deprecation if necessary. In practice, this means that if a Terraform resource representing a secret version is removed from the configuration, and that version holds the AWSCURRENT label, the deletion process in Terraform will not remove the label. The version will remain in AWS. To ensure complete cleanup, the secret itself must be deleted, or the label must be manually moved to another version before the Terraform resource is destroyed.
This edge case is particularly relevant in rotation scenarios. When Secrets Manager automatically rotates a secret, it creates a new version and moves the AWSCURRENT label to that new version. The old version is then labeled AWSPREVIOUS. If a Terraform resource is managing the old version and is subsequently removed, Terraform will attempt to delete that version. However, if the rotation logic has already moved AWSCURRENT away, the deletion will proceed normally. If, however, the Terraform resource is managing the current version and is deleted, the AWSCURRENT label handling becomes critical. Engineers should document this behavior in their runbooks to avoid unexpected persistence of secret versions.
Retrieving Secrets and Integration with Other Resources
While the aws_secretsmanager_secret_version resource is used for writing and managing secret values, reading secrets is handled by the aws_secretsmanager_secret_version data source. This distinction is important for configurations where secrets are created outside of Terraform, such as in a different workspace, by a CI/CD pipeline, or manually by an administrator.
The data source allows Terraform to read the current version of a secret without managing it. This is useful for consuming secrets that are managed elsewhere. The data source requires the secret_id argument to specify which secret to read.
```hcl
data "awssecretsmanagersecret" "database" {
name = "production/database/credentials"
}
data "awssecretsmanagersecretversion" "database" {
secretid = data.awssecretsmanagersecret.database.id
}
```
Once the secret value is retrieved, it can be parsed and used in other resources. Since the secret value is often stored as a JSON string, the jsondecode function is commonly used to convert the string into a map, allowing for key-based access.
```hcl
locals {
dbcredentials = jsondecode(data.awssecretsmanagersecretversion.database.secret_string)
}
resource "awsdbinstance" "main" {
engine = "postgres"
engineversion = "15.4"
instanceclass = "db.t3.medium"
dbname = "myapp"
username = local.dbcredentials["username"]
password = local.dbcredentials["password"]
vpcsecuritygroupids = [awssecuritygroup.database.id]
dbsubnetgroupname = awsdbsubnetgroup.main.name
}
```
This pattern decouples the creation of the secret from the consumption of the secret. It allows for a multi-team environment where one team manages the secrets (using aws_secretsmanager_secret_version resources) and another team consumes them (using data sources) without requiring a direct dependency on the managing team's Terraform code. This is a common pattern in large organizations with shared infrastructure.
Security Considerations and State File Management
A critical security consideration when using Terraform with AWS Secrets Manager is the storage of secret values in the Terraform state file. By default, Terraform stores the attributes of resources, including secret_string, in the state file. This means that the actual secret value is persisted in the state backend, which could be a local file, an S3 bucket, or a remote state backend like DynamoDB or Terraform Cloud.
If the state file is compromised, the secret values are exposed. Therefore, protecting the state backend is paramount. Organizations should enforce strict IAM policies on the state bucket, enabling server-side encryption and access logging. Additionally, AWS recently introduced write-only attributes in Terraform, which allow sensitive values to be written to a resource but not stored in the state file. While support for this feature in the AWS provider is evolving, it is an area of active development that engineers should monitor.
When using random_password resources to generate secret values, it is crucial to understand that the generated password will also be stored in the state file unless specific safeguards are in place. The following example demonstrates creating a secret with a randomly generated password:
```hcl
resource "awssecretsmanagersecret" "database" {
name = "production/database/credentials"
description = "Database credentials for the production environment"
recoverywindowin_days = 30
tags = {
Environment = "production"
ManagedBy = "terraform"
}
}
resource "randompassword" "database" {
length = 32
special = true
overridespecial = "!#$%&*()-_=+[]{}<>:?"
}
resource "awssecretsmanagersecretversion" "database" {
secretid = awssecretsmanagersecret.database.id
secretstring = jsonencode({
username = "admin"
password = randompassword.database.result
engine = "postgres"
host = awsdbinstance.main.address
port = 5432
dbname = "myapp"
})
}
```
In this configuration, the random_password resource generates a secure password, and the aws_secretsmanager_secret_version resource stores it as part of a JSON object. This ensures that the secret value is not hardcoded in the Terraform file, reducing the risk of accidental exposure in version control systems. However, the state file still contains the generated password, making state file security a top priority.
Conclusion
The aws_secretsmanager_secret_version resource is a pivotal component in the Terraform ecosystem for AWS-based infrastructure. It bridges the gap between the metadata management provided by aws_secretsmanager_secret and the actual storage of sensitive data. By understanding the distinct roles of these resources, engineers can design more robust and secure secret management workflows. The explicit separation of metadata and version allows for granular control over secret rotation, updates, and deletion.
Key takeaways for practitioners include:
- Always use
aws_secretsmanager_secret_versionto store secret values when usingaws_secretsmanager_secretfor metadata. - Be aware of the
AWSCURRENTstaging label behavior during deletion to avoid state drift and zombie versions. - Utilize
jsonencodeandjsondecodeto handle structured secret data effectively. - Protect the Terraform state file rigorously, as it contains secret values by default.
- Leverage data sources for reading secrets managed outside of the current Terraform workspace.
Mastering these concepts enables teams to adopt a best-practice approach to secrets management, ensuring that sensitive data is handled securely, consistently, and automatically across the infrastructure lifecycle. As AWS and Terraform continue to evolve, features such as write-only attributes and enhanced rotation policies will further enhance the security and usability of these resources, but the foundational principles outlined in this article remain central to effective implementation.