Secrets management DATAVERKET 001

Accepted Security Secrets Encryption Sops Opentofu

Defines how infrastructure secrets and OpenTofu state are encrypted and managed in version control using SOPS and hardware-bound keys.

Author
Jan Ivar Beddari
Updated

Status

Accepted on 2026-03-08 by Jan Ivar Beddari.

Context

Infrastructure configuration managed through OpenTofu requires secrets such as API tokens. These secrets must be stored in version control safely and decryptable only by authorized parties.

The solution must work for a single operator initially, scale to a team later, avoid vendor lock-in, and allow secrets to be replaced individually without decrypting unrelated material.

Decision Drivers

  • Secrets must be safe to store in version control.
  • Operators should not need to decrypt unrelated secrets when rotating one value.
  • Private key material should remain hardware-bound rather than stored on disk.
  • The solution should scale from one operator to a team without forcing an immediate external KMS dependency.

Considered Options

SOPS with hardware-bound age recipients and one secret per file

Encrypt each secret independently and decrypt inline during OpenTofu operations using hardware-backed recipients.

Shared encrypted secret bundle

Store multiple secrets together in one encrypted file or one broader secret set.

Immediate centralized secret backend requirement

Require an external centralized secrets system from the first phase.

Decision

Dataverket infrastructure secrets use SOPS for secret files and OpenTofu native encryption for state. Both rely on hardware-bound key sources based on age-plugin-se and or age-plugin-yubikey.

Encryption layers

WhatEncrypted byKey source
Secrets such as tokens and credentialsSOPSage-plugin-se / age-plugin-yubikey
OpenTofu stateOpenTofu native state encryptionage-plugin-se / age-plugin-yubikey

Secret file strategy

Each secret is a separate SOPS-encrypted file under infra/secrets/.

infra/secrets/
├── codeberg-token.enc.yaml
├── mirror-token.enc.yaml
└── ...

One secret per file means:

  • rotating a secret only requires re-encrypting that one file
  • no unrelated secrets need to be decrypted to replace one value
  • version control diffs stay narrow and understandable

SOPS integration

The nobbs/sops OpenTofu provider decrypts secrets inline during tofu plan and tofu apply. No wrapper scripts or environment variables are required.

Hardware-bound keys

  • age-plugin-se and or age-plugin-yubikey act as SOPS recipients
  • private key material never exists on disk
  • each operator’s public key is listed in .sops.yaml
  • hardware-token choice remains an operator-side concern

Planned phase 2

An OpenBao transit key may be added later as an additional SOPS key source so that new team members can authenticate to OpenBao instead of managing individual age recipients only. Existing age keys remain as offline and emergency fallback.

Consequences

Positive

  • Secrets and state are safe to commit because they remain encrypted at rest.
  • Individual secret rotation becomes straightforward because files are separated.
  • The approach avoids an immediate dependency on a cloud KMS.

Negative

  • Team membership changes still require recipient management in .sops.yaml until a centralized transit backend is added.
  • The security model depends on operators maintaining working hardware-bound decryption paths.

Neutral

  • Migration to OpenBao later is additive rather than a full replacement.
  • Operator onboarding can evolve from “add your public key” to “get OpenBao access” without discarding the initial model.

Decision Outcome

Accepted. Infrastructure secrets and OpenTofu state will use encrypted-at-rest workflows based on SOPS, OpenTofu native state encryption, and hardware-bound age recipients.

  • No external links are required for this ADR.

More Information

  • The future OpenBao transit phase is an explicit extension path, not a prerequisite for the initial design.

Audit

  • 2026-03-08: ADR created and accepted.