187 lines
7.3 KiB
Nix
187 lines
7.3 KiB
Nix
{
|
|
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:
|
|
# <ca-sops-file> at YAML path <ca-prefix>/ca-cert and <ca-prefix>/ca-key
|
|
#
|
|
# The new cert+key are written to:
|
|
# <host-sops-file> at YAML paths
|
|
# <host-prefix>/<host-secret-name>-cert
|
|
# <host-prefix>/<host-secret-name>-key
|
|
#
|
|
# Usage:
|
|
# nebula-sign-cert \
|
|
# --name <node-name> # e.g. "nas" — used in the cert
|
|
# --ip <overlay-ip/mask> # e.g. "10.1.1.2/24"
|
|
# --ca-file <path/to/ca-secrets.yaml>
|
|
# --ca-prefix <sops-key-prefix> # e.g. "pi5/nebula"
|
|
# --host-file <path/to/host-secrets.yaml>
|
|
# --host-prefix <sops-key-prefix> # e.g. "jallen-nas/nebula"
|
|
# --host-secret-name <name> # e.g. "nas" (cert stored as nas-cert/nas-key)
|
|
# [--groups <group1,group2>] # optional Nebula groups
|
|
# [--duration <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 <node-name> \\"
|
|
echo " --ip <overlay-ip/mask> \\"
|
|
echo " --ca-file <path/to/ca-sops-file.yaml> \\"
|
|
echo " --ca-prefix <sops-key-prefix> (e.g. pi5/nebula) \\"
|
|
echo " --host-file <path/to/host-sops-file.yaml> \\"
|
|
echo " --host-prefix <sops-key-prefix> (e.g. jallen-nas/nebula) \\"
|
|
echo " --host-secret-name <name> (e.g. nas) \\"
|
|
echo " [--groups <group1,group2>] \\"
|
|
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 .#<hostname>"
|
|
'';
|
|
|
|
meta = {
|
|
description = "Sign a Nebula host certificate using a CA stored in SOPS";
|
|
mainProgram = "nebula-sign-cert";
|
|
};
|
|
}
|