feat(secrets): OpenBao-only backend via castle.yaml + per-node prefix
- backend selected by castle.yaml 'secrets:' block (env overrides); no file fallback in openbao mode (bootstrap token still read from file) - route direct-read secrets (public DNS token, supabase pw) through read_secret - OpenBaoBackend node_prefix: read <prefix>/<name> then shared <name>, so a shared vault serves per-node overrides (e.g. each node's postgres password) - tests updated (fallback removed, settings + node_prefix selection)
This commit is contained in:
@@ -38,30 +38,36 @@ def test_build_backend_defaults_to_file(tmp_path: Path, monkeypatch) -> None:
|
||||
assert isinstance(build_backend(tmp_path), FileSecretBackend)
|
||||
|
||||
|
||||
def test_build_backend_openbao_selected(tmp_path: Path, monkeypatch) -> None:
|
||||
def test_build_backend_openbao_via_env(tmp_path: Path, monkeypatch) -> None:
|
||||
monkeypatch.setenv("CASTLE_SECRET_BACKEND", "openbao")
|
||||
assert isinstance(build_backend(tmp_path), OpenBaoBackend)
|
||||
|
||||
|
||||
def test_openbao_falls_back_to_file_when_unreachable(tmp_path: Path) -> None:
|
||||
"""An unreachable vault (or empty token) resolves via the file fallback."""
|
||||
def test_build_backend_openbao_via_settings(tmp_path: Path, monkeypatch) -> None:
|
||||
"""The castle.yaml `secrets:` block selects the backend (env still overrides)."""
|
||||
monkeypatch.delenv("CASTLE_SECRET_BACKEND", raising=False)
|
||||
settings = {"backend": "openbao", "addr": "https://vault:8200", "mount": "castle"}
|
||||
assert isinstance(build_backend(tmp_path, settings), OpenBaoBackend)
|
||||
|
||||
|
||||
def test_openbao_unreachable_returns_none_no_fallback(tmp_path: Path) -> None:
|
||||
"""No file fallback: an unreachable vault returns None even if a file exists."""
|
||||
(tmp_path / "ONLY_IN_FILE").write_text("from-file")
|
||||
backend = OpenBaoBackend(
|
||||
addr="http://127.0.0.1:1", # nothing listening
|
||||
token="dummy",
|
||||
mount="castle",
|
||||
fallback=FileSecretBackend(tmp_path),
|
||||
)
|
||||
assert backend.read("ONLY_IN_FILE") == "from-file"
|
||||
backend = OpenBaoBackend(addr="http://127.0.0.1:1", token="dummy", mount="castle")
|
||||
assert backend.read("ONLY_IN_FILE") is None
|
||||
assert backend.read("NOT_ANYWHERE") is None
|
||||
|
||||
|
||||
def test_openbao_empty_token_uses_fallback(tmp_path: Path) -> None:
|
||||
(tmp_path / "K").write_text("v")
|
||||
backend = OpenBaoBackend(
|
||||
addr="http://127.0.0.1:8200",
|
||||
token="", # no token → never hits the network
|
||||
mount="castle",
|
||||
fallback=FileSecretBackend(tmp_path),
|
||||
def test_openbao_empty_token_returns_none(tmp_path: Path) -> None:
|
||||
backend = OpenBaoBackend(addr="http://127.0.0.1:8200", token="", mount="castle")
|
||||
assert backend.read("K") is None
|
||||
|
||||
|
||||
def test_openbao_node_prefix_from_settings(tmp_path: Path, monkeypatch) -> None:
|
||||
monkeypatch.delenv("CASTLE_SECRET_BACKEND", raising=False)
|
||||
backend = build_backend(
|
||||
tmp_path,
|
||||
{"backend": "openbao", "addr": "http://x", "node_prefix": "nodes/primer"},
|
||||
)
|
||||
assert backend.read("K") == "v"
|
||||
assert isinstance(backend, OpenBaoBackend)
|
||||
assert backend._node_prefix == "nodes/primer"
|
||||
|
||||
Reference in New Issue
Block a user