WIUNIX ACADEMY
SOPS and age: rotate recipients, data keys and recovery access
Rehearse a staged age-recipient migration, isolate decryption tests and distinguish rewrapping from data-key rotation without pretending old ciphertext has been revoked.
Make this guide yoursYOUR SYSTEM. YOUR STEPS.
Where are you working?
Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.
USE YOUR OWN VALUES
Configure this tutorial
Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.
Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.
Set values for my environment2 editable values
THE OUTCOME
What will you have at the end?
Rehearse a staged age-recipient migration, isolate decryption tests and distinguish rewrapping from data-key rotation without pretending old ciphertext has been revoked.
Supported operating systems and versions
Documented preparation for Ubuntu 26.10/26.04/24.04, Debian 13/12, RHEL/Rocky/AlmaLinux 10/9 and Arch/Manjaro rolling, reviewed 2026-11-05. Private SOPS 3.13.3/age 1.3.2 Linux x86_64/aarch64 binaries and existing trusted verifiers required; no all-environment key/decryption execution is claimed.
Before you begin
- An isolated administration lab, unprivileged Bash account, a new private path outside Git and approved access to public release artifacts.
- Existing trusted Cosign and sigsum-verify, including sigsum-generic-2025-1 and independently approved publisher-verification keys.
- curl, CA certificates, coreutils, tar and Git base tools; package preparation only in an agreed lab maintenance window.
- Only the public fixture value; no real secret, KMS/plugin/SSH recipient or production consumer enters this exercise.
Rotate the correct key, and define what retirement can mean
SOPS encrypts file values with a data key and stores encrypted copies of that key for approved recipients. An age recipient is public; its matching identity is private. Changing the recipient list, generating a different data key and replacing an age identity are three distinct operations.
We rehearse an old-to-new recipient migration with an independent recovery recipient using fresh local identities and a deliberately non-secret YAML value. No real secret or production deployment is needed. The fixture stays outside Git and no private identity is placed in a browser field, command argument or pasted file panel.
updatekeys changes the file’s key wrappers using the selected policy without rotating its data key. rotate makes a new data key and reencrypts the values. Neither action revokes access to an old ciphertext copy, data key or plaintext that somebody already retained. A compromised application credential must also be replaced at its actual issuer.
Choose only public lab names
Apply a new absolute lab directory and a short document basename in the values form. The directory must not exist or sit inside a Git working tree. The basename controls the encrypted filename and matching policy rule together; it is not a secret name or customer identifier.
Recipients are public keys, but their actual value and ownership still require authenticated exchange. This form intentionally does not accept keys. The lab derives public recipients from its new private files locally; real migrations must verify the recipient’s owner and recovery custody through the existing protected process. Tab-local values are not access authorization.
LET’S BUILD
Record distribution tools and trusted verifiers
Use an unprivileged Bash account on an isolated administration lab. The actual SOPS/age executables will be pinned inside this new lab, so an existing system package or another PATH entry is never silently replaced. The reviewed release pair is SOPS 3.13.3 and age 1.3.2, checked 2026-11-05, for Linux x86_64/aarch64.
A trusted existing Cosign and sigsum-verify (with the sigsum-generic-2025-1 policy, available in the documented v0.13.1 path) are prerequisites. Obtain missing verifiers through the organization’s already approved installation route before this recipe. Do not bootstrap a verifier from an unchecked binary and call the following verification independent.
Rolling review dated 2026-11-05; installed builds and the current Manjaro branch still matter. Arch currently offers sops/age packages, but this exact fixture uses verified pinned private binaries. Do not inject Arch packages into Manjaro or perform a partial upgrade.
Same unprivileged Bash shell on the isolated administration lab
cat /etc/os-release
uname -m
bash --version
pacman -Q curl ca-certificates coreutils tar git util-linux
pacman-mirrors -G
command -v curl sha256sum awk tar install env git findmnt cosign sigsum-verify
cosign version
sigsum-verify --helpThe host/build and trusted verifier availability are known. Missing prerequisites stop preparation before downloads or key creation.
LET’S BUILD
Prepare base tools through this distribution’s repositories
Skip installation when the base tools already exist. Otherwise review the native package transaction in a suitable lab maintenance window. DEB uses apt, the RHEL family uses its entitled/vendor dnf repositories, and Arch/Manjaro use their own rolling repositories with a planned full upgrade.
SOPS also publishes DEB/RPM packages and Arch currently packages SOPS/age. This exact recipe instead uses the upstream binary verification path below, with private version-pinned launchers. The SOPS signed checksum configuration covers the binary artifacts; do not assume that the same checksum signature authenticates every DEB/RPM asset. No daemon, firewall, MAC or credential-store policy is changed.
Same unprivileged Bash shell on the isolated administration lab
sudo pacman -Syu curl ca-certificates coreutils tar git util-linuxBase tools are present from the matching official repository; trusted verifiers remain separately required.
LET’S BUILD
Reserve a private lab outside Git
The commands disable shell tracing, set a private creation mask and refuse an existing path, including a dangling symlink. They create separate key, ciphertext, empty-discovery and tool directories under one new private lab. No Git repository is initialized.
Keep this Bash shell for the remaining panels. A failure can leave the already-created private directory; inspect it and choose a fresh unused path rather than deleting unrelated files. The recovery identity is co-located only to demonstrate the mechanics; real recovery custody and backup must be independent.
Same unprivileged Bash shell on the isolated administration lab
set +x
umask 077
set -o pipefail
lab='/var/tmp/academy-sops-age'
doc='rotation-demo'
case "$lab" in /*) ;; *) printf '%s\n' 'STOP: require an absolute lab path.'; exit 1 ;; esac
test ! -e "$lab" && test ! -L "$lab" || exit 1
mkdir -m 0700 -- "$lab" || exit 1
probe=$(readlink -f -- "$lab") || exit 1
while :; do
test -r "$probe" && test -x "$probe" || exit 1
if test -e "$probe/.git" || test -L "$probe/.git"; then
printf '%s\n' 'STOP: an ancestor contains a Git working-tree marker.'
exit 1
fi
test "$probe" != / || break
probe=$(dirname -- "$probe") || exit 1
done
if git -C "$lab" rev-parse --show-toplevel >/dev/null 2>&1; then
printf '%s\n' 'STOP: the lab must be outside every Git working tree.'
exit 1
fi
mkdir -m 0700 -- "$lab/keys" "$lab/work" "$lab/empty-config" "$lab/downloads" "$lab/vendor" "$lab/bin" || exit 1
printf 'Private non-production lab: %s\n' "$lab"A new private lab exists outside Git, with no production files touched.
LET’S BUILD
Download the exact reviewed release artifacts
Select the Linux architecture from the actual host and fetch version-pinned public artifacts into the private download directory. Unsupported architectures stop explicitly. These files are still untrusted input: do not run or extract the executables before the next two verification steps.
The age archive has a Sigsum proof; SOPS has a signed checksum bundle. HTTPS transport alone and a checksum taken from the same unauthenticated source are not substitutes for checking the intended publisher. Retain the releases, verifier versions and verification results with the lab evidence.
Same unprivileged Bash shell on the isolated administration lab
case "$(uname -m)" in
x86_64) arch=amd64 ;;
aarch64) arch=arm64 ;;
*) printf '%s\n' 'STOP: this binary path covers only Linux x86_64/aarch64.'; exit 1 ;;
esac
cd "$lab/downloads" || exit 1
sops_artifact="sops-v3.13.3.linux.$arch"
age_archive="age-v1.3.2-linux-$arch.tar.gz"
curl --fail --location --proto '=https' --tlsv1.2 --output "$sops_artifact" "https://github.com/getsops/sops/releases/download/v3.13.3/$sops_artifact" || exit 1
curl --fail --location --proto '=https' --tlsv1.2 --output sops-v3.13.3.checksums.txt https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.checksums.txt || exit 1
curl --fail --location --proto '=https' --tlsv1.2 --output sops-v3.13.3.checksums.sigstore.json https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.checksums.sigstore.json || exit 1
curl --fail --location --proto '=https' --tlsv1.2 --output "$age_archive" "https://github.com/FiloSottile/age/releases/download/v1.3.2/$age_archive" || exit 1
curl --fail --location --proto '=https' --tlsv1.2 --output "$age_archive.proof" "https://github.com/FiloSottile/age/releases/download/v1.3.2/$age_archive.proof" || exit 1The selected SOPS binary/checksum bundle and age archive/proof are downloaded but not yet installed or executed.