agent-credential-broker: fork of vaultwarden-agent-broker + OpenBao backend
Mechanical rename (package agent_credential_broker; entry points
agent-broker{,-mcp,-plugin}; env AGENT_BROKER_*; paths agent-broker;
agent-browser credential provider 'broker'; MCP server name agent-broker).
New backend 'openbao': stdlib-only HTTP client for OpenBao/Vault KV v2
(approle login or token file/env; metadata-only ref index; lazy re-login
on 401/403; token renewal). bitwarden-cli backend kept as transition.
This commit is contained in:
commit
be87900794
35 files changed
+5003
No files matched your search
@@ -0,0 +1,118 @@
|
||||
"""agent-broker command line.
|
||||
|
||||
agent-broker daemon run the unix-socket daemon (foreground)
|
||||
agent-broker status
|
||||
agent-broker unlock interactive, TTY, no echo
|
||||
(or non-interactive when
|
||||
master_password_file is configured)
|
||||
agent-broker lock
|
||||
agent-broker confirm <ticket>
|
||||
|
||||
The master password is read with getpass() by default -- it never arrives
|
||||
via argv or env. If config sets master_password_file, unlock reads it from
|
||||
that file instead (600 perms + same-owner enforced by read_secret_file).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import getpass
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from . import __version__
|
||||
from .config import Config, read_secret_file, socket_path
|
||||
from .errors import BrokerError
|
||||
from .server import call, serve_forever
|
||||
|
||||
|
||||
def _out(obj):
|
||||
print(json.dumps(obj, indent=2, sort_keys=True))
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
ap = argparse.ArgumentParser(prog="agent-broker")
|
||||
ap.add_argument("--version", action="version", version=__version__)
|
||||
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||
sub.add_parser("daemon", help="run the unix-socket daemon in the foreground")
|
||||
sub.add_parser("status")
|
||||
sub.add_parser("unlock", help="interactive unlock (TTY, no echo)")
|
||||
sub.add_parser("lock")
|
||||
sub.add_parser("logout", help="lock and log out the vault backend")
|
||||
c = sub.add_parser("confirm", help="approve a pending confirmation ticket")
|
||||
c.add_argument("ticket")
|
||||
sub.add_parser("doctor", help="check installation prerequisites")
|
||||
l = sub.add_parser("list-refs")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
if args.cmd == "daemon":
|
||||
serve_forever()
|
||||
return 0
|
||||
try:
|
||||
if args.cmd == "status":
|
||||
_out(call("status"))
|
||||
elif args.cmd == "unlock":
|
||||
mpf = Config.load().master_password_file
|
||||
if mpf:
|
||||
password = read_secret_file(mpf)
|
||||
else:
|
||||
if not (sys.stdin.isatty() and sys.stderr.isatty()):
|
||||
raise BrokerError("DENIED", "unlock requires an interactive TTY")
|
||||
password = getpass.getpass("Master Password: ", stream=sys.stderr)
|
||||
if not password:
|
||||
raise BrokerError("DENIED", "empty password")
|
||||
try:
|
||||
_out(call("unlock", {"password": password}))
|
||||
finally:
|
||||
password = None
|
||||
elif args.cmd == "lock":
|
||||
_out(call("lock"))
|
||||
elif args.cmd == "logout":
|
||||
_out(call("logout"))
|
||||
elif args.cmd == "confirm":
|
||||
_out(call("confirm", {"ticket": args.ticket}))
|
||||
elif args.cmd == "list-refs":
|
||||
_out(call("list_refs"))
|
||||
elif args.cmd == "doctor":
|
||||
_out(doctor())
|
||||
except BrokerError as e:
|
||||
out = {"ok": False, "error": e.to_dict()}
|
||||
if getattr(e, "ticket", None):
|
||||
out["ticket"] = e.ticket
|
||||
_out(out)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def doctor():
|
||||
cfg = Config.load()
|
||||
import shutil
|
||||
checks = {
|
||||
"socket": str(socket_path()),
|
||||
"vault_backend": cfg.vault_backend,
|
||||
"agent_browser": bool(shutil.which("agent-browser")),
|
||||
"git": bool(shutil.which("git")),
|
||||
"ssh": bool(shutil.which("ssh")),
|
||||
# bitwarden-cli backend
|
||||
"vault_url": cfg.vault_url,
|
||||
"bw": bool(shutil.which("bw") or (cfg.bw_path and os.path.exists(cfg.bw_path))),
|
||||
"client_id_file": cfg.client_id_file.exists(),
|
||||
"client_secret_file": cfg.client_secret_file.exists(),
|
||||
# openbao backend
|
||||
"openbao_addr": cfg.openbao_addr,
|
||||
"openbao_role_id_file": cfg.openbao_role_id_file.exists(),
|
||||
"openbao_secret_id_file": cfg.openbao_secret_id_file.exists(),
|
||||
"openbao_token_file": cfg.openbao_token_file.exists(),
|
||||
"openbao_token_env": bool(os.environ.get(cfg.openbao_token_env)),
|
||||
}
|
||||
try:
|
||||
from .backends import make_backend
|
||||
checks["configured"] = make_backend(cfg).configured()
|
||||
except BrokerError:
|
||||
checks["configured"] = False
|
||||
return checks
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user