Keeping secrets out of Terraform state

Viewed 3496

I'm trying to avoid having secrets in Terraform state.

Is there a better way of setting an RDS password from a secret in Secrets Manager that does this?

resource "null_resource" "master_password" {
  triggers = {
    db_host = module.myrdsdatabase.cluster_id
  }

  provisioner "local-exec" {
    command = <<TOF
    password=$(aws secretsmanager get-secret-value --secret-id myrdscreds | jq '.SecretString | fromjson | .password' | tr -d '"')
    aws rds modify-db-cluster --db-cluster-identifier ${module.myrdsdatabase.cluster_id} --master-user-password $password --apply-immediately
    TOF

    interpreter = ["bash", "-c"]
  }
}
3 Answers

There is no concrete solution to this issue. There is nearly 7 year old, still active, discussion on TF github issue about handling secrets in TF.

In your question you are already avoiding aws_secretsmanager_secret_version which is good practice. aws_secretsmanager_secret_version will not protect your secrets from being in plain text in TF state file!

Generally, there are two things people do to keep the secrets secret:

  1. Store your TF state in a remote backend. Such as S3 with strict IAM and bucket policy controls, along with encryption at rest.
  2. Use external procedure to set the passwords for your database. One way is local-exec, other could be using remote lambda through aws_lambda_invocation.

Other ways are possible, such as creating RDS databases through CloudFormation (CFN) from your TF. CFN has a proper way of accessing secret manager securely through dynamic references.

The current possible workaround is using SecretHub

https://secrethub.io/docs/guides/terraform/

but again as @marcin said no native support from Terraform, and the idea to encrypt your state file and restrict access to the backend eg. S3

another way I would do this by using aws cli outside of terraform to rest the password and upload it to secret manager.

We have to accept that the secrets are in the state and manage access and encryption to the remote state accordingly.

You can get a value right out of secrets manager with the aws_secretsmanager_secret_version data source

your local-exec can be simplified a bit by using jq --raw-format or -r

 password=$(aws secretsmanager get-secret-value --secret-id myrdscreds | jq -r .SecretString | jq -r .password)

I prefer to fetch and then pass in the secret in a variable from a build script, rather than local-exec. It seems cleaner, in some cases the trigger on the null resource may not fire when the secret value has been changed.

Related