Add secrets-bao with sec CLI; use in hosts; fix git helpers

This commit is contained in:
RingOfStorms (Joshua Bell) 2026-01-06 20:05:14 -06:00
parent c223dedb70
commit f8f93a97dc
6 changed files with 192 additions and 33 deletions

View file

@ -1,9 +1,9 @@
gcamp() { gcamp() {
VISUAL=vi EDITOR=vi gcam "$(gcpropose -a | vipe)" VISUAL=vi EDITOR=vi git commit -a -m "$(gcpropose -a | vipe)"
} }
gcmp() { gcmp() {
VISUAL=vi EDITOR=vi gcm "$(gcpropose | vipe)" VISUAL=vi EDITOR=vi git commit -m "$(gcpropose | vipe)"
} }
gcpropose() { gcpropose() {

View file

@ -196,6 +196,76 @@ let
in in
builtins.head (lib.strings.splitString "/" noProto); 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 <kv-path> [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" '' mkAgentConfig = pkgs.writeText "vault-agent.hcl" ''
vault { vault {
address = "${cfg.openBaoAddr}" address = "${cfg.openBaoAddr}"
@ -245,12 +315,6 @@ let
in in
{ {
options.age.secrets = lib.mkOption {
type = lib.types.attrsOf lib.types.anything;
default = { };
description = "Compatibility shim for modules that expect config.age.secrets.<name>.path.";
};
options.ringofstorms.secretsBao = { options.ringofstorms.secretsBao = {
enable = lib.mkEnableOption "Fetch runtime secrets via OpenBao"; enable = lib.mkEnableOption "Fetch runtime secrets via OpenBao";
@ -409,6 +473,7 @@ in
pkgs.openssl pkgs.openssl
pkgs.openbao pkgs.openbao
zitadelMintJwt zitadelMintJwt
sec
]; ];
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
@ -594,16 +659,7 @@ in
} }
) cfg.secrets) ) cfg.secrets)
]; ];
age.secrets = lib.mapAttrs' (
name: secret:
lib.nameValuePair name {
file = null;
path = secret.path;
}
) cfg.secrets;
} }
] ]
); );
} }

View file

@ -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 <kv-path> [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`

View file

@ -69,7 +69,6 @@
}) })
inputs.common.nixosModules.jetbrains_font inputs.common.nixosModules.jetbrains_font
inputs.secrets-bao.nixosModules.default
inputs.ros_neovim.nixosModules.default inputs.ros_neovim.nixosModules.default
({ ({
ringofstorms-nvim.includeAllRuntimeDependencies = true; ringofstorms-nvim.includeAllRuntimeDependencies = true;
@ -115,6 +114,7 @@
) )
inputs.common.nixosModules.remote_lio_builds inputs.common.nixosModules.remote_lio_builds
inputs.secrets-bao.nixosModules.default
( (
{ inputs, lib, ... }: { inputs, lib, ... }:
let let
@ -124,6 +124,12 @@
dependencies = [ "tailscaled" ]; dependencies = [ "tailscaled" ];
configChanges.services.tailscale.authKeyFile = "$SECRET_PATH"; 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 = { nix2github = {
owner = "josh"; owner = "josh";
group = "users"; group = "users";
@ -295,6 +301,50 @@
"com.spotify.Client" "com.spotify.Client"
"com.bitwarden.desktop" "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"
'';
};
};
} }
) )
]; ];

32
hosts/lio/flake.lock generated
View file

@ -63,20 +63,14 @@
}, },
"common": { "common": {
"locked": { "locked": {
"dir": "flakes/common", "path": "../../flakes/common",
"lastModified": 1767740224, "type": "path"
"narHash": "sha256-7yUQUw/7IMTBHy2EtuDggE8+NwUN3vDH5fwiTQDIrsI=",
"ref": "refs/heads/master",
"rev": "4bc645061b8c3108fdb3ee92a61dbe3e98ecdaea",
"revCount": 1082,
"type": "git",
"url": "https://git.joshuabell.xyz/ringofstorms/dotfiles"
}, },
"original": { "original": {
"dir": "flakes/common", "path": "../../flakes/common",
"type": "git", "type": "path"
"url": "https://git.joshuabell.xyz/ringofstorms/dotfiles" },
} "parent": []
}, },
"crane": { "crane": {
"locked": { "locked": {
@ -1321,7 +1315,8 @@
"nixpkgs-unstable": "nixpkgs-unstable", "nixpkgs-unstable": "nixpkgs-unstable",
"opencode": "opencode", "opencode": "opencode",
"ros_neovim": "ros_neovim", "ros_neovim": "ros_neovim",
"secrets": "secrets" "secrets": "secrets",
"secrets-bao": "secrets-bao"
} }
}, },
"ros_neovim": { "ros_neovim": {
@ -1460,6 +1455,17 @@
"url": "https://git.joshuabell.xyz/ringofstorms/dotfiles" "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": { "systems": {
"locked": { "locked": {
"lastModified": 1681028828, "lastModified": 1681028828,

View file

@ -6,10 +6,12 @@
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable"; nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
# Use relative to get current version for testing # Use relative to get current version for testing
# common.url = "path:../../flakes/common"; common.url = "path:../../flakes/common";
common.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/common"; # common.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/common";
# secrets.url = "path:../../flakes/secrets"; # secrets.url = "path:../../flakes/secrets";
secrets.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=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 = "path:../../flakes/flatpaks";
flatpaks.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/flatpaks"; flatpaks.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/flatpaks";
# beszel.url = "path:../../flakes/beszel"; # beszel.url = "path:../../flakes/beszel";
@ -95,6 +97,36 @@
common.nixosModules.zsh common.nixosModules.zsh
common.nixosModules.more_filesystems 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 beszel.nixosModules.agent
({ ({
beszelAgent = { beszelAgent = {