Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a0217e-8c4c-7383-be6b-f50a6e485306
171 lines
5.9 KiB
Python
171 lines
5.9 KiB
Python
import json
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from secrets_engine.errors import BackendError
|
|
from secrets_engine.openbao import OpenBaoClient
|
|
|
|
|
|
def test_kv_patch_keeps_secret_out_of_argv_and_cleans_input(monkeypatch):
|
|
secret = "fake-SUPER-SECRET-value"
|
|
client = OpenBaoClient(addr="http://example.invalid", token="", bao_bin="bao")
|
|
monkeypatch.setattr(client, "kv_current_version", lambda _mount, _path: 7)
|
|
captured = {}
|
|
|
|
def fake_run_ok(args, *, stdin=None):
|
|
captured["args"] = list(args)
|
|
captured["stdin"] = stdin
|
|
input_path = Path(args[-1][1:])
|
|
captured["input_path"] = input_path
|
|
captured["mode"] = input_path.stat().st_mode & 0o777
|
|
captured["payload"] = json.loads(input_path.read_text(encoding="utf-8"))
|
|
return ""
|
|
|
|
monkeypatch.setattr(client, "_run_ok", fake_run_ok)
|
|
base = client.kv_patch_fields("secret", "prod/example", {"TOKEN": secret})
|
|
|
|
assert base == 7
|
|
assert secret not in " ".join(captured["args"])
|
|
assert captured["stdin"] is None
|
|
assert captured["mode"] == 0o600
|
|
assert captured["payload"] == {"TOKEN": secret}
|
|
assert not captured["input_path"].exists()
|
|
|
|
|
|
def test_new_path_uses_cas_zero_put_without_secret_in_argv(monkeypatch):
|
|
secret = "fake-new-path-secret"
|
|
client = OpenBaoClient(addr="http://example.invalid", token="", bao_bin="bao")
|
|
monkeypatch.setattr(client, "kv_current_version", lambda _mount, _path: 0)
|
|
captured = {}
|
|
|
|
def fake_run_ok(args, *, stdin=None):
|
|
captured["args"] = list(args)
|
|
return ""
|
|
|
|
monkeypatch.setattr(client, "_run_ok", fake_run_ok)
|
|
client.kv_patch_fields("secret", "build/example", {"TOKEN": secret})
|
|
|
|
assert captured["args"][:5] == [
|
|
"kv",
|
|
"put",
|
|
"-mount=secret",
|
|
"-cas=0",
|
|
"build/example",
|
|
]
|
|
assert secret not in " ".join(captured["args"])
|
|
assert not Path(captured["args"][-1][1:]).exists()
|
|
|
|
|
|
def test_kv_current_version_fails_closed_on_permission_error(monkeypatch):
|
|
client = OpenBaoClient(addr="http://example.invalid", token="", bao_bin="bao")
|
|
monkeypatch.setattr(
|
|
client,
|
|
"_run",
|
|
lambda _args: SimpleNamespace(
|
|
returncode=2, stdout="", stderr="permission denied"
|
|
),
|
|
)
|
|
with pytest.raises(BackendError, match="metadata get failed"):
|
|
client.kv_current_version("secret", "prod/example")
|
|
|
|
|
|
def test_kv_current_version_returns_zero_only_for_absent_path(monkeypatch):
|
|
client = OpenBaoClient(addr="http://example.invalid", token="", bao_bin="bao")
|
|
monkeypatch.setattr(
|
|
client,
|
|
"_run",
|
|
lambda _args: SimpleNamespace(
|
|
returncode=2, stdout="No value found at secret/metadata/example", stderr=""
|
|
),
|
|
)
|
|
assert client.kv_current_version("secret", "example") == 0
|
|
|
|
|
|
def test_approle_session_keeps_login_material_out_of_argv_and_revokes_self(monkeypatch):
|
|
client = OpenBaoClient(addr="http://example.invalid", token="parent", bao_bin="bao")
|
|
monkeypatch.setattr(client, "read_approle_role_id", lambda _role: "role-id-value")
|
|
monkeypatch.setattr(
|
|
client, "create_approle_secret_id", lambda _role: "secret-id-value"
|
|
)
|
|
captured = {}
|
|
|
|
def fake_json_call(args, payload):
|
|
captured["args"] = list(args)
|
|
captured["payload"] = dict(payload)
|
|
return json.dumps(
|
|
{"auth": {"client_token": "scoped-token", "accessor": "accessor-value"}}
|
|
)
|
|
|
|
monkeypatch.setattr(client, "_run_ok_with_json_file", fake_json_call)
|
|
session = client.create_approle_session("example-role")
|
|
revoke_calls = []
|
|
monkeypatch.setattr(
|
|
session.client,
|
|
"_run_ok",
|
|
lambda args, **_kwargs: revoke_calls.append(list(args)) or "",
|
|
)
|
|
|
|
assert "role-id-value" not in " ".join(captured["args"])
|
|
assert "secret-id-value" not in " ".join(captured["args"])
|
|
assert captured["payload"] == {
|
|
"role_id": "role-id-value",
|
|
"secret_id": "secret-id-value",
|
|
}
|
|
assert session.client.token == "scoped-token"
|
|
assert session.accessor_fingerprint
|
|
|
|
session.close()
|
|
session.close()
|
|
assert revoke_calls == [["token", "revoke", "-self"]]
|
|
assert session.client.token == ""
|
|
assert session.closed is True
|
|
assert session.evidence() == {
|
|
"session_handle": session.accessor_fingerprint,
|
|
"established": True,
|
|
"revocation_attempted": True,
|
|
"revocation_succeeded": True,
|
|
}
|
|
assert "accessor-value" not in json.dumps(session.evidence())
|
|
assert "scoped-token" not in json.dumps(session.evidence())
|
|
|
|
|
|
def test_failed_session_revocation_is_visible_and_credential_is_dropped(monkeypatch):
|
|
client = OpenBaoClient(addr="http://example.invalid", token="issued", bao_bin="bao")
|
|
from secrets_engine.openbao import ScopedTokenSession
|
|
|
|
session = ScopedTokenSession(client=client, accessor_fingerprint="safe-handle")
|
|
monkeypatch.setattr(
|
|
client,
|
|
"_run_ok",
|
|
lambda *_args, **_kwargs: (_ for _ in ()).throw(BackendError("revoke failed")),
|
|
)
|
|
|
|
with pytest.raises(BackendError, match="revoke failed"):
|
|
session.close()
|
|
|
|
assert session.client.token == ""
|
|
assert session.closed is True
|
|
assert session.evidence() == {
|
|
"session_handle": "safe-handle",
|
|
"established": True,
|
|
"revocation_attempted": True,
|
|
"revocation_succeeded": False,
|
|
}
|
|
|
|
|
|
def test_approle_session_context_revokes_on_failure(monkeypatch):
|
|
client = OpenBaoClient(addr="http://example.invalid", token="parent", bao_bin="bao")
|
|
session = SimpleNamespace(closed=False)
|
|
|
|
def close():
|
|
session.closed = True
|
|
|
|
session.close = close
|
|
monkeypatch.setattr(client, "create_approle_session", lambda _role: session)
|
|
|
|
with pytest.raises(RuntimeError, match="child failed"):
|
|
with client.approle_session("example-role"):
|
|
raise RuntimeError("child failed")
|
|
assert session.closed is True
|