nats declares its own server settings, and where the mesh's users go
The split the controller now makes, from this side. The module's own configuration — ports, TLS, JetStream — is a declared file resource, because those are properties of this container and change when its image does. `bus-users` names where the mesh writes every account and permission, in the same directory, and the module's configuration includes it. **Both files in one directory because they have to be.** An absolute include path is resolved relative to the including file's directory: nats-server given `include /etc/nats/accounts.conf` from /etc/nats-server/nats.conf looks for /etc/nats-server/etc/nats/accounts.conf and refuses to start. Verified against the server, and recorded in the configuration itself where somebody moving a file will read it. **`verify: true` is gone, and it was refusing every connection in the mesh.** It makes the server demand a client certificate; a host pins this server's exact certificate and authenticates with the password the mesh minted, and presents none. Found by building this image and connecting to it as a host would. The entrypoint now waits for both files and watches the mesh's half: the module's own does not change without a new declaration, and that recreates the container anyway. Verified end to end against this image — the mesh's user list rewritten, the module noticing and reloading the server itself with no signal from outside, and the connection the mesh already had still working afterwards.
This commit is contained in:
+21
-13
@@ -3,9 +3,9 @@
|
||||
# configuration.
|
||||
#
|
||||
# **Why this exists inside the module** (novox/hq design 25 §5). The controller composes every
|
||||
# account and permission into one configuration file, and that file changes whenever a module is
|
||||
# added, reassigned, or a person's access is granted or revoked — which is often, and on the one
|
||||
# server everything else depends on. The host has no way to say "reload this container": a
|
||||
# account and permission into one file, and that file changes whenever a module is added,
|
||||
# reassigned, or a person's access is granted or revoked — which is often, and on the one server
|
||||
# everything else depends on. The host has no way to say "reload this container": a
|
||||
# container resource has `restart-on` and nothing else, and a container's `restart-on` means
|
||||
# *recreate* — every connection dropped and every in-flight JetStream ack lost, mid-flight, for a
|
||||
# permission change. `reload-on` is real but it is a *service* field, not a container's.
|
||||
@@ -19,21 +19,29 @@
|
||||
# here is declared `restart-on` or `reload-on`.
|
||||
set -eu
|
||||
|
||||
# **Two files, and only one of them is the mesh's** (novox/hq design 25 §4, task 1.7). CONF is this
|
||||
# module's own — ports, TLS, JetStream — declared in its manifest, because those are properties of
|
||||
# the container this module raises. USERS is every account and permission, composed by the
|
||||
# controller, and CONF includes it. So what is watched here is the mesh's half: the module's own
|
||||
# does not change without a new declaration, and that recreates the container anyway.
|
||||
CONF="${MESH_NATS_CONF:-/etc/nats/nats.conf}"
|
||||
USERS="${MESH_NATS_USERS:-/etc/nats/accounts.conf}"
|
||||
POLL="${MESH_NATS_CONF_POLL_SECONDS:-5}"
|
||||
|
||||
# The controller writes the configuration as part of the same declaration that creates this
|
||||
# container, but the two are not ordered against each other. Waiting is correct and starting
|
||||
# without one is not: nats-server would come up with its compiled-in defaults — no TLS, no
|
||||
# accounts, every subject open to anyone who can reach the port — and then be reloaded into
|
||||
# correctness a moment later. A bus that is briefly open to everything is not a bus that is
|
||||
# briefly wrong; it is an open bus.
|
||||
while [ ! -s "$CONF" ]; do
|
||||
echo "[nats] waiting for the mesh to compose $CONF"
|
||||
# Both are written as part of the same declaration that creates this container, but none of the
|
||||
# three are ordered against each other. Waiting is correct and starting without them is not:
|
||||
# nats-server given a configuration whose include is missing refuses to start, and one given no
|
||||
# configuration at all comes up with its compiled-in defaults — no TLS, no accounts, every subject
|
||||
# open to anyone who can reach the port. A bus that is briefly open to everything is not a bus that
|
||||
# is briefly wrong; it is an open bus.
|
||||
for needed in "$CONF" "$USERS"; do
|
||||
while [ ! -s "$needed" ]; do
|
||||
echo "[nats] waiting for the mesh to write $needed"
|
||||
sleep 1
|
||||
done
|
||||
done
|
||||
|
||||
digest() { sha256sum "$CONF" 2>/dev/null | cut -d' ' -f1; }
|
||||
digest() { sha256sum "$USERS" 2>/dev/null | cut -d' ' -f1; }
|
||||
|
||||
nats-server --config "$CONF" "$@" &
|
||||
server=$!
|
||||
@@ -52,7 +60,7 @@ while kill -0 "$server" 2>/dev/null; do
|
||||
[ -n "$now" ] || continue
|
||||
if [ "$now" != "$last" ]; then
|
||||
last=$now
|
||||
echo "[nats] configuration changed; reloading in place"
|
||||
echo "[nats] the mesh's user list changed; reloading in place"
|
||||
kill -HUP "$server" || true
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"scope": "mesh"
|
||||
}
|
||||
],
|
||||
"bus-users": "/var/lib/nats-module/conf/accounts.conf",
|
||||
"capabilities": [
|
||||
"container-runtime"
|
||||
],
|
||||
@@ -42,6 +43,13 @@
|
||||
"path": "/var/lib/nats-module/conf",
|
||||
"mode": "0700"
|
||||
},
|
||||
{
|
||||
"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\nhttp: 127.0.0.1:8222\n\ntls {\n cert_file: \"/tls/tls.crt\"\n key_file: \"/tls/tls.key\"\n ca_file: \"/tls/ca.crt\"\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 \u00a74), 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 \u2014 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 \u2014 a certificate per module per node \u2014 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",
|
||||
"mode": "0644"
|
||||
},
|
||||
{
|
||||
"id": "server",
|
||||
"type": "container",
|
||||
|
||||
Reference in New Issue
Block a user