249 lines
9.0 KiB
Python
249 lines
9.0 KiB
Python
"""CLI entry point for zkac-node."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
|
|
from . import client, store
|
|
|
|
|
|
# ── identity ──────────────────────────────────────────────────────────
|
|
|
|
def _cmd_identity_init(_args):
|
|
if store.identity_exists():
|
|
print("identity already exists")
|
|
ident = store.load_identity()
|
|
print(f" issuance public key: {ident['issuance_pk'].hex()}")
|
|
return
|
|
path = store.create_identity()
|
|
ident = store.load_identity()
|
|
print("created identity")
|
|
print(f" issuance public key: {ident['issuance_pk'].hex()}")
|
|
print(f" (share this key out-of-band to receive credentials)")
|
|
print(f" stored in {path}")
|
|
|
|
|
|
def _cmd_identity_show(_args):
|
|
ident = store.load_identity()
|
|
print(f" issuance pk: {ident['issuance_pk'].hex()}")
|
|
|
|
regs = client.list_own_registries()
|
|
if regs:
|
|
print(f" registries owned: {len(regs)}")
|
|
for r in regs:
|
|
print(f" {r['registry_id']} @ {r['server']} roles={r['roles']}")
|
|
|
|
creds = store.list_credentials()
|
|
if creds:
|
|
print(f" credentials: {len(creds)}")
|
|
for reg_hex, role in creds:
|
|
print(f" {reg_hex[:16]}… / {role}")
|
|
|
|
|
|
# ── server ────────────────────────────────────────────────────────────
|
|
|
|
def _cmd_serve(args):
|
|
from .server import serve
|
|
serve(args.data_dir, args.host, args.port)
|
|
|
|
|
|
def _cmd_server_pin(args):
|
|
pk_hex = args.key
|
|
import base64
|
|
pk_bytes = bytes.fromhex(pk_hex)
|
|
store.pin_server(args.server, base64.b64encode(pk_bytes).decode())
|
|
print(f"pinned {args.server} -> {pk_hex[:16]}…")
|
|
|
|
|
|
# ── registry ──────────────────────────────────────────────────────────
|
|
|
|
def _cmd_registry_create(args):
|
|
roles = [r.strip() for r in args.roles.split(",")]
|
|
rid = client.create_registry(args.server, roles)
|
|
print(f"registry created: {rid}")
|
|
print(f" roles: {', '.join(roles)}")
|
|
|
|
|
|
def _cmd_registry_update(args):
|
|
add = [r.strip() for r in args.add_roles.split(",")]
|
|
client.update_registry(args.server, args.registry, add)
|
|
print(f"registry updated: {args.registry[:16]}…")
|
|
print(f" added roles: {', '.join(add)}")
|
|
|
|
|
|
def _cmd_registry_get(args):
|
|
info = client.get_registry(args.server, args.registry)
|
|
print(f"registry: {args.registry}")
|
|
print(f" state bytes: {len(info.get('state_bytes_b64', ''))} chars (b64)")
|
|
|
|
|
|
def _cmd_registry_list(_args):
|
|
regs = client.list_own_registries()
|
|
if not regs:
|
|
print("no registries")
|
|
return
|
|
for r in regs:
|
|
print(f" {r['registry_id']} @ {r['server']} roles={r['roles']}")
|
|
|
|
|
|
# ── grant (admin-initiated) ──────────────────────────────────────────
|
|
|
|
def _cmd_grant(args):
|
|
gid = client.grant(args.server, args.registry, args.role, args.to)
|
|
print(f"granted {args.role!r} to {args.to[:16]}…")
|
|
print(f" grant id: {gid}")
|
|
print(f" recipient can collect with:")
|
|
print(f" zkac-node collect {args.server}:{args.registry}:{args.role}")
|
|
|
|
|
|
# ── credentials list / collect ────────────────────────────────────────
|
|
|
|
def _cmd_credentials_list(args):
|
|
local = store.list_credentials()
|
|
print("local credentials:")
|
|
if not local:
|
|
print(" (none)")
|
|
for reg_hex, role in local:
|
|
print(f" {reg_hex}:{role}")
|
|
|
|
servers = list(args.server or [])
|
|
for s in store.list_pinned_servers():
|
|
if s not in servers:
|
|
servers.append(s)
|
|
|
|
if not servers:
|
|
print("\n(no servers to query; pass --server host:port to check for pending)")
|
|
return
|
|
|
|
print("\npending grants:")
|
|
any_pending = False
|
|
for srv in servers:
|
|
try:
|
|
grants = client.list_pending(srv)
|
|
except Exception as exc:
|
|
print(f" [{srv}] error: {exc}")
|
|
continue
|
|
for g in grants:
|
|
any_pending = True
|
|
rid = g.get("registry_id", "?")
|
|
role = g.get("role_name", "?")
|
|
if rid != "?" and role != "?" and store.has_credential(rid, role):
|
|
note = " (already collected locally)"
|
|
else:
|
|
note = ""
|
|
print(f" {srv}:{rid}:{role}{note}")
|
|
if not any_pending:
|
|
print(" (none)")
|
|
|
|
|
|
def _cmd_collect(args):
|
|
result = client.collect(args.spec)
|
|
print("collected credential")
|
|
print(f" registry: {result['registry_id']}")
|
|
print(f" role: {result['role']}")
|
|
print(f" server: {result['server']}")
|
|
|
|
|
|
# ── auth ──────────────────────────────────────────────────────────────
|
|
|
|
def _cmd_auth(args):
|
|
resp = client.authenticate(args.registry, args.role, server=args.server)
|
|
print(json.dumps(resp, indent=2))
|
|
|
|
|
|
# ── argparse wiring ───────────────────────────────────────────────────
|
|
|
|
def main():
|
|
p = argparse.ArgumentParser(prog="zkac-node")
|
|
sub = p.add_subparsers(dest="group", required=True)
|
|
|
|
# identity
|
|
id_p = sub.add_parser("identity", help="manage local identity")
|
|
id_sub = id_p.add_subparsers(dest="action", required=True)
|
|
|
|
c = id_sub.add_parser("init", help="generate a new identity")
|
|
c.set_defaults(func=_cmd_identity_init)
|
|
|
|
c = id_sub.add_parser("show", help="show identity + registries + credentials")
|
|
c.set_defaults(func=_cmd_identity_show)
|
|
|
|
# serve
|
|
c = sub.add_parser("serve", help="run as a ZKAC server node")
|
|
c.add_argument("--data-dir", required=True, help="server data directory")
|
|
c.add_argument("--host", default="127.0.0.1")
|
|
c.add_argument("--port", type=int, default=9800)
|
|
c.set_defaults(func=_cmd_serve)
|
|
|
|
# server pin
|
|
srv_p = sub.add_parser("server", help="manage server pins")
|
|
srv_sub = srv_p.add_subparsers(dest="action", required=True)
|
|
|
|
c = srv_sub.add_parser("pin", help="pin a server's public key")
|
|
c.add_argument("server", help="host:port")
|
|
c.add_argument("--key", required=True, help="server public key (hex)")
|
|
c.set_defaults(func=_cmd_server_pin)
|
|
|
|
# registry
|
|
reg_p = sub.add_parser("registry", help="manage registries")
|
|
reg_sub = reg_p.add_subparsers(dest="action", required=True)
|
|
|
|
c = reg_sub.add_parser("create", help="create a new registry on a server")
|
|
c.add_argument("server", help="host:port")
|
|
c.add_argument("--roles", required=True, help="comma-separated role names")
|
|
c.set_defaults(func=_cmd_registry_create)
|
|
|
|
c = reg_sub.add_parser("update", help="add roles to a registry you own")
|
|
c.add_argument("server", help="host:port")
|
|
c.add_argument("--registry", required=True)
|
|
c.add_argument("--add-roles", required=True, help="comma-separated new roles")
|
|
c.set_defaults(func=_cmd_registry_update)
|
|
|
|
c = reg_sub.add_parser("get", help="fetch registry info from server")
|
|
c.add_argument("server", help="host:port")
|
|
c.add_argument("--registry", required=True)
|
|
c.set_defaults(func=_cmd_registry_get)
|
|
|
|
c = reg_sub.add_parser("list", help="list local registries (owned)")
|
|
c.set_defaults(func=_cmd_registry_list)
|
|
|
|
# grant
|
|
c = sub.add_parser("grant", help="issue a credential to a recipient (admin)")
|
|
c.add_argument("--server", required=True, help="host:port")
|
|
c.add_argument("--registry", required=True)
|
|
c.add_argument("--role", required=True)
|
|
c.add_argument("--to", required=True,
|
|
help="recipient's issuance public key (hex)")
|
|
c.set_defaults(func=_cmd_grant)
|
|
|
|
# credentials list
|
|
cred_p = sub.add_parser("credentials", help="credentials (local + pending)")
|
|
cred_sub = cred_p.add_subparsers(dest="action", required=True)
|
|
c = cred_sub.add_parser("list", help="show local + pending credentials")
|
|
c.add_argument("--server", action="append",
|
|
help="server to query (host:port); may be repeated")
|
|
c.set_defaults(func=_cmd_credentials_list)
|
|
|
|
# collect
|
|
c = sub.add_parser("collect", help="fetch and finalize a pending credential")
|
|
c.add_argument("spec", help="host:port:registry_id:role")
|
|
c.set_defaults(func=_cmd_collect)
|
|
|
|
# auth
|
|
c = sub.add_parser("auth", help="authenticate to a server with a credential")
|
|
c.add_argument("--registry", required=True)
|
|
c.add_argument("--role", required=True)
|
|
c.add_argument("--server", default=None, help="host:port (optional if known)")
|
|
c.set_defaults(func=_cmd_auth)
|
|
|
|
args = p.parse_args()
|
|
if not hasattr(args, "func"):
|
|
p.print_help()
|
|
sys.exit(1)
|
|
args.func(args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|