← Start of tutorial

WIUNIX ACADEMY

SOPS and age: rotate recipients, data keys and recovery access

Page 3 of 4. Review the earlier steps and prerequisites before continuing.

YOUR SYSTEM. YOUR STEPS.

Where are you working?

Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.

Red Hat Enterprise Linux 10Reviewed against official documentation ·

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
11

LET’S BUILD

Prove new and recovery access before retiring anything

Test each identity independently against the same updated ciphertext and keep an encrypted dual-access checkpoint. Do not accept a success from a shell that may have found another user’s key. The file fingerprints show which ciphertext versions were tested; changing bytes alone proves no permission or content guarantee.

For a real migration, this overlap period includes every actual deployment/CI consumer and an independently exercised recovery route. A successful human workstation decrypt does not prove that a service account, hardware token or disaster-recovery host can use the new identity.

bash

Same unprivileged Bash shell on the isolated administration lab

with_identity "$lab/keys/old.txt" decrypt --output /dev/null "$doc.enc.yaml" || exit 1
with_identity "$lab/keys/new.txt" decrypt --output /dev/null "$doc.enc.yaml" || exit 1
with_identity "$lab/keys/recovery.txt" decrypt --output /dev/null "$doc.enc.yaml" || exit 1
cp -- "$doc.enc.yaml" "$lab/dual-recipient.enc.yaml" || exit 1
sha256sum "$lab/before-old.enc.yaml" "$lab/dual-recipient.enc.yaml"
How do I know it worked?

Old, new and recovery each decrypt the dual-recipient fixture independently; no private key is retired.

12

LET’S BUILD

Remove the old wrapper and rotate the data key

For this fixture, replace the policy with new plus recovery, inspect it and use the already-proven new identity for updatekeys. Then rotate the data key on that current file. Keep all flags before the filename and stop if either selected-file action fails.

Removing only the wrapper is insufficient when the old recipient may have learned the prior data key. Rotation gives this current ciphertext a new data key, but it cannot erase previously copied plaintext or old encrypted snapshots. No application password/API credential is changed by this panel.

bash

Same unprivileged Bash shell on the isolated administration lab

cd "$lab/work" || exit 1
cat > .sops.yaml <<POLICY
creation_rules:
  - path_regex: ^rotation-demo\.enc\.yaml$
    age:
      - $new_recipient
      - $recovery_recipient
POLICY
cat .sops.yaml
with_identity "$lab/keys/new.txt" --config "$lab/work/.sops.yaml" updatekeys "$doc.enc.yaml" || exit 1
with_identity "$lab/keys/new.txt" rotate --in-place "$doc.enc.yaml" || exit 1
How do I know it worked?

The current file uses only new/recovery wrappers and a freshly rotated data key.

13

LET’S BUILD

Check current access and the historical limit separately

First prove new and recovery can still open the current file. Then prove the old identity still opens the retained old ciphertext, so the negative current-file test is not merely a broken old-key setup. Finally test that the normal SOPS decryption path cannot open the current wrappers with old alone.

The expected old-file success is the point: retirement cannot revoke somebody’s old copies. This negative test checks the current wrapper/access path, not every retained data key or plaintext. Keep the final encrypted checkpoint, the consumer rollout evidence and the recovery custody decision before actually retiring a real identity.

bash

Same unprivileged Bash shell on the isolated administration lab

with_identity "$lab/keys/new.txt" decrypt --output /dev/null "$doc.enc.yaml" || exit 1
with_identity "$lab/keys/recovery.txt" decrypt --output /dev/null "$doc.enc.yaml" || exit 1
with_identity "$lab/keys/old.txt" decrypt --output /dev/null "$lab/before-old.enc.yaml" || exit 1
if with_identity "$lab/keys/old.txt" decrypt --output /dev/null "$doc.enc.yaml"; then
    printf '%s\n' 'STOP: old identity unexpectedly opens the current file.'
    exit 1
else
    printf '%s\n' 'Old identity cannot open current wrappers; historical ciphertext still opens.'
fi
cp -- "$doc.enc.yaml" "$lab/final-new.enc.yaml" || exit 1
How do I know it worked?

New/recovery open the current fixture, old opens the historical copy, and old alone fails against the current wrappers.

Do not bypass authentication to complete a rotation

Run only the diagnostic appropriate to this private lab. Preserve the error and checkpoint; never share private identities, decrypted content or unrestricted logs. A MAC failure is a stop condition, not permission to add ignore-mac.

The new identity cannot open the dual-access file

Confirm the exact private launcher, fixture and public recipient derivation. Inspect the public policy locally and rerun the selected updatekeys step only after its proposed recipient change is understood. A policy edit by itself did not migrate the ciphertext.

bash
"$keygen_bin" -y "$lab/keys/new.txt"
cat "$lab/work/.sops.yaml"
"$sops_bin" filestatus "$lab/work/$doc.enc.yaml"
Old-key decryption unexpectedly succeeds on the final file

Stop retirement. Check that the controlled helper is loaded, that the current file is the final version and that another identity source or recipient was not retained. A new shell needs the earlier helper and variables reloaded; do not turn this into a broad search for private keys.

bash
type with_identity
sha256sum "$lab/work/$doc.enc.yaml" "$lab/final-new.enc.yaml"
"$keygen_bin" -y "$lab/keys/old.txt"
No matching creation rule or wrong file format

Compare the exact basename, YAML extension and explicitly selected config. The fixture has one simple rule relative to that config directory. Do not broaden a real project rule just to make a sample match; paths and format are part of the authoring contract.

bash
pwd
ls -l -- "$lab/work/$doc.enc.yaml" "$lab/work/.sops.yaml"
cat "$lab/work/.sops.yaml"
Recovery or MAC validation fails

Keep the existing ciphertext and identity custody intact. Try the known encrypted checkpoint with its authorized recovery identity and ordinary MAC verification. A failed recovery check means do not retire the remaining working identity; investigate the exact checkpoint/key pairing.

bash
with_identity "$lab/keys/recovery.txt" decrypt --output /dev/null "$lab/dual-recipient.enc.yaml"
A verified private launcher will not start

Inspect the actual mount options, executable mode and architecture. A noexec mount or MAC denial is a host-policy boundary, not a checksum failure. Use a new approved executable lab location or resolve the specific denial with the host owner; do not remount broadly, relabel system paths or disable enforcement.

bash
findmnt --target "$lab" -o TARGET,SOURCE,FSTYPE,OPTIONS
stat -c '%a %n' "$lab/bin/sops" "$lab/bin/age" "$lab/bin/age-keygen"
uname -m
14

LET’S BUILD

Optionally restore the lab’s dual-access checkpoint

This optional rollback deliberately reintroduces old-recipient access in the lab. Use it only for a routine migration rehearsal, never as an automatic response to a compromised-key incident. Verify recovery access to the saved checkpoint before copying it over this fixture’s current ciphertext.

The new-only policy file still describes the intended forward migration; restoring an old ciphertext does not reconcile that policy. Either repeat the reviewed forward steps or retain the lab as an explicitly rolled-back test. Do not represent it as a finished production retirement.

bash

Same unprivileged Bash shell on the isolated administration lab

test -f "$lab/dual-recipient.enc.yaml" || exit 1
with_identity "$lab/keys/recovery.txt" decrypt --output /dev/null "$lab/dual-recipient.enc.yaml" || exit 1
cp -- "$lab/dual-recipient.enc.yaml" "$lab/work/$doc.enc.yaml" || exit 1
with_identity "$lab/keys/old.txt" decrypt --output /dev/null "$lab/work/$doc.enc.yaml" || exit 1
with_identity "$lab/keys/new.txt" decrypt --output /dev/null "$lab/work/$doc.enc.yaml" || exit 1
How do I know it worked?

The private lab is deliberately back at its verified dual-access checkpoint, with that rollback state explicitly recorded.

Retire identity custody only after consumers and backups agree

For real files, inventory the exact encrypted revisions, approved recipients, key groups/KMS dependencies, deployment consumers, backup retention and recovery custodians. This simple one-group age exercise is not a ready-made rewrite of a mixed KMS/Shamir project. Rehearse one approved file, protect concurrent source edits, then use the owning repository’s review and deployment workflow.

After consumer and recovery acceptance, stop distributing the retired private identity through the actual secret store/CI/user-access mechanism. Preserve a controlled recovery copy only when retention policy requires it and its risk is understood. Neither deletion on one laptop nor rewriting Git history can retract copies elsewhere. Old ciphertext in history, backups, artifacts or third-party clones can remain decryptable.

If the old identity or plaintext was exposed, also change the actual secrets at their services, revoke the old credentials, update consumers and assess historical exposure through the incident workflow. A new age identity and SOPS data key cannot make an already-known password unknown again.

Explore all tutorials ↗