net-kingdom/sso-mfa/bootstrap/decrypt-secrets.sh
tegwick 4cf26a9bc8
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
fix: make custody input explicit and remove partial decryptions
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a12d13-27f2-7163-b8c0-f682af02772c
2026-10-12 10:53:30 +02:00

48 lines
2.1 KiB
Bash
Executable file

#!/usr/bin/env bash
# Explicit attended custody input only. Publish plaintext only after all files
# decrypt successfully into a private staging directory; never emit values.
set +x
set -euo pipefail
umask 077
fail() { printf 'ERROR: %s\n' "$1" >&2; exit 1; }
OUTPUT_DIR="${1:-./secrets}"
AGE_KEY="${2:-${NETKINGDOM_CUSTODY_AGE_KEY_FILE:-}}"
[[ $# -le 2 ]] || fail "unsupported decrypt argument"
[[ -n "$AGE_KEY" && -f "$AGE_KEY" ]] || fail "explicit temporary custody key required"
ENC_DIR="$(dirname "$OUTPUT_DIR")/secrets.enc"
[[ -d "$ENC_DIR" && ! -L "$ENC_DIR" ]] || fail "encrypted source directory unavailable"
[[ ! -e "$OUTPUT_DIR" && ! -L "$OUTPUT_DIR" ]] || fail "output already exists"
[[ -d "$(dirname "$OUTPUT_DIR")" ]] || fail "output parent unavailable"
STAGING=$(mktemp -d "$(dirname "$OUTPUT_DIR")/.netkingdom-decrypt.XXXXXXXX")
cleanup() {
if [[ -d "$STAGING" ]]; then
find "$STAGING" -type f -exec shred -u {} \; 2>/dev/null || true
rm -rf -- "$STAGING"
fi
}
trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
count=0
for component_dir in "$ENC_DIR"/*; do
[[ -e "$component_dir" || -L "$component_dir" ]] || continue
[[ -d "$component_dir" && ! -L "$component_dir" ]] || fail "unexpected encrypted component"
component=$(basename "$component_dir")
mkdir "$STAGING/$component"
for file in "$component_dir"/*.age; do
[[ -e "$file" || -L "$file" ]] || continue
[[ -f "$file" && ! -L "$file" ]] || fail "unexpected encrypted input"
name=$(basename "${file%.age}")
age -d -i "$AGE_KEY" -o "$STAGING/$component/$name" "$file" 2>/dev/null || fail "decryption failed; partial plaintext removed"
count=$((count + 1))
done
done
[[ "$count" -gt 0 ]] || fail "no encrypted files found"
# Never replace an output created by another process during decryption.
mv -Tn -- "$STAGING" "$OUTPUT_DIR" || fail "could not publish private output"
[[ ! -d "$STAGING" ]] || fail "output appeared during decryption"
printf '%s file(s) decrypted to private output; remove plaintext after the approved operation.\n' "$count"