nats: a WebSocket listener for a page in a browser, from the overlay only (hq research 036)
mesh/merge-gate head e6d5ead50dfd merged into trunk 44e7002d3c84 — fail: builds nats, sent nowhere; merging this needs a person's bus upgrade; 1 wait(s) fo…
mesh/repo-check head e6d5ead50dfd merged into trunk 44e7002d3c84 — fail: head e6d5ead50dfd does not merge cleanly into trunk 44e7002d3c84: in conflict modu…
mesh/delivery stopped: the pull request closed unmerged

The bus has no way for a browser to reach it (research 036, gap G1). The server now also listens
over WebSocket on 4223: published from the mesh like 4222, so the machine's filter admits it from
the overlay and nothing else; no TLS, acceptable only because the overlay is WireGuard and a
browser cannot be made to trust the mesh's own certificate; compression on. The user a page
connects as is the read-only view the controller composes (mesh-controller, `bus view-credential`).

A test parses the declared configuration with the server itself, holds the opening, the published
port and the listener to one port, and connects a client over WebSocket. The bus module's policy is
`record`: this reaches the bus's machine only through the planned step a person starts
(`bus upgrade`), as every bus change does.
This commit is contained in:
jochen
2026-10-10 16:00:52 +02:00
parent 1fcf46fbfc
commit e6d5ead50d
3 changed files with 210 additions and 1 deletions
+9 -1
View File
@@ -33,6 +33,13 @@
"protocol": "tcp",
"from": "mesh",
"why": "the mesh bus — every link the mesh has, over TLS, reached across the overlay"
},
{
"name": "bus-websocket",
"port": 4223,
"protocol": "tcp",
"from": "mesh",
"why": "the bus over WebSocket, for a page in a browser on the overlay — the view's read-only user, no TLS because the overlay is WireGuard (research 036)"
}
],
"tools": [
@@ -98,7 +105,7 @@
"id": "server-conf",
"type": "file",
"path": "/var/lib/nats-module/conf/nats.conf",
"content": "# The nats module's own server settings. Declared by the module, because a port, a TLS path\n# and a store directory are properties of the container this module raises: they live in its\n# image and its mounts and change when it does.\n#\n# The mesh writes accounts.conf beside this one and nothing else. A controller that wrote the\n# whole file would have to be kept in step with a Dockerfile it never sees.\n\nport: 4222\n# Monitoring on every interface *inside* the container, so the publish below can reach it; the publish\n# is 127.0.0.1:8222 on the machine, so nothing beyond the machine reaches it, and the module guards 8222\n# too. Bound to the container's own loopback until 2026-10-04, the published port answered nothing\n# (connection reset), and the only way in was `docker exec`.\nhttp: 0.0.0.0:8222\n\n# The mesh's own broker certificate — the one every machine already pins by fingerprint and the\n# controller already trusts (MESH_BROKER_CERTIFICATE). Serving the new bus with it means no\n# machine's pin changes when it moves, and no second certificate exists to be wrong about.\ntls {\n cert_file: \"/tls/tls.crt\"\n key_file: \"/tls/tls.key\"\n}\n\n# **No `verify`, deliberately, and it was `verify: true` until a probe ran this image.** That\n# setting makes the server demand a *client* certificate, and nothing in the mesh presents one: a\n# host pins this server's exact certificate and authenticates with the password the mesh minted\n# (novox/hq ADR 0004, design 25 §4), and so does a module's runtime. With it on, every connection\n# in the mesh is refused at the TLS handshake, before any password is looked at — and the error is\n# \"client didn't provide a certificate\", which reads as a client fault.\n#\n# TLS is still required: a tls block is what makes it required, and verify only decides whether\n# client certificates are checked. What is given up is a second factor the mesh has no machinery\n# to issue or rotate — a certificate per module per node — and what is kept is stronger than a\n# name check in both directions: an exact pin outward, a per-user password inward.\n\njetstream {\n store_dir: \"/data\"\n}\n\n# Every user of the mesh, composed by the controller and rewritten whenever a module is\n# assigned, a node enrols or a person's access changes.\n#\n# **Relative, and in this same directory, because it has to be.** An absolute include path is\n# resolved relative to the including file's directory, not from the root: nats-server given\n# `include /etc/nats/accounts.conf` from /etc/nats-server/nats.conf looks for\n# /etc/nats-server/etc/nats/accounts.conf and refuses to start. Verified against the server.\ninclude accounts.conf\n",
"content": "# The nats module's own server settings. Declared by the module, because a port, a TLS path\n# and a store directory are properties of the container this module raises: they live in its\n# image and its mounts and change when it does.\n#\n# The mesh writes accounts.conf beside this one and nothing else. A controller that wrote the\n# whole file would have to be kept in step with a Dockerfile it never sees.\n\nport: 4222\n# Monitoring on every interface *inside* the container, so the publish below can reach it; the publish\n# is 127.0.0.1:8222 on the machine, so nothing beyond the machine reaches it, and the module guards 8222\n# too. Bound to the container's own loopback until 2026-10-04, the published port answered nothing\n# (connection reset), and the only way in was `docker exec`.\nhttp: 0.0.0.0:8222\n\n# The mesh's own broker certificate — the one every machine already pins by fingerprint and the\n# controller already trusts (MESH_BROKER_CERTIFICATE). Serving the new bus with it means no\n# machine's pin changes when it moves, and no second certificate exists to be wrong about.\ntls {\n cert_file: \"/tls/tls.crt\"\n key_file: \"/tls/tls.key\"\n}\n\n# **No `verify`, deliberately, and it was `verify: true` until a probe ran this image.** That\n# setting makes the server demand a *client* certificate, and nothing in the mesh presents one: a\n# host pins this server's exact certificate and authenticates with the password the mesh minted\n# (novox/hq ADR 0004, design 25 §4), and so does a module's runtime. With it on, every connection\n# in the mesh is refused at the TLS handshake, before any password is looked at — and the error is\n# \"client didn't provide a certificate\", which reads as a client fault.\n#\n# TLS is still required: a tls block is what makes it required, and verify only decides whether\n# client certificates are checked. What is given up is a second factor the mesh has no machinery\n# to issue or rotate — a certificate per module per node — and what is kept is stronger than a\n# name check in both directions: an exact pin outward, a per-user password inward.\n\n# A WebSocket listener, for a client in a browser (nats.ws; novox/hq research 036, gap G1): the issue\n# tracker's view page watches the bus directly, as the read-only user the controller composes for it\n# (`bus view-credential`). Published from the mesh like the bus port (`listens`, from mesh): the machine's\n# filter admits it from the overlay only, and nothing off the overlay reaches it.\n#\n# **`no_tls`, and only because the overlay is WireGuard.** Every byte that reaches this port has crossed the\n# tunnel, encrypted and authenticated by the peers' keys before it was on the wire; the listener adds no\n# second layer, because a browser would have to be made to trust the mesh's own certificate for it, and off\n# the overlay the port is closed. The user still authenticates: the password the mesh minted, checked by the\n# server, with a permission list that grants it reads and nothing else. The client port beside it keeps its\n# TLS; nothing of the mesh moves here. Compression is on: a page watching a bucket reads JSON.\nwebsocket {\n listen: \"0.0.0.0:4223\"\n no_tls: true\n compression: true\n}\n\njetstream {\n store_dir: \"/data\"\n}\n\n# Every user of the mesh, composed by the controller and rewritten whenever a module is\n# assigned, a node enrols or a person's access changes.\n#\n# **Relative, and in this same directory, because it has to be.** An absolute include path is\n# resolved relative to the including file's directory, not from the root: nats-server given\n# `include /etc/nats/accounts.conf` from /etc/nats-server/nats.conf looks for\n# /etc/nats-server/etc/nats/accounts.conf and refuses to start. Verified against the server.\ninclude accounts.conf\n",
"mode": "0644"
},
{
@@ -107,6 +114,7 @@
"name": "mesh-broker-nats",
"ports": [
"4222:4222",
"4223:4223",
"127.0.0.1:8222:8222"
],
"volumes": [