Skip to content

ADR-0013: Secret references, resolved on the host

Status: Accepted

Context

Datum could not place a credential anywhere, and that was recorded as a gap, not an open question, on the grounds that no half-designed answer was waiting for a decision.

The gap turned out to block more than it appeared to. User models no authentication, so a password hash cannot be set. An authorized_keys file is a File resource whose content is key material. A TLS private key, a database password, a registry credential and a monitoring token are all ordinary requirements of the hosts this system exists to configure. The flagship worked example hardens sshd and cannot provision the keys needed to log in afterwards.

A configuration system that cannot place a credential does not avoid the problem. The credentials get placed by something else, and that something else becomes the real configuration system for the parts that matter most.

The reason the gap persisted is that the obvious answer is wrong. Committing secret values to the repository would make every secret in the fleet readable by every managed machine, because in the deployment model each host reads the whole repository. That is not a limitation to be worked around with encryption at rest in Git, because the hosts would still all hold the decryption key.

A secret mechanism does not require anything central. What it requires is that the secret value never travels through the repository.

Decision

Desired state declares a reference to a secret. A host-local secret provider resolves that reference to a value during apply, and the value exists nowhere else.

repository          holds a reference             app/db-password
effective manifest  holds the same reference      app/db-password
host, during apply  resolves it to a value        (never recorded)

The reference is resolved after the effective manifest has been built, which places it in a different phase from label substitution and gives it different properties on purpose.

Label substitution Secret resolution
Happens during Resolution Apply
Reads Repository content A host-local backend
Affects the manifest digest Yes No
Visible in datum render The resolved value The reference only
Works from a checkout with no host Yes No

A secret value never appears in an effective manifest, its digest, a plan, a report, a log, a metric label or any provenance record. Those sinks are enumerated, not described, because a redaction rule that is described tends to cover the outputs somebody remembered.

The secret provider is selected by agent configuration and not by desired state, for the same reason trust anchors are not managed by Datum. A repository that could nominate where secrets come from could nominate a source the attacker controls.

Secret providers are out-of-process and named, never commanded, exactly as resource providers are. No backend is configured by supplying a command line, because that would be arbitrary root execution from configuration arriving through a different door.

A reference that cannot be resolved fails the resource. It does not resolve to an empty value, and it does not skip the field.

Consequences

The sensitive flag and this mechanism are now clearly different things, which they were not before. sensitive: true suppresses rendering of content that is in the repository, and a secret reference means the content was never there. Both remain, because a file can be confidential without being a credential.

Rotating a secret in the backend changes nothing in Git, so the manifest digest does not change and datum affected will not report it. That is correct and it is also a real limitation, because the digest answers what the repository says rather than what a host will end up holding, and rotation therefore has to be tracked by whatever performs it.

Resolution stays a pure function of the repository, which is what keeps datum validate able to resolve every host in CI with no access to any secret backend. Validation confirms that a reference is well formed and cannot confirm that it exists, so a reference to a secret nobody has created is a failure that appears on a host and not in review.

This is a per-host boundary the design did not previously have. The repository is still readable by every machine, and secret values are not in it, so compromising one host no longer discloses the fleet's credentials.

Datum does not become a secret store. It resolves references against something that already exists, whether that is a file placed by provisioning, an agent-authenticated fetch from a vault, or a platform's own instance credential service. Deciding what that thing is remains the fleet's problem, and the interface is the only part Datum specifies.