diff --git a/flakes/secrets-bao/nixos-module.nix b/flakes/secrets-bao/nixos-module.nix index 81e46e8a..5b1a001d 100644 --- a/flakes/secrets-bao/nixos-module.nix +++ b/flakes/secrets-bao/nixos-module.nix @@ -253,6 +253,7 @@ in systemd.tmpfiles.rules = [ "d /run/openbao 0700 root root - -" + "f /run/openbao/zitadel.jwt 0400 root root - -" "d /run/secrets 0711 root root - -" ]; @@ -267,16 +268,99 @@ in "NetworkManager-wait-online.service" "systemd-resolved.service" ]; - wants = [ "network-online.target" "NetworkManager-wait-online.service" "systemd-resolved.service" ]; + wants = [ + "network-online.target" + "NetworkManager-wait-online.service" + "systemd-resolved.service" + ]; serviceConfig = { Type = "oneshot"; User = "root"; Group = "root"; Restart = "on-failure"; + RestartSec = "30s"; + TimeoutStartSec = "2min"; + UMask = "0077"; + + ExecStart = pkgs.writeShellScript "zitadel-mint-jwt-service" '' + #!/usr/bin/env bash + set -euo pipefail + + if [ ! -d "/run/openbao" ]; then + ${pkgs.coreutils}/bin/mkdir -p /run/openbao + ${pkgs.coreutils}/bin/chmod 0700 /run/openbao + fi + + if [ ! -f "${cfg.zitadelKeyPath}" ]; then + echo "Missing Zitadel key JSON at ${cfg.zitadelKeyPath}" >&2 + exit 1 + fi + + echo "zitadel-mint-jwt: starting (host=${zitadelHost})" >&2 + + jwt_is_valid() { + local token="$1" + local payload_b64 payload_json exp now + + payload_b64="$(${pkgs.coreutils}/bin/printf '%s' "$token" | ${pkgs.coreutils}/bin/cut -d. -f2)" + payload_b64="$(${pkgs.coreutils}/bin/printf '%s' "$payload_b64" | ${pkgs.gnused}/bin/sed -e 's/-/+/g' -e 's/_/\//g')" + + case $((${pkgs.coreutils}/bin/printf '%s' "$payload_b64" | ${pkgs.coreutils}/bin/wc -c)) in + *1) payload_b64="$payload_b64=" ;; + *2) payload_b64="$payload_b64==" ;; + *3) : ;; + *0) : ;; + esac + + payload_json="$(${pkgs.coreutils}/bin/printf '%s' "$payload_b64" | ${pkgs.coreutils}/bin/base64 -d 2>/dev/null || true)" + exp="$(${pkgs.jq}/bin/jq -r '.exp // empty' <<<"$payload_json" 2>/dev/null || true)" + if [ -z "$exp" ]; then + return 1 + fi + + now="$(${pkgs.coreutils}/bin/date +%s)" + if [ "$exp" -gt $(( now + 60 )) ]; then + return 0 + fi + return 1 + } + + if [ -s "${cfg.zitadelJwtPath}" ] && jwt_is_valid "$(cat "${cfg.zitadelJwtPath}")"; then + echo "zitadel-mint-jwt: existing token still valid; skipping" >&2 + exit 0 + fi + + jwt="$(${mkJwtMintScript})" + + if [ -z "$jwt" ] || [ "$jwt" = "null" ]; then + echo "Failed to mint Zitadel access token" >&2 + exit 1 + fi + + tmp="$(${pkgs.coreutils}/bin/mktemp)" + trap '${pkgs.coreutils}/bin/rm -f "$tmp"' EXIT + ${pkgs.coreutils}/bin/printf '%s' "$jwt" > "$tmp" + + # In-place update so the agent's file watcher sees changes. + ${pkgs.coreutils}/bin/cat "$tmp" > "${cfg.zitadelJwtPath}" + ${pkgs.coreutils}/bin/chmod 0400 "${cfg.zitadelJwtPath}" || true + ''; + }; + }; + + vault-agent = { + description = "OpenBao agent for rendering secrets"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" "zitadel-mint-jwt.service" ]; + wants = [ "network-online.target" "zitadel-mint-jwt.service" ]; + + serviceConfig = { + Type = "simple"; + User = "root"; + Group = "root"; + Restart = "on-failure"; RestartSec = "10s"; - - TimeoutStartSec = "30s"; UMask = "0077"; ExecStart = "${pkgs.openbao}/bin/bao agent -log-level=debug -config=${mkAgentConfig}"; diff --git a/hosts/juni/hardware-mounts.nix b/hosts/juni/hardware-mounts.nix index eaca3a64..ba86146f 100644 --- a/hosts/juni/hardware-mounts.nix +++ b/hosts/juni/hardware-mounts.nix @@ -14,9 +14,7 @@ let IMPERMANENCE = true; ENCRYPTED = true; - USB_KEY = null; - - USB_KEY_PATH = if USB_KEY == null then "" else USB_KEY; + USB_KEY = "/dev/disk/by-uuid/ea3e20f6-c7f2-407c-b9a2-00b4ac000178"; primaryDeviceUnit = "${utils.escapeSystemdPath PRIMARY}.device"; in @@ -221,68 +219,72 @@ lib.mkMerge [ TTYVTDisallocate = true; }; - script = '' - unlock_with_usb_key() { - if [[ -z "${USB_KEY_PATH}" ]]; then - return 2 - fi - - echo "Searching for USB unlock key..." - KEY_FOUND=0 - # 4 second search - for i in {1..40}; do - if [ -e "${USB_KEY_PATH}" ]; then - KEY_FOUND=1 - break + script = + let + USB_KEY_PATH = if USB_KEY == null then "" else USB_KEY; + in + '' + unlock_with_usb_key() { + if [[ -z "${USB_KEY_PATH}" ]]; then + return 2 fi - sleep 0.1 - done - if [ "$KEY_FOUND" -ne 1 ]; then - echo "USB key not found within timeout." - return 2 - fi + echo "Searching for USB unlock key..." + KEY_FOUND=0 + # 4 second search + for i in {1..40}; do + if [ -e "${USB_KEY_PATH}" ]; then + KEY_FOUND=1 + break + fi + sleep 0.1 + done - echo "USB key found at ${USB_KEY_PATH}. Attempting unlock..." - mkdir -p /tmp/usb_key_mount + if [ "$KEY_FOUND" -ne 1 ]; then + echo "USB key not found within timeout." + return 2 + fi - # Mount read-only - if ! mount -t bcachefs -o ro "${USB_KEY_PATH}" /tmp/usb_key_mount; then - echo "Failed to mount USB key device." - return 1 - fi + echo "USB key found at ${USB_KEY_PATH}. Attempting unlock..." + mkdir -p /tmp/usb_key_mount - if ${pkgs.bcachefs-tools}/bin/bcachefs unlock -f /tmp/usb_key_mount/key "${PRIMARY}"; then - umount /tmp/usb_key_mount || true - echo "Bcachefs unlock successful (USB key)!" - return 0 - fi + # Mount read-only + if ! mount -t bcachefs -o ro "${USB_KEY_PATH}" /tmp/usb_key_mount; then + echo "Failed to mount USB key device." + return 1 + fi - umount /tmp/usb_key_mount || true - echo "Failed to unlock with USB key." - return 1 - } - - unlock_with_passphrase_until_success() { - echo "Unlocking ${PRIMARY} (will retry on failure)..." - while true; do - if ${pkgs.bcachefs-tools}/bin/bcachefs unlock "${PRIMARY}"; then - echo "Bcachefs unlock successful (passphrase)!" + if ${pkgs.bcachefs-tools}/bin/bcachefs unlock -f /tmp/usb_key_mount/key "${PRIMARY}"; then + umount /tmp/usb_key_mount || true + echo "Bcachefs unlock successful (USB key)!" return 0 fi - echo "Unlock failed. Try again." - sleep 0.2 - done - } - # 1) Optional USB key unlock attempt (if configured) - if unlock_with_usb_key; then - exit 0 - fi + umount /tmp/usb_key_mount || true + echo "Failed to unlock with USB key." + return 1 + } - # 2) If USB key not configured or failed, prompt for passphrase and retry - unlock_with_passphrase_until_success - ''; + unlock_with_passphrase_until_success() { + echo "Unlocking ${PRIMARY} (will retry on failure)..." + while true; do + if ${pkgs.bcachefs-tools}/bin/bcachefs unlock "${PRIMARY}"; then + echo "Bcachefs unlock successful (passphrase)!" + return 0 + fi + echo "Unlock failed. Try again." + sleep 0.2 + done + } + + # 1) Optional USB key unlock attempt (if configured) + if unlock_with_usb_key; then + exit 0 + fi + + # 2) If USB key not configured or failed, prompt for passphrase and retry + unlock_with_passphrase_until_success + ''; }; }) ]