Documentation · Use it

Secrets in the record store

Anyone who can read an estate’s records can read every value that estate recorded, secrets included, in clear: s3:GetObject on the estate’s prefix, get on Secrets in the records namespace, or read on the directory. It is the same bargain a state file makes.

Encryption at rest does not change this, because the store decrypts for any caller that access control allows. A customer managed KMS key is the one arrangement that puts a second thing in the way, since the reader then also needs kms:Decrypt.

What is recorded

The default is strict { secrets = "store" }.

A record-backed resource is recorded whole: a random_password or a tls_private_key has no live object, so the record is the only copy of the value.

An ordinary resource is recorded in part. Its record holds what a read cannot give back, and that includes arguments the API never returns, such as a database’s master password. So an estate with no random_* in it can still have secrets in its store. A kubernetes_secret is the counter-example: the API returns its data, so its record holds none of it.

A write-only argument is never recorded. A root output marked sensitive is never written.

Who holds the read

WhoWhy
The estate’s own roleIt has to
Anyone with a broad read on the bucket, the account or the clusterA read-only audit role reads private keys here
Anyone who can assume eitherIncluding CI
Whoever recovers a deleted recordRecovery reads the record

The cache file holds them too

Under store, every apply also writes .terraform/choudoufu-cache.tfstate on the machine that ran it: a stock state file, unencrypted, with every sensitive attribute and output in it. .terraform is gitignored by convention, which does nothing about who can read the disk or what a CI system caches between jobs. CHOUDOUFU_STATE_CACHE=off stops it being written.

What secrets = "refuse" does

One thing is outside it. A record-backed resource your configuration hands a secret, terraform_data { input = var.db_password } for example, is recorded whole, because the refusal is by resource type.

Under refuse, generate secrets somewhere built to hold them and pass a reference. CHOUDOUFU_STRICT_PIN=1 in the environment stops a configuration relaxing the setting.

secrets = "ssm", designed and not yet built

A third setting keeps the values in SSM Parameter Store under a customer managed KMS key, with the record carrying a reference. Its grammar, refusals and nested ssm block are in; nothing writes a parameter yet, so asking for it is refused by name (#1515). refuse is the answer until it ships.

live/SECRETS.md has the full account: the measured record of a kubernetes_secret, the two kinds of sensitive argument that stay out of a record under either setting, and what the SSM setting will and will not move.