feat: auth keys shows trusted keys with names; remote auth trust/keys
- authorized_keys format extended to '<hex> [optional-name]' - auth keys repurposed: shows server's trusted keys (Name/Public Key table) instead of local client keys; --remote queries the remote serve instance - auth trust gains --name flag for labelling keys; --remote pushes the key to the remote server's authorized_keys - serve.py handles browser-cli.auth.keys and browser-cli.auth.trust as server-side commands (authenticated, never forwarded to native host) - serve.py reloads authorized_keys from disk on every connection so auth trust --remote takes effect immediately without restarting serve - auth show unchanged: still prints your own client public key Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+53
-32
@@ -252,9 +252,11 @@ def cmd_auth_keygen(output, force):
|
||||
|
||||
@auth_group.command("trust")
|
||||
@click.argument("pubkey")
|
||||
@click.option("--name", default="", metavar="NAME", help="Human-friendly label for this key.")
|
||||
@click.option("--file", "keys_file", default=None, metavar="PATH", help="Authorized keys file (default: ~/.config/browser-cli/authorized_keys).")
|
||||
def cmd_auth_trust(pubkey, keys_file):
|
||||
"""Add a public key to the authorized keys file on the serve host."""
|
||||
@click.pass_context
|
||||
def cmd_auth_trust(ctx, pubkey, name, keys_file):
|
||||
"""Add a public key to the authorized keys file (locally or on a remote serve host)."""
|
||||
from browser_cli.auth import DEFAULT_AUTHORIZED_KEYS_PATH, add_authorized_key
|
||||
|
||||
if len(pubkey) != 64:
|
||||
@@ -266,10 +268,28 @@ def cmd_auth_trust(pubkey, keys_file):
|
||||
console.print("[red]Invalid public key:[/red] not valid hex")
|
||||
sys.exit(1)
|
||||
|
||||
remote = (ctx.obj or {}).get("remote")
|
||||
if remote:
|
||||
from browser_cli.client import send_command
|
||||
result = send_command(
|
||||
"browser-cli.auth.trust",
|
||||
args={"pubkey": pubkey, "name": name},
|
||||
remote=remote,
|
||||
key=(ctx.obj or {}).get("key"),
|
||||
)
|
||||
added = (result or {}).get("added", False)
|
||||
label = f" ({name})" if name else ""
|
||||
if added:
|
||||
console.print(f"[green]✓[/green] Trusted on {remote}{label}: [cyan]{pubkey}[/cyan]")
|
||||
else:
|
||||
console.print(f"[yellow]Already trusted on {remote}:[/yellow] {pubkey}")
|
||||
return
|
||||
|
||||
path = Path(keys_file) if keys_file else DEFAULT_AUTHORIZED_KEYS_PATH
|
||||
added = add_authorized_key(path, pubkey)
|
||||
added = add_authorized_key(path, pubkey, name)
|
||||
label = f" ({name})" if name else ""
|
||||
if added:
|
||||
console.print(f"[green]✓[/green] Trusted: [cyan]{pubkey}[/cyan]")
|
||||
console.print(f"[green]✓[/green] Trusted{label}: [cyan]{pubkey}[/cyan]")
|
||||
console.print(f" File: {path}")
|
||||
console.print(f"\nStart the server with:")
|
||||
console.print(f" [dim]browser-cli serve --authorized-keys {path}[/dim]")
|
||||
@@ -313,39 +333,40 @@ def cmd_auth_show(key_src):
|
||||
|
||||
|
||||
@auth_group.command("keys")
|
||||
def cmd_auth_keys():
|
||||
"""List all Ed25519 keys available for pubkey auth (file + SSH agent)."""
|
||||
from browser_cli.auth import DEFAULT_KEY_PATH, agent_list_keys, load_private_key, public_key_hex
|
||||
@click.option("--file", "keys_file", default=None, metavar="PATH", help="Authorized keys file (default: ~/.config/browser-cli/authorized_keys).")
|
||||
@click.pass_context
|
||||
def cmd_auth_keys(ctx, keys_file):
|
||||
"""List trusted public keys (server's authorized_keys). With --remote, queries the remote server."""
|
||||
from rich.table import Table
|
||||
|
||||
table = Table(show_header=True, header_style="bold cyan")
|
||||
table.add_column("Source")
|
||||
table.add_column("Comment / Path")
|
||||
table.add_column("Public Key")
|
||||
remote = (ctx.obj or {}).get("remote")
|
||||
if remote:
|
||||
from browser_cli.client import send_command
|
||||
result = send_command(
|
||||
"browser-cli.auth.keys",
|
||||
remote=remote,
|
||||
key=(ctx.obj or {}).get("key"),
|
||||
)
|
||||
entries = result or []
|
||||
source_label = remote
|
||||
else:
|
||||
from browser_cli.auth import DEFAULT_AUTHORIZED_KEYS_PATH, load_authorized_keys_with_names
|
||||
path = Path(keys_file) if keys_file else DEFAULT_AUTHORIZED_KEYS_PATH
|
||||
entries = [{"pubkey": pk, "name": name} for pk, name in load_authorized_keys_with_names(path)]
|
||||
source_label = str(path)
|
||||
|
||||
# File key
|
||||
if DEFAULT_KEY_PATH.exists():
|
||||
try:
|
||||
priv = load_private_key(DEFAULT_KEY_PATH)
|
||||
hex_key = public_key_hex(priv)
|
||||
table.add_row("[green]file[/green]", str(DEFAULT_KEY_PATH), hex_key)
|
||||
except Exception as e:
|
||||
table.add_row("[red]file[/red]", str(DEFAULT_KEY_PATH), f"[red]{e}[/red]")
|
||||
|
||||
# Agent keys
|
||||
try:
|
||||
agent_keys = agent_list_keys()
|
||||
for k in agent_keys:
|
||||
table.add_row("[cyan]agent[/cyan]", k.comment, public_key_hex(k))
|
||||
except Exception as e:
|
||||
table.add_row("[dim]agent[/dim]", f"[dim]{e}[/dim]", "")
|
||||
|
||||
if table.row_count == 0:
|
||||
console.print("[yellow]No keys found.[/yellow] Run: [dim]browser-cli auth keygen[/dim]")
|
||||
if not entries:
|
||||
console.print(f"[yellow]No trusted keys[/yellow] in {source_label}")
|
||||
console.print(" Add one: [dim]browser-cli auth trust <public-key> --name <label>[/dim]")
|
||||
return
|
||||
|
||||
table = Table(show_header=True, header_style="bold cyan")
|
||||
table.add_column("Name")
|
||||
table.add_column("Public Key")
|
||||
for entry in entries:
|
||||
name = entry.get("name") or "[dim]—[/dim]"
|
||||
table.add_row(name, entry.get("pubkey", ""))
|
||||
console.print(table)
|
||||
console.print("\nTo trust a key on the serve host:")
|
||||
console.print(" [dim]browser-cli auth trust <public-key>[/dim]")
|
||||
|
||||
|
||||
main.add_command(auth_group)
|
||||
|
||||
Reference in New Issue
Block a user