From 9e0eb5b5833f9fab8224f629fc2804ed1420e7b4 Mon Sep 17 00:00:00 2001 From: eric Date: Wed, 18 Mar 2026 03:15:11 +0100 Subject: [PATCH] fix: correct field from bao --- README.md | 9 ++-- hosts/lab/configuration.nix | 30 +++++++++++ hosts/lab/disko.nix | 47 ++++++++++++++++++ hosts/lab/hardware-configuration.nix | 5 ++ pkgs/helpers/__pycache__/cli.cpython-313.pyc | Bin 0 -> 70382 bytes pkgs/helpers/cli.py | 27 +++++----- .../__pycache__/test_cli.cpython-313.pyc | Bin 0 -> 6652 bytes pkgs/helpers/tests/test_cli.py | 23 +++++++++ 8 files changed, 125 insertions(+), 16 deletions(-) create mode 100644 hosts/lab/configuration.nix create mode 100644 hosts/lab/disko.nix create mode 100644 hosts/lab/hardware-configuration.nix create mode 100644 pkgs/helpers/__pycache__/cli.cpython-313.pyc create mode 100644 pkgs/helpers/tests/__pycache__/test_cli.cpython-313.pyc diff --git a/README.md b/README.md index 8a4b53f..0ebdd84 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ This repo currently provisions NixOS hosts with: - New machines are installed with `nixos-anywhere` - Ongoing changes are deployed with `colmena` - Hosts authenticate to OpenBao as clients -- Tailscale auth keys are fetched from OpenBao namespace `it`, path `tailscale`, field `auth_key` +- Tailscale auth keys are fetched from OpenBao namespace `it`, KV mount `kv`, path `tailscale`, field `auth_key` - Public SSH must work independently of Tailscale for first access and recovery ## Repo Layout @@ -227,6 +227,7 @@ The host uses: - OpenBao address: `https://secrets.api.nodeiwest.se` - namespace: `it` +- KV mount: `kv` - auth mount: `auth/approle` - secret path: `tailscale` - field: `auth_key` @@ -247,12 +248,12 @@ Create a minimal read-only policy for the Tailscale secret. If the secret is accessible as: ```bash -BAO_NAMESPACE=it bao kv get tailscale +BAO_NAMESPACE=it bao kv get -mount=kv tailscale ``` then create the matching read policy for that mount. -Example shape for a KV v2 mount named `kv`: +Example shape for the KV v2 mount `kv`: ```hcl path "kv/data/tailscale" { @@ -341,7 +342,7 @@ On first boot: 1. `vault-agent-tailscale.service` starts using `pkgs.openbao` 2. it authenticates to OpenBao with AppRole -3. it renders `auth_key` from `it/tailscale` to `/run/nodeiwest/tailscale-auth-key` +3. it renders `auth_key` from namespace `it`, KV mount `kv`, path `tailscale` to `/run/nodeiwest/tailscale-auth-key` 4. `nodeiwest-tailscale-authkey-ready.service` waits until that file exists 5. `tailscaled-autoconnect.service` uses that file and runs `tailscale up --ssh` diff --git a/hosts/lab/configuration.nix b/hosts/lab/configuration.nix new file mode 100644 index 0000000..f0de599 --- /dev/null +++ b/hosts/lab/configuration.nix @@ -0,0 +1,30 @@ +{ lib, ... }: +{ + # Generated by nodeiwest host init. + imports = [ + ./disko.nix + ./hardware-configuration.nix + ]; + + networking.hostName = "lab"; + networking.useDHCP = lib.mkDefault true; + + time.timeZone = "UTC"; + + boot.loader.efi.canTouchEfiVariables = true; + boot.loader.grub = { + enable = true; + efiSupport = true; + device = "nodev"; + }; + + nodeiwest.ssh.userCAPublicKeys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE6c2oMkM7lLg9qWHVgbrFaFBDrrFyynFlPviiydQdFi openbao-user-ca" + ]; + + nodeiwest.tailscale.openbao = { + enable = true; + }; + + system.stateVersion = "25.05"; +} diff --git a/hosts/lab/disko.nix b/hosts/lab/disko.nix new file mode 100644 index 0000000..377830e --- /dev/null +++ b/hosts/lab/disko.nix @@ -0,0 +1,47 @@ +{ + lib, + ... +}: +{ + # Generated by nodeiwest host init. + # Replace the disk only if the provider exposes a different primary device. + disko.devices = { + disk.main = { + type = "disk"; + device = lib.mkDefault "/dev/sda 11"; + content = { + type = "gpt"; + partitions = { + ESP = { + priority = 1; + name = "ESP"; + start = "1MiB"; + end = "512MiB"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "umask=0077" ]; + }; + }; + swap = { + size = "4GiB"; + content = { + type = "swap"; + resumeDevice = true; + }; + }; + root = { + size = "100%"; + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + }; + }; + }; + }; + }; + }; +} diff --git a/hosts/lab/hardware-configuration.nix b/hosts/lab/hardware-configuration.nix new file mode 100644 index 0000000..3f6bc7b --- /dev/null +++ b/hosts/lab/hardware-configuration.nix @@ -0,0 +1,5 @@ +{ ... }: +{ + # Placeholder generated by nodeiwest host init. + # nixos-anywhere will replace this with the generated hardware config. +} diff --git a/pkgs/helpers/__pycache__/cli.cpython-313.pyc b/pkgs/helpers/__pycache__/cli.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a14814e469cc1633b8515ab751b45246d9079b0 GIT binary patch literal 70382 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 r5B{PsGWr argparse.ArgumentParser: init_host_parser = openbao_subparsers.add_parser("init-host", help="Create policy, AppRole, and bootstrap files.") init_host_parser.add_argument("--name", required=True, help="Host name, e.g. vps2.") init_host_parser.add_argument("--namespace", default="it", help="OpenBao namespace. Default: it.") + init_host_parser.add_argument("--kv-mount", default="kv", help="KV v2 mount name. Default: kv.") init_host_parser.add_argument("--secret-path", default="tailscale", help="Logical secret path. Default: tailscale.") - init_host_parser.add_argument("--field", default="auth_key", help="Secret field. Default: auth_key.") + init_host_parser.add_argument("--field", default="CLIENT_SECRET", help="Secret field. Default: CLIENT_SECRET.") init_host_parser.add_argument("--auth-path", default="auth/approle", help="AppRole auth mount. Default: auth/approle.") init_host_parser.add_argument("--policy-name", help="Policy name. Default: tailscale-.") init_host_parser.add_argument("--role-name", help="AppRole name. Default: tailscale-.") @@ -351,7 +352,7 @@ def cmd_openbao_init_host(args: argparse.Namespace) -> int: role_id_path = output_dir / "var" / "lib" / "nodeiwest" / "openbao-approle-role-id" secret_id_path = output_dir / "var" / "lib" / "nodeiwest" / "openbao-approle-secret-id" - secret_data = bao_kv_get(args.namespace, args.secret_path) + secret_data = bao_kv_get(args.namespace, args.kv_mount, args.secret_path) fields = secret_data.get("data", {}) if isinstance(fields.get("data"), dict): fields = fields["data"] @@ -363,11 +364,12 @@ def cmd_openbao_init_host(args: argparse.Namespace) -> int: if args.kv_mount_path: policy_content = render_openbao_policy(args.kv_mount_path) else: - policy_content = derive_openbao_policy(args.namespace, args.secret_path) + policy_content = derive_openbao_policy(args.namespace, args.kv_mount, args.secret_path) role_command = build_approle_write_command(args.auth_path, role_name, policy_name, args.cidr) print(f"Namespace: {args.namespace}") + print(f"KV mount: {args.kv_mount}") print(f"Policy name: {policy_name}") print(f"Role name: {role_name}") print(f"Secret path: {args.secret_path}") @@ -1022,13 +1024,13 @@ def ensure_bao_authenticated() -> None: ) -def bao_kv_get(namespace: str, secret_path: str) -> dict[str, Any]: +def bao_kv_get(namespace: str, kv_mount: str, secret_path: str) -> dict[str, Any]: result = run_command( - ["bao", "kv", "get", "-format=json", secret_path], + ["bao", "kv", "get", f"-mount={kv_mount}", "-format=json", secret_path], env={"BAO_NAMESPACE": namespace}, next_fix=( - "Check BAO_ADDR, BAO_NAMESPACE, and the logical secret path. " - "If the path or mount is ambiguous, re-run with --kv-mount-path." + "Check BAO_ADDR, BAO_NAMESPACE, the KV mount, and the logical secret path. " + "If the KV mount is not the default, re-run with --kv-mount." ), ) try: @@ -1037,12 +1039,13 @@ def bao_kv_get(namespace: str, secret_path: str) -> dict[str, Any]: raise NodeiwestError(f"Failed to parse `bao kv get` JSON output: {exc}") from exc -def derive_openbao_policy(namespace: str, secret_path: str) -> str: +def derive_openbao_policy(namespace: str, kv_mount: str, secret_path: str) -> str: result = run_command( - ["bao", "kv", "get", "-output-policy", secret_path], + ["bao", "kv", "get", f"-mount={kv_mount}", "-output-policy", secret_path], env={"BAO_NAMESPACE": namespace}, next_fix=( - "Check BAO_ADDR, BAO_NAMESPACE, and the logical secret path. " + "Check BAO_ADDR, BAO_NAMESPACE, the KV mount, and the logical secret path. " + "If the KV mount is not the default, re-run with --kv-mount. " "If policy derivation still does not match your mount layout, re-run with --kv-mount-path." ), ) @@ -1220,8 +1223,8 @@ def infer_verify_failures( messages.append("Missing AppRole files on the host. Check /var/lib/nodeiwest/openbao-approle-role-id and ...secret-id.") if any(fragment in combined for fragment in ["invalid secret id", "permission denied", "approle", "failed to authenticate"]): messages.append("OpenBao AppRole authentication failed. Re-check the role, secret_id, namespace, and auth mount.") - if any(fragment in combined for fragment in ["auth_key", "timed out waiting for rendered tailscale auth key", "no data", "secret path"]): - messages.append("OpenBao rendered no Tailscale auth key. Check the secret path, KV mount path, and auth_key field.") + if any(fragment in combined for fragment in ["CLIENT_SECRET", "timed out waiting for rendered tailscale auth key", "no data", "secret path"]): + messages.append("OpenBao rendered no Tailscale auth key. Check the secret path, KV mount path, and CLIENT_SECRET field.") if tailscale_status.returncode != 0 or "logged out" in (tailscale_status.stdout or "").lower(): messages.append("Tailscale autoconnect is blocked. Check tailscaled-autoconnect, the rendered auth key, and outbound access to Tailscale.") diff --git a/pkgs/helpers/tests/__pycache__/test_cli.cpython-313.pyc b/pkgs/helpers/tests/__pycache__/test_cli.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dba937d410b2bd7fec1b8e7769e3c7c721c8b560 GIT binary patch literal 6652 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 None: + completed = mock.Mock() + completed.stdout = '{"data": {"data": {"CLIENT_ID": "x"}}}' + with mock.patch.object(cli, "run_command", return_value=completed) as run_command: + data = cli.bao_kv_get("it", "kv", "tailscale") + + self.assertEqual(data["data"]["data"]["CLIENT_ID"], "x") + command = run_command.call_args.args[0] + self.assertEqual(command, ["bao", "kv", "get", "-mount=kv", "-format=json", "tailscale"]) + self.assertEqual(run_command.call_args.kwargs["env"], {"BAO_NAMESPACE": "it"}) + + def test_derive_openbao_policy_uses_explicit_kv_mount(self) -> None: + completed = mock.Mock() + completed.stdout = 'path "kv/data/tailscale" { capabilities = ["read"] }\n' + with mock.patch.object(cli, "run_command", return_value=completed) as run_command: + policy = cli.derive_openbao_policy("it", "kv", "tailscale") + + self.assertIn('path "kv/data/tailscale"', policy) + command = run_command.call_args.args[0] + self.assertEqual(command, ["bao", "kv", "get", "-mount=kv", "-output-policy", "tailscale"]) + self.assertEqual(run_command.call_args.kwargs["env"], {"BAO_NAMESPACE": "it"}) + if __name__ == "__main__": unittest.main()