Skip to main content
formae treats a secret as an ordinary managed resource whose value is referenced, never copied. This page covers the three layers of that: referencing a secret’s value, having formae generate the value in the first place, and rotating it on a schedule.
The examples below use local to bind a resource to a variable so it can be referenced later via .res. A local still has to be mentioned inside the forma block to actually be created. See Write your first forma for the full pattern.

Reference, don’t store

A resource or target that needs a secret’s value references it:
What is stored, everywhere, is the reference. The value is read live from the provider at each plugin call, and where formae has to record what it saw, it records a digest, never the cleartext. Nothing shows the value: not the plan, not the inventory, not the logs. Use .secretValue.at("key") to pull one entry out of a map-shaped secret and .json("path") to reach into a JSON payload. See Resolvable for the reference mechanism itself.

Generators: credentials formae draws

Referencing solves reading a credential. A generator solves writing one: instead of minting a password at evaluation time and pinning it so it stops changing, declare a generator and bind properties to its output.
A generator is a top-level forma entry, like a stack or a target: declare it, then mention it inside forma { ... } alongside the resources that bind to it. formae draws the value with a cryptographically secure generator, writes it to every bound property in one command, and keeps only a digest. Without a rotation block the generator never rotates on a schedule, though an apply can still redraw it: binding a new destination draws a value for it, and changing the generator’s spec so the held generation no longer satisfies it forces a fresh draw. This directly replaces the eval-time pattern:
Every property bound to a drawing generator must be part of the same apply. The drawn value exists only for the duration of that command, so a destination left out could never be caught up without drawing again; formae refuses the apply and names the destinations it cannot reach.

Rotation

Give the generator a cadence and formae rotates the credential on schedule, unattended:
A rotation is one coordinated command that moves everything downstream of the credential: the generator’s destinations (the secret), and, transitively, the resources that consume those destinations by reference. In the shape above, rotating the password updates the secret, the database role whose password references it, and anything referencing the role, together. Rotation uses ordinary update semantics, so it refuses when the stack, or any of those consumers, has drifted; a stack carrying an auto-reconcile policy is the standing opt-in to overwrite drift, and there rotation proceeds. Inspect what rotates with formae inventory generators: each generator’s cadence, the instant of its last committed rotation, and the resources that take their value from it.

The window, and what your consumers must do

One thing no rotation scheme with a single credential can remove: there is no transaction spanning the secret store and the system that accepts the credential, so on every rotation there is a brief moment where the two disagree. Anything that reads the secret to authenticate has to be built for that, and the contract is short:
  • Re-read the credential at least once per rotation period. Resolve it per connection, or cache it with a bounded lifetime. A consumer that reads the credential once at startup breaks on the first rotation and stays broken until it restarts.
  • Tolerate transient authentication failures around a rotation, for up to your own cache lifetime, by retrying or reconnecting. The failures end on their own once the cache refreshes.
The formae agent is itself a worked example: point datastore.postgres.passwordSecretArn at the secret and the agent resolves its own database password per new connection, riding out a rotation of that credential without a restart. See the configuration reference. Only overlapping validity, two credentials alternating so one is always accepted, removes the window entirely. formae does not orchestrate that today.

Choosing a cadence

The schema floor is fifteen minutes, set by what the common secret store sustains rather than by the scheduler: AWS Secrets Manager retains every secret version from the last 24 hours against a fixed quota of 100 versions per secret and advises against sustained writes more often than once every 10 minutes, so a faster cadence exhausts the quota within hours and every rotation after that fails. Treat minutes-scale cadences as something for a drill, and pick a steady-state cadence in days: 30 or 90 days are common choices. A credential that must turn over faster than the floor wants short-lived credentials issued per use, not a scheduler revisiting a long-lived one.

Key pairs

KeyPairGenerator draws an RSA key pair as two named outputs of one draw: gen.privateKey (a PKCS#8 PEM) and gen.publicKey (a PKIX PEM). Bind each half to its own destination and the two always hold halves of the same pair:
bits is 2048 (the default), 3072 or 4096. Both halves are stored opaquely. A binding that names an output the generator’s kind does not produce fails the apply with the destination and the output named, so the wrong half can never land silently. Once a key-pair generator is stored, the agent cannot be downgraded to a version that predates the kind.