#!/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.'