6.3 KiB
QUBES_OS.md
1. Goal
Run n_signer inside a dedicated Qubes OS qube (for example vault-like behavior), and let caller qubes access signing via qrexec with explicit policy control.
This doc outlines what must be implemented/packaged for a reliable Qubes deployment path.
2. Current status (where we are now)
Implemented in current codebase:
nsignersupports--listen qrexecand--listen stdio.- Framing is transport-agnostic and shared via length-prefixed JSON (
4-byte big-endian length + payload). - In qrexec/stdio mode, server handles one framed request-response exchange.
- Caller identity extraction supports
QREXEC_REMOTE_DOMAIN, surfaced asqubes:<source-vm>when available.
Still missing for complete Qubes integration:
- qrexec service file + wrapper script artifacts.
- dom0 qrexec policy artifacts with sane defaults.
- install/uninstall guidance and verification flow for real Qubes deployment.
- packaging path (
packaging/qubes/) and docs wired into README map.
3. Architecture in Qubes
3.1 Components
- Signer qube (target): runs
nsignerservice entrypoint. - Caller qube(s): apps/tools invoking qrexec service.
- dom0 policy: controls which caller qubes may invoke signer service.
3.2 Request path
- Caller qube invokes qrexec service (e.g.
qubes.NsignerRpc). - qrexec starts service command inside signer qube.
- Service command runs
nsigner --listen qrexec. - Caller sends framed JSON-RPC request over qrexec stdio channel.
nsignerreturns framed JSON-RPC response.
3.3 Trust and identity
- Source qube identity comes from
QREXEC_REMOTE_DOMAIN. n_signermaps caller asqubes:<source-vm>where available.- qrexec policy in dom0 remains first enforcement boundary.
n_signerpolicy/approval remains second boundary.
3.4 Optional per-program identity inside one qube (--auth optional)
qrexec now supports an optional auth-envelope layer:
nsigner --listen qrexec --auth off(default): identity remainsqubes:<source-vm>.nsigner --listen qrexec --auth optional: if a caller includes a valid auth envelope, identity becomesqubes:<source-vm>+pubkey:<hex>.nsigner --listen qrexec --auth required: all requests must carry valid envelopes and identity is alwaysqubes:<source-vm>+pubkey:<hex>.
This enables per-program approvals inside a single caller qube while preserving existing behavior for legacy callers that do not emit auth envelopes.
4. Required implementation tasks
4.1 Service entrypoint artifacts ✅ Implemented
Implemented repo artifacts:
packaging/qubes/rpc/qubes.NsignerRpcpackaging/qubes/install-service.sh
qubes.NsignerRpc runs:
exec /usr/local/bin/nsigner --listen qrexec
Install inside the signer qube:
sudo sh packaging/qubes/install-service.sh
This installs the qrexec service to /etc/qubes-rpc/qubes.NsignerRpc with executable permissions.
4.2 dom0 policy artifacts ✅ Implemented
Implemented repo artifacts:
packaging/qubes/policy.d/40-nsigner.policypackaging/qubes/install-policy.sh
Policy defaults now use explicit ask plus deny catch-all:
qubes.NsignerRpc * @anyvm @tag:nsigner-signer ask default_target=nsigner-vaultqubes.NsignerRpc * @anyvm @anyvm deny
Install in dom0:
sudo sh packaging/qubes/install-policy.sh
This installs /etc/qubes/policy.d/40-nsigner.policy and prints signer-tag guidance.
4.3 Policy model inside n_signer for qubes callers ✅ Implemented
Current code reads caller as qubes:<vm> and qrexec default behavior is hardened.
In qrexec mode, default prompt behavior is now:
PROMPT_EVERY_REQUEST
This replaces the previous permissive PROMPT_NEVER temporary setting.
If qrexec auth envelopes are enabled (--auth optional or --auth required), approved callers can also be recorded as composite identities:
qubes:<vm>+pubkey:<hex>
This gives independent approval rows for distinct programs running in the same source qube.
4.4 Client helper examples ✅ Implemented
Added:
documents/qubes_client_examples.md
Includes:
- shell helper example invoking
qrexec-client-vmwith framed request/response handling - Python helper example implementing frame encode/decode over qrexec stdio channel
- reference to
documents/CLIENT_IMPLEMENTATION.mdfor full protocol details
5. Operational runbook
5.1 Setup signer qube
- install
nsignerbinary at/usr/local/bin/nsigner - run
sudo sh packaging/qubes/install-service.sh - verify
/etc/qubes-rpc/qubes.NsignerRpcexists and is executable
5.2 Setup dom0 policy
- run
sudo sh packaging/qubes/install-policy.sh - tag signer qube (example):
qvm-tags nsigner-vault add nsigner-signer - reload qrexec policy per Qubes procedure/version
5.3 Verification
- from caller qube, invoke test request (
get_public_key) - confirm signer qube receives request
- confirm activity log displays
qubes:<source-vm>caller prefix - with
--auth optional, confirm envelope-enabled clients showqubes:<source-vm>+pubkey:<hex> - validate deny behavior from unauthorized qube
5.4 Failure checks
- malformed frame -> parse error response
- missing policy -> deny path
- missing
QREXEC_REMOTE_DOMAIN-> fallback identity path
6. Security requirements
- Never run signer service in disposable qube if mnemonic persistence is expected.
- Prefer dedicated minimal template for signer qube.
- Keep qrexec policy narrowly scoped (explicit source + target).
- Require user approval for sensitive methods unless explicitly intended otherwise.
- Log caller identity and method (without secret payload logging).
7. Documentation tasks
Update these after packaging lands:
README.md- add Qubes deployment subsection under transport/usage
- add
documents/QUBES_OS.mdand moveddocuments/CLIENT_IMPLEMENTATION.mdin document map
plans/nsigner.md- mark T1 done with packaging status clearly separated
8. Definition of done (Qubes)
Qubes integration is considered complete when:
- qrexec service artifact exists and is installable.
- dom0 policy artifact exists with secure default pattern.
- End-to-end call from allowed caller qube succeeds.
- Call from unauthorized qube is denied.
- Caller displayed as
qubes:<vm>in activity. - README + docs include full setup and troubleshooting.