Files
n_signer/plans/interactive_transport_selection.md

7.4 KiB

Plan: Interactive multi-transport selection at startup

Status: design / ready for review.

Related:


1. Motivation

The current CLI has many transport flags (--listen, --socket-name, --bridge-source-trusted, --auth, --allow-all) that the user must know in advance. For interactive use, this is unfriendly — the user just wants to answer "how should other programs reach this signer?"

Additionally, the user may want multiple transports active simultaneously (e.g. unix socket for local clients + TCP for FIPS mesh clients + qrexec bridge for other qubes). Today only one --listen mode is supported at a time.


2. Design

2.1 Interactive transport menu at startup

After mnemonic entry and before the running phase, if no --listen flag was given (i.e. interactive mode), present a multi-select menu:

Transport — how should other programs reach this signer?
Select one or more (space to toggle, enter to confirm):

  [x] 1. Local Unix socket (same machine/qube)
  [ ] 2. Qubes qrexec bridge (other qubes via qrexec, no network)
  [ ] 3. TCP listener (FIPS mesh or local network)
  [ ] 4. Qrexec one-shot (legacy, one request per invocation)

  [a] select all

  (at least one must be selected)

Each selected transport gets configured with sensible defaults:

Choice What it starts Defaults
1. Unix socket --listen unix socket name nsigner (or random if collision)
2. Qrexec bridge --listen unix --bridge-source-trusted socket name nsigner, accepts qrexec preamble
3. TCP --listen tcp:[::]:11111 bind all interfaces, port 11111
4. Qrexec one-shot --listen qrexec one request via stdin, then exit

Choices 1 and 2 can coexist — they're both unix listeners, just with different identity handling. Choice 2 implies choice 1's socket. If both are selected, the bridge-source-trusted flag is set on the single unix listener (it handles both local and bridge connections).

Choice 3 (TCP) can coexist with 1+2 — it's a separate listener on a different fd.

Choice 4 (qrexec one-shot) is mutually exclusive with all others — it uses stdin/stdout and exits after one request. If selected alone, it runs the one-shot path. If selected with others, it's ignored with a warning (or: the user is told it can't combine).

2.2 Multi-listener architecture

Currently main() creates one server_ctx_t and polls server.listen_fd + STDIN_FILENO. To support multiple listeners:

  • Create an array of server_ctx_t (up to 3: unix, tcp, and optionally a second unix for bridge — though 1+2 collapse into one).
  • Each server_ctx_t gets its own server_start().
  • The poll loop expands to poll all listener fds + STDIN_FILENO.
  • When a listener fd has activity, call server_handle_one() on that context.
  • The TUI status display shows all active transports.
Connections
listen: unix @nsigner (bridge-source-trusted)
listen: tcp [::]:11111
client: nsigner --socket-name nsigner client '<json>'

2.3 Coexistence with CLI flags

  • If --listen is given on the command line, skip the interactive menu (automation/scripting path unchanged).
  • If no --listen is given and stdin is a TTY, show the interactive menu.
  • If no --listen is given and stdin is NOT a TTY, default to unix socket (current behavior, for --mnemonic-stdin / --mnemonic-fd supervised launches).

This preserves backward compatibility: all existing flags and scripts work unchanged. The menu is purely an interactive convenience.

2.4 TUI implementation

The menu uses the existing TUI rendering helpers (tui_render_content_screen, tui_print). It appears after mnemonic acceptance and before the status display. Navigation:

  • 1-4 or space: toggle a choice
  • a: select all (except 4, which is mutually exclusive)
  • a: select all (except 4, which is mutually exclusive)
  • enter: confirm and proceed to running phase (at least one must be selected)

After confirmation, the selected listeners are started and the status display renders.

2.5 Security considerations

  • The menu is purely a UX layer — it doesn't change the security model. Each transport still goes through the same policy/approval pipeline.
  • --bridge-source-trusted is only set if the user explicitly selects the qrexec bridge option. It's never silently enabled.
  • TCP listener still requires auth envelopes (existing behavior).
  • The menu doesn't offer --allow-all; that remains a CLI flag for users who want it.

3. Implementation checklist

3.1 Multi-listener support in main.c

  • Replace single server_ctx_t server with an array (e.g. server_ctx_t servers[3]).
  • Add a server_count variable.
  • Expand the poll loop to poll all servers[i].listen_fd + STDIN_FILENO.
  • On activity, call server_handle_one(&servers[i], ...) for the matching fd.
  • server_stop() all on shutdown.

3.2 Interactive menu

  • Add prompt_transport_selection() function in main.c (after mnemonic load, before server start).
  • Returns a bitmask of selected transports.
  • Only called when listen_mode was not set by CLI and stdin is a TTY.
  • Uses tui_render_content_screen + read_line_stdin for input.

3.3 Transport setup from menu selection

  • Unix: server_init + server_start with NSIGNER_LISTEN_UNIX, socket name nsigner.
  • Qrexec bridge: same as unix + server_set_bridge_source_trusted(&server, 1).
  • TCP: server_init + server_start with NSIGNER_LISTEN_TCP, target tcp:[::]:11111.
  • Qrexec one-shot: existing NSIGNER_LISTEN_QREXEC path (stdin, one request, exit).

3.4 TUI status display

  • Update render_status() to show multiple active listeners.
  • Show (bridge-source-trusted) annotation on unix listeners that have it.

3.5 Testing

  • Interactive: start nsigner with no --listen, select unix+TCP, verify both listeners work.
  • Interactive: select qrexec bridge, verify bridge connections get qubes:<vm> identity.
  • CLI: --listen tcp:[::]:11111 still works (skips menu).
  • CLI: --listen unix --bridge-source-trusted still works (skips menu).
  • Non-TTY: --mnemonic-stdin without --listen defaults to unix (no menu).

4. Open questions

  1. Should the menu remember the last selection? n_signer has no config files (by design). We could store the preference in an env var or a /tmp file, but that violates the zero-filesystem-footprint principle. Decision: no persistence — the menu appears fresh each time.

  2. Should there be a hotkey to add/remove transports at runtime? E.g. press t in the TUI to bring up the transport menu again. This is a nice-to-have but adds complexity. Decision: defer — the menu is startup-only for now.

  3. TCP port selection in the menu? The menu could ask for a port number if TCP is selected. Decision: default to 11111, let the user override with --listen tcp:... if they need a different port. Keep the menu simple.

  4. Socket name selection? Same — default to nsigner, override with --socket-name if needed. Decision: default, no prompt.