From f8f93a97dc9cdd86869fb3a526c453aa7697056b Mon Sep 17 00:00:00 2001 From: "RingOfStorms (Joshua Bell)" Date: Tue, 6 Jan 2026 20:05:14 -0600 Subject: [PATCH] Add secrets-bao with sec CLI; use in hosts; fix git helpers --- .../common/nix_modules/git/gcpropose.func.sh | 4 +- flakes/secrets-bao/nixos-module.nix | 86 +++++++++++++++---- flakes/secrets-bao/readme.md | 15 ++++ hosts/juni/flake.nix | 52 ++++++++++- hosts/lio/flake.lock | 32 ++++--- hosts/lio/flake.nix | 36 +++++++- 6 files changed, 192 insertions(+), 33 deletions(-) create mode 100644 flakes/secrets-bao/readme.md diff --git a/flakes/common/nix_modules/git/gcpropose.func.sh b/flakes/common/nix_modules/git/gcpropose.func.sh index 32e51da7..2c3ff0f0 100644 --- a/flakes/common/nix_modules/git/gcpropose.func.sh +++ b/flakes/common/nix_modules/git/gcpropose.func.sh @@ -1,9 +1,9 @@ gcamp() { - VISUAL=vi EDITOR=vi gcam "$(gcpropose -a | vipe)" + VISUAL=vi EDITOR=vi git commit -a -m "$(gcpropose -a | vipe)" } gcmp() { - VISUAL=vi EDITOR=vi gcm "$(gcpropose | vipe)" + VISUAL=vi EDITOR=vi git commit -m "$(gcpropose | vipe)" } gcpropose() { diff --git a/flakes/secrets-bao/nixos-module.nix b/flakes/secrets-bao/nixos-module.nix index eb1c2ca7..1b03034b 100644 --- a/flakes/secrets-bao/nixos-module.nix +++ b/flakes/secrets-bao/nixos-module.nix @@ -196,6 +196,76 @@ let in builtins.head (lib.strings.splitString "/" noProto); + sec = pkgs.writeShellScriptBin "sec" '' + #!/usr/bin/env bash + set -euo pipefail + + if [ "$(${pkgs.coreutils}/bin/id -u)" -ne 0 ]; then + exec ${pkgs.sudo}/bin/sudo "$0" "$@" + fi + + vault_addr=${lib.escapeShellArg cfg.openBaoAddr} + jwt_mount_path=${lib.escapeShellArg cfg.jwtAuthMountPath} + role=${lib.escapeShellArg cfg.openBaoRole} + jwt_path=${lib.escapeShellArg cfg.zitadelJwtPath} + token_path=${lib.escapeShellArg cfg.vaultAgentTokenPath} + + usage() { + echo "usage: sec [field]" >&2 + echo " examples:" >&2 + echo " sec machines/home_roaming/test value" >&2 + echo " sec kv/data/machines/home_roaming/test value" >&2 + } + + die() { + echo "sec: $*" >&2 + exit 1 + } + + kv_path="''${1-}" + field="''${2:-value}" + + if [ -z "$kv_path" ] || [ "$kv_path" = "-h" ] || [ "$kv_path" = "--help" ]; then + usage + exit 2 + fi + + export VAULT_ADDR="$vault_addr" + + token="" + + if [ -r "$token_path" ] && [ -s "$token_path" ]; then + token="$(cat "$token_path")" + else + if [ ! -r "$jwt_path" ] || [ ! -s "$jwt_path" ]; then + die "Missing JWT at $jwt_path (try: systemctl start zitadel-mint-jwt)" + fi + + token="$(${pkgs.openbao}/bin/bao write -field=token "$jwt_mount_path/login" role="$role" jwt="$(cat "$jwt_path")")" + fi + + if [ -z "$token" ] || [ "$token" = "null" ]; then + die "Failed to get OpenBao token" + fi + + export VAULT_TOKEN="$token" + + # Accept either KV v2 logical paths (machines/foo/bar) or raw API paths (kv/data/machines/foo/bar). + if [[ "$kv_path" == kv/data/* ]]; then + json="$(${pkgs.openbao}/bin/bao read -format=json "$kv_path")" + else + json="$(${pkgs.openbao}/bin/bao kv get -format=json -mount=kv "$kv_path")" + fi + + value="$(${pkgs.jq}/bin/jq -er --arg field "$field" '.data.data[$field]' <<<"$json" 2>/dev/null || true)" + + if [ -z "$value" ] || [ "$value" = "null" ]; then + die "Field not found: $field" + fi + + printf '%s\n' "$value" + ''; + mkAgentConfig = pkgs.writeText "vault-agent.hcl" '' vault { address = "${cfg.openBaoAddr}" @@ -245,12 +315,6 @@ let in { - options.age.secrets = lib.mkOption { - type = lib.types.attrsOf lib.types.anything; - default = { }; - description = "Compatibility shim for modules that expect config.age.secrets..path."; - }; - options.ringofstorms.secretsBao = { enable = lib.mkEnableOption "Fetch runtime secrets via OpenBao"; @@ -409,6 +473,7 @@ in pkgs.openssl pkgs.openbao zitadelMintJwt + sec ]; systemd.tmpfiles.rules = [ @@ -594,16 +659,7 @@ in } ) cfg.secrets) ]; - - age.secrets = lib.mapAttrs' ( - name: secret: - lib.nameValuePair name { - file = null; - path = secret.path; - } - ) cfg.secrets; } - ] ); } diff --git a/flakes/secrets-bao/readme.md b/flakes/secrets-bao/readme.md new file mode 100644 index 00000000..98d77b1a --- /dev/null +++ b/flakes/secrets-bao/readme.md @@ -0,0 +1,15 @@ +- Create machine in zitadel and generate a key. Put that at /machine-key.json +- sudo chmod + +## CLI + +If `ringofstorms.secretsBao.enable = true`, you also get a `sec` helper. + +It reads `/run/openbao/*` files, so it will `sudo` itself if needed. + +- `sec [field]` reads a field (default: `value`) from KV v2. +- It reuses `/run/openbao/vault-agent.token` when available, otherwise it logs in via the same jwt auth mount path using `/run/openbao/zitadel.jwt`. + +Example: + +- `sec machines/home_roaming/test value` diff --git a/hosts/juni/flake.nix b/hosts/juni/flake.nix index 92289ab6..9ec10aef 100644 --- a/hosts/juni/flake.nix +++ b/hosts/juni/flake.nix @@ -69,7 +69,6 @@ }) inputs.common.nixosModules.jetbrains_font - inputs.secrets-bao.nixosModules.default inputs.ros_neovim.nixosModules.default ({ ringofstorms-nvim.includeAllRuntimeDependencies = true; @@ -115,6 +114,7 @@ ) inputs.common.nixosModules.remote_lio_builds + inputs.secrets-bao.nixosModules.default ( { inputs, lib, ... }: let @@ -124,6 +124,12 @@ dependencies = [ "tailscaled" ]; configChanges.services.tailscale.authKeyFile = "$SECRET_PATH"; }; + "atuin-key-josh" = { + owner = "josh"; + group = "users"; + mode = "0400"; + template = ''{{- with secret "kv/data/machines/home_roaming/atuin-key-josh" -}}{{ printf "%s\n%s\n%s" .Data.data.user .Data.data.password .Data.data.value }}{{- end -}}''; + }; nix2github = { owner = "josh"; group = "users"; @@ -295,6 +301,50 @@ "com.spotify.Client" "com.bitwarden.desktop" ]; + + systemd.services.atuin-autologin = { + description = "Auto-login to Atuin (if logged out)"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" "openbao-secret-atuin-key-josh.service" ]; + wants = [ "network-online.target" "openbao-secret-atuin-key-josh.service" ]; + requires = [ "openbao-secret-atuin-key-josh.service" ]; + + serviceConfig = { + Type = "oneshot"; + User = "josh"; + Group = "users"; + Environment = [ + "HOME=/home/josh" + "XDG_CONFIG_HOME=/home/josh/.config" + "XDG_DATA_HOME=/home/josh/.local/share" + ]; + + ExecStart = pkgs.writeShellScript "atuin-autologin" '' + #!/usr/bin/env bash + set -euo pipefail + + secret="/run/secrets/atuin-key-josh" + if [ ! -s "$secret" ]; then + echo "Missing atuin secret at $secret" >&2 + exit 1 + fi + + # status exits non-zero when logged out. + out="$(${pkgs.atuin}/bin/atuin status 2>&1)" && exit 0 + + if [[ "$out" != *"You are not logged in"* ]]; then + echo "$out" >&2 + exit 1 + fi + + username="$(${pkgs.coreutils}/bin/sed -n '1p' "$secret")" + password="$(${pkgs.coreutils}/bin/sed -n '2p' "$secret")" + key="$(${pkgs.coreutils}/bin/sed -n '3p' "$secret")" + + exec ${pkgs.atuin}/bin/atuin login --username "$username" --password "$password" --key "$key" + ''; + }; + }; } ) ]; diff --git a/hosts/lio/flake.lock b/hosts/lio/flake.lock index be525817..9d2f4ea8 100644 --- a/hosts/lio/flake.lock +++ b/hosts/lio/flake.lock @@ -63,20 +63,14 @@ }, "common": { "locked": { - "dir": "flakes/common", - "lastModified": 1767740224, - "narHash": "sha256-7yUQUw/7IMTBHy2EtuDggE8+NwUN3vDH5fwiTQDIrsI=", - "ref": "refs/heads/master", - "rev": "4bc645061b8c3108fdb3ee92a61dbe3e98ecdaea", - "revCount": 1082, - "type": "git", - "url": "https://git.joshuabell.xyz/ringofstorms/dotfiles" + "path": "../../flakes/common", + "type": "path" }, "original": { - "dir": "flakes/common", - "type": "git", - "url": "https://git.joshuabell.xyz/ringofstorms/dotfiles" - } + "path": "../../flakes/common", + "type": "path" + }, + "parent": [] }, "crane": { "locked": { @@ -1321,7 +1315,8 @@ "nixpkgs-unstable": "nixpkgs-unstable", "opencode": "opencode", "ros_neovim": "ros_neovim", - "secrets": "secrets" + "secrets": "secrets", + "secrets-bao": "secrets-bao" } }, "ros_neovim": { @@ -1460,6 +1455,17 @@ "url": "https://git.joshuabell.xyz/ringofstorms/dotfiles" } }, + "secrets-bao": { + "locked": { + "path": "../../flakes/secrets-bao", + "type": "path" + }, + "original": { + "path": "../../flakes/secrets-bao", + "type": "path" + }, + "parent": [] + }, "systems": { "locked": { "lastModified": 1681028828, diff --git a/hosts/lio/flake.nix b/hosts/lio/flake.nix index 4154e110..b2ae02fc 100644 --- a/hosts/lio/flake.nix +++ b/hosts/lio/flake.nix @@ -6,10 +6,12 @@ nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable"; # Use relative to get current version for testing - # common.url = "path:../../flakes/common"; - common.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/common"; + common.url = "path:../../flakes/common"; + # common.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/common"; # secrets.url = "path:../../flakes/secrets"; secrets.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/secrets"; + secrets-bao.url = "path:../../flakes/secrets-bao"; + # secrets-bao.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/secrets-bao"; # flatpaks.url = "path:../../flakes/flatpaks"; flatpaks.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/flatpaks"; # beszel.url = "path:../../flakes/beszel"; @@ -95,6 +97,36 @@ common.nixosModules.zsh common.nixosModules.more_filesystems + inputs.secrets-bao.nixosModules.default + ( + { inputs, lib, ... }: + let + secrets = { + headscale_auth = { + kvPath = "kv/data/machines/home_roaming/headscale_auth"; + # dependencies = [ "tailscaled" ]; + # configChanges.services.tailscale.authKeyFile = "$SECRET_PATH"; # TODO remove secrets and enable this + }; + }; + in + lib.mkMerge [ + { + ringofstorms.secretsBao = { + enable = true; + zitadelKeyPath = "/machine-key.json"; + openBaoAddr = "https://sec.joshuabell.xyz"; + jwtAuthMountPath = "auth/zitadel-jwt"; + openBaoRole = "machines"; + zitadelIssuer = "https://sso.joshuabell.xyz"; + zitadelProjectId = "344379162166820867"; + inherit secrets; + }; + } + (inputs.secrets-bao.lib.applyConfigChanges secrets) + (inputs.secrets-bao.lib.applyHmChanges secrets) + ] + ) + beszel.nixosModules.agent ({ beszelAgent = {