{ writeShellApplication, nebula, sops, coreutils, jq, ... }: writeShellApplication { name = "nebula-sign-cert"; runtimeInputs = [ nebula sops coreutils jq ]; text = '' # --------------------------------------------------------------------------- # nebula-sign-cert # # Signs a new Nebula host certificate using the CA stored in a SOPS secrets # file and writes the resulting cert+key back into a (possibly different) # SOPS secrets file. # # The CA is read from: # at YAML path /ca-cert and /ca-key # # The new cert+key are written to: # at YAML paths # /-cert # /-key # # Usage: # nebula-sign-cert \ # --name # e.g. "nas" — used in the cert # --ip # e.g. "10.1.1.2/24" # --ca-file # --ca-prefix # e.g. "pi5/nebula" # --host-file # --host-prefix # e.g. "jallen-nas/nebula" # --host-secret-name # e.g. "nas" (cert stored as nas-cert/nas-key) # [--groups ] # optional Nebula groups # [--duration ] # e.g. "8760h0m0s" (1 year), default: CA lifetime # # All temp files are written to a private tmpdir and shredded on exit. # --------------------------------------------------------------------------- set -euo pipefail # ── argument parsing ──────────────────────────────────────────────────────── NAME="" IP="" CA_FILE="" CA_PREFIX="" HOST_FILE="" HOST_PREFIX="" HOST_SECRET_NAME="" NEBULA_GROUPS="" DURATION="" usage() { echo "Usage: nebula-sign-cert \\" echo " --name \\" echo " --ip \\" echo " --ca-file \\" echo " --ca-prefix (e.g. pi5/nebula) \\" echo " --host-file \\" echo " --host-prefix (e.g. jallen-nas/nebula) \\" echo " --host-secret-name (e.g. nas) \\" echo " [--groups ] \\" echo " [--duration <8760h0m0s>]" exit 1 } while [[ $# -gt 0 ]]; do case "$1" in --name) NAME="$2"; shift 2 ;; --ip) IP="$2"; shift 2 ;; --ca-file) CA_FILE="$2"; shift 2 ;; --ca-prefix) CA_PREFIX="$2"; shift 2 ;; --host-file) HOST_FILE="$2"; shift 2 ;; --host-prefix) HOST_PREFIX="$2"; shift 2 ;; --host-secret-name) HOST_SECRET_NAME="$2"; shift 2 ;; --groups) NEBULA_GROUPS="$2"; shift 2 ;; --duration) DURATION="$2"; shift 2 ;; -h|--help) usage ;; *) echo "Unknown argument: $1"; usage ;; esac done # ── validate required args ────────────────────────────────────────────────── missing=() [[ -z "$NAME" ]] && missing+=(--name) [[ -z "$IP" ]] && missing+=(--ip) [[ -z "$CA_FILE" ]] && missing+=(--ca-file) [[ -z "$CA_PREFIX" ]] && missing+=(--ca-prefix) [[ -z "$HOST_FILE" ]] && missing+=(--host-file) [[ -z "$HOST_PREFIX" ]] && missing+=(--host-prefix) [[ -z "$HOST_SECRET_NAME" ]] && missing+=(--host-secret-name) if [[ ''${#missing[@]} -gt 0 ]]; then echo "error: missing required arguments: ''${missing[*]}" usage fi [[ -f "$CA_FILE" ]] || { echo "error: CA secrets file not found: $CA_FILE"; exit 1; } [[ -f "$HOST_FILE" ]] || { echo "error: host secrets file not found: $HOST_FILE"; exit 1; } # Convert "a/b/c" prefix → sops extract path ["a"]["b"]["c"] prefix_to_sops_path() { local prefix="$1" local IFS='/' local result="" for segment in $prefix; do result+="[\"$segment\"]" done echo "$result" } CA_SOPS_PATH=$(prefix_to_sops_path "$CA_PREFIX") HOST_SOPS_PATH=$(prefix_to_sops_path "$HOST_PREFIX") # ── setup temp directory (cleaned up on exit) ─────────────────────────────── TMPDIR=$(mktemp -d) cleanup() { # Shred all temp files before removing the directory find "$TMPDIR" -type f -exec shred -u {} \; rm -rf "$TMPDIR" } trap cleanup EXIT CA_CRT="$TMPDIR/ca.crt" CA_KEY="$TMPDIR/ca.key" HOST_CRT="$TMPDIR/host.crt" HOST_KEY="$TMPDIR/host.key" # ── extract CA cert and key from SOPS ────────────────────────────────────── echo "» Extracting CA from $CA_FILE ($CA_PREFIX)..." sops decrypt --extract "''${CA_SOPS_PATH}[\"ca-cert\"]" "$CA_FILE" > "$CA_CRT" sops decrypt --extract "''${CA_SOPS_PATH}[\"ca-key\"]" "$CA_FILE" > "$CA_KEY" # ── build nebula-cert sign args ───────────────────────────────────────────── SIGN_ARGS=( sign -name "$NAME" -ip "$IP" -ca-crt "$CA_CRT" -ca-key "$CA_KEY" -out-crt "$HOST_CRT" -out-key "$HOST_KEY" ) [[ -n "$NEBULA_GROUPS" ]] && SIGN_ARGS+=(-groups "$NEBULA_GROUPS") [[ -n "$DURATION" ]] && SIGN_ARGS+=(-duration "$DURATION") # ── sign the certificate ──────────────────────────────────────────────────── echo "» Signing certificate for $NAME ($IP)..." nebula-cert "''${SIGN_ARGS[@]}" echo "» Certificate details:" nebula-cert print -path "$HOST_CRT" # ── write cert and key back into the host SOPS file ──────────────────────── # sops set requires a JSON-encoded string value; use jq -Rs . to encode the # file contents and pipe via --value-stdin to avoid leaking secrets in ps. echo "» Writing ''${HOST_SECRET_NAME}-cert into $HOST_FILE ($HOST_PREFIX)..." jq -Rs . "$HOST_CRT" | sops set --value-stdin \ "$HOST_FILE" \ "''${HOST_SOPS_PATH}[\"''${HOST_SECRET_NAME}-cert\"]" echo "» Writing ''${HOST_SECRET_NAME}-key into $HOST_FILE ($HOST_PREFIX)..." jq -Rs . "$HOST_KEY" | sops set --value-stdin \ "$HOST_FILE" \ "''${HOST_SOPS_PATH}[\"''${HOST_SECRET_NAME}-key\"]" echo "" echo "✓ Done. Certificate for '$NAME' written to $HOST_FILE" echo " Rebuild the host to apply: sudo nixos-rebuild switch --flake .#" ''; meta = { description = "Sign a Nebula host certificate using a CA stored in SOPS"; mainProgram = "nebula-sign-cert"; }; }