net-kingdom/sso-mfa/bootstrap/pack-bundle.sh
tegwick d9557ba90e
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Harden remaining bootstrap custody callers and prepare isolated recovery
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 22:57:14 +02:00

61 lines
2.8 KiB
Bash
Executable file

#!/usr/bin/env bash
# Encrypt a private service-file snapshot without a plaintext tar intermediate.
# Usage: pack-bundle.sh SECRETS_DIR PUBLIC_AGE_RECIPIENT [NEW_OUTPUT_FILE]
# Use the independently registered recipient; private age keys never belong in
# the payload. Run under owner-exclusive custody maintenance (no same-UID writers).
set +x
set -euo pipefail
umask 077
fail() { printf '%s\n' 'ERROR: bundle refused or failed; no new recovery artifact published.' >&2; exit 1; }
[[ $# -ge 2 && $# -le 3 ]] || fail
SECRETS_DIR="${1%/}"
RECIPIENT="$2"
OUTPUT="${3:-ops-bundle-$(date +%Y%m%dT%H%M%S)-$RANDOM.tar.age}"
[[ "$RECIPIENT" == age1* ]] || fail
printf '' | age -r "$RECIPIENT" >/dev/null 2>&1 || fail
[[ ! -e "$OUTPUT" && ! -L "$OUTPUT" ]] || fail
PARENT="$(dirname -- "$OUTPUT")"
[[ -d "$PARENT" ]] || fail
STAGING=$(mktemp -d "$PARENT/.netkingdom-bundle-XXXXXXXX") || fail
trap 'rm -rf -- "$STAGING"' EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
# Emit only member names to a private local list. Never follow links or include
# special/hardlinked files, permissive custody inputs or age private material.
if ! python3 - "$SECRETS_DIR" "$OUTPUT" > "$STAGING/members" 2>/dev/null <<'PY'
import os, pathlib, stat, sys
root = pathlib.Path(os.path.abspath(sys.argv[1]))
output = pathlib.Path(os.path.abspath(sys.argv[2]))
for path in (root, *root.parents, output.parent, *output.parent.parents):
if path.is_symlink(): raise ValueError()
if root == output or root in output.parents: raise ValueError()
count = 0
for path in [root, *sorted(root.rglob('*'))]:
info = path.lstat()
if info.st_uid != os.getuid() or info.st_mode & 0o077: raise ValueError()
if stat.S_ISDIR(info.st_mode): continue
if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_size > 32 * 1024 * 1024:
raise ValueError()
with path.open('rb') as source:
if b'AGE-SECRET-KEY-' in source.read(): raise ValueError()
name = str(path.relative_to(root.parent))
if any(ord(c) < 32 for c in name): raise ValueError()
sys.stdout.buffer.write(name.encode() + b'\0')
count += 1
if not count: raise ValueError()
PY
then fail; fi
# Source/root are data arguments; member names are NUL delimited and verbatim.
if ! tar -C "$(dirname -- "$SECRETS_DIR")" --null --verbatim-files-from \
--no-recursion -T "$STAGING/members" -cf - 2>/dev/null \
| age -r "$RECIPIENT" -o "$STAGING/ciphertext" 2>/dev/null; then fail; fi
python3 - "$STAGING/ciphertext" 2>/dev/null <<'PYSYNC' || fail
import os, sys
fd=os.open(sys.argv[1], os.O_RDONLY)
try: os.fsync(fd)
finally: os.close(fd)
PYSYNC
# Hard-link publication atomically refuses an output created concurrently.
ln -T -- "$STAGING/ciphertext" "$OUTPUT" 2>/dev/null || fail
printf '%s\n' 'Encrypted bundle published privately; retain it off-host and independently verify recovery before removing source custody.'