Write-only attribute "version" changes that depend on changes to other resources

I am working on a Terraform project where I generate a random password for a mongodb user and additionally create a secret in the AWS secrets manager that provides a mongodb connection string based on both that ephemeral random password and other connection information like a hostname. In this situation, I want the connection string secret to update any time that either someone wants the password to be regenerated (prompted by a version number being bumped in a Terraform file) or when the mongodb connection information changes. In this scenario, with all resources containing passwords being ephemeral, I cannot find a reasonable way to have Terraform resolve these dependencies automatically. Here is an example Terraform file, simplified for clarity:

// First, we create an AWS secret to store the user credentials in so they can stay the same even when the connection string changes.
resource "aws_secretsmanager_secret" "db_user" {
  name = "db-user"
}

ephemeral "random_password" "db_user" {
  length = 16
  special = false
}

// The database credentials are stored here. We can't store the mongo connection string here because then we couldn't update the connection string without also generating a new random password.
resource "aws_secretsmanager_secret_version" "db_user" {
  secret_id = aws_secretsmanager_secret.db_user.id
  secret_string_wo = jsonencode({
    db_user = "foo"
    db_pass = ephemeral.random_password.db_user.result
  })
  secret_string_wo_version = 1
}

// We can get an ephemeral version of the previously created secret for use in the rest of the file.
ephemeral "aws_secretsmanager_secret_version" "db_user" {
  secret_id = aws_secretsmanager_secret_version.db_user.secret_id
}

// A new secret to store a mongodb connection string.
resource "aws_secretsmanager_secret" "db_conn" {
  name = "db-conn"
}

locals {
  db_creds_ephemeral = jsondecode(aws_secretsmanager_secret_version.db_user.secret_string)
}


resource "aws_secretsmanager_secret_version" "db_conn" {
  secret_id = aws_secretsmanager_secret.db_conn.id

  secret_string_wo = jsonencode({
    db_conn = "mongodb://${local.db_creds_ephemeral.db_user}:${local.db_creds_ephemeral.db_pass}@${var.mongo_host}"
  })

  // What do we do here??
  secret_string_wo_version = TODO
}



The problem here is that I don’t have a good value to set aws_secretsmanger_secret_version.db_conn.secret_string_wo_versionto so that the secret is updated when either aws_secretsmanager_secret_version.db_useror var.mongo_host change.

The solution I’m trying is to use a time_static resource to generate a version as a UNIX timestamp, and triggering a replace of that resource when the dependent attributes change. This is an awkward solution (notably because you have to wait at least one second between updates to this secret or state will break).

resource "time_static" "db_conn_secret_wo_version" {
  triggers = {
    db_user = aws_secretsmanager_secret_version.db_user.version_id
    domain_name = var.mongo_host
  }
}

resource "aws_secretsmanager_secret_version" "db_conn" {

(...)

  secret_string_wo_version = time_static.db_conn_secret_wo_version.unix
}

Another solution I could see is to use a replace_triggered_by lifecylcle argument in aws_secretsmanager_secret_version.db_conn, and to use a static secret_string_wo_version, but although that might work here it doesn’t work in any resource that needs to be updated in-place when a write-only value is changed instead of being replaced.

Another solution would be a per-provider change to how _wo_version attributes work. There is an issue for the terraform AWS provider that I think would allow for a more robust solution to this particular problem where the `secret_string_wo_version` is just set to an object containing all the values that the secret might depend on. However, that solution relies on every provider considering this best-practice and puts the responsibility on them to implement, so I don’t feel like this is a good solution for Terraform as a whole either.

Maybe a feature addition for Terraform could be a solution to this, where you could for example wrap a value in a function before assigning it to a write-only attribute that attaches dependency values to it so the provider knows when the write-only value is supposed to update.

So, what’s the best way of handling this situation currently? Are there any ideas that I haven’t thought of? Is this a gap in functionality that could be deserving of a new feature in Terraform?

Will it let you use aws_secretsmanager_secret_version.db_user.secret_string_wo_version directly? I tried a local copy of more or less your config from above and did

  secret_string_wo_version = aws_secretsmanager_secret_version.db_user.secret_string_wo_version

and terraform validate at least didn’t complain, but I didn’t test actually creating the resources :person_shrugging:

A really lame way would be to just add secret_string_version into locals, and reference local.secret_string_version in both places.

That doesn’t solve the problem. That makes aws_secretsmanager_secret_version.connupdate only when aws_secretsmanager_secret_version.db_user does, but not when var.mongo_host does. If var.mongo_host were to change, the value of the db_conn secret would be come incorrect because it would still refer to the old mongo host.

I see what you mean.

Would adding keepers to the value that depends on the mongo hostname help here somehow, maybe?

Are you suggesting That the aws_secretsmanager_secret_version.db_connresource could have a keepers attribute? If so, its purpeos seems to conflict with that of the secret_string_wo_version attribute. I suppose it would effectively allow the same thing as the GitHub issue I mentioned earlier for the Terraform AWS provider (Support arbitrary values in wo_version fields · Issue #42441 · hashicorp/terraform-provider-aws · GitHub).

Not sure - was mostly just thinking aloud, mostly just because I’ve seen keepers used to trigger recreation of one resource when another one changes.