hive-matrix: reject an override of registrationTokenFile instead of desyncing hive-c0re
This commit is contained in:
parent
5c658e9bcf
commit
9d55cba786
3 changed files with 76 additions and 3 deletions
|
|
@ -255,6 +255,9 @@ pub const FORGE_CORE_TOKEN: &str = "/var/lib/hyperhive/forge-core-token";
|
||||||
|
|
||||||
/// `matrix-register-token` — shared matrix registration token.
|
/// `matrix-register-token` — shared matrix registration token.
|
||||||
// nix: bind-mounted into the tuwunel/matrix container (hive-matrix.nix) — must match.
|
// nix: bind-mounted into the tuwunel/matrix container (hive-matrix.nix) — must match.
|
||||||
|
// Not operator-option-driven: `registrationTokenFile` is `internal` on the nix
|
||||||
|
// side, and an `assertions` entry there rejects any attempt to move it, so this
|
||||||
|
// literal can never diverge from it.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn matrix_register_token() -> PathBuf {
|
pub fn matrix_register_token() -> PathBuf {
|
||||||
state_root().join("matrix-register-token")
|
state_root().join("matrix-register-token")
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,12 @@ let
|
||||||
# login into an intermittent one.
|
# login into an intermittent one.
|
||||||
matrixSecretPath = "/var/lib/tuwunel-oidc/${cfg.sso.clientId}.secret";
|
matrixSecretPath = "/var/lib/tuwunel-oidc/${cfg.sso.clientId}.secret";
|
||||||
|
|
||||||
|
# Single source for the registration-token path: the option's `default`,
|
||||||
|
# its `config`-level `mkDefault` self-definition, and the assertion that
|
||||||
|
# rejects a moved path all read this same binding rather than repeating
|
||||||
|
# the string literal. See `registrationTokenFile`'s own comment below.
|
||||||
|
registrationTokenPath = "/var/lib/hyperhive/matrix-register-token";
|
||||||
|
|
||||||
# ⚠️ tuwunel does NOT read the path above directly, and this indirection
|
# ⚠️ tuwunel does NOT read the path above directly, and this indirection
|
||||||
# is not ceremony. Upstream's own words: "under systemd the path must be
|
# is not ceremony. Upstream's own words: "under systemd the path must be
|
||||||
# visible to the service after sandboxing (ReadWritePaths / ProtectHome),
|
# visible to the service after sandboxing (ReadWritePaths / ProtectHome),
|
||||||
|
|
@ -452,7 +458,8 @@ in
|
||||||
|
|
||||||
registrationTokenFile = lib.mkOption {
|
registrationTokenFile = lib.mkOption {
|
||||||
type = lib.types.path;
|
type = lib.types.path;
|
||||||
default = "/var/lib/hyperhive/matrix-register-token";
|
internal = true;
|
||||||
|
default = registrationTokenPath;
|
||||||
description = ''
|
description = ''
|
||||||
Host path to a file containing the matrix registration token
|
Host path to a file containing the matrix registration token
|
||||||
tuwunel reads to authorise new-account creation. The token is
|
tuwunel reads to authorise new-account creation. The token is
|
||||||
|
|
@ -461,8 +468,28 @@ in
|
||||||
tuwunel container at the same path. Agents never see this
|
tuwunel container at the same path. Agents never see this
|
||||||
token — hive-c0re uses it to provision per-agent accounts
|
token — hive-c0re uses it to provision per-agent accounts
|
||||||
and the agent only receives the resulting `access_token`.
|
and the agent only receives the resulting `access_token`.
|
||||||
Override only when integrating with externally-managed
|
|
||||||
registration tokens.
|
Not operator-settable — `hive-c0re`'s Rust side derives this
|
||||||
|
same path independently (`paths::matrix_register_token()`) with
|
||||||
|
nothing wiring an override across, so a moved path used to
|
||||||
|
desync the two silently. An externally-managed token is
|
||||||
|
delivered by writing into *this* fixed path instead of moving it —
|
||||||
|
see `glue-matrix-bao-token.nix`, which fetches from the swarm
|
||||||
|
secret store and overwrites this file in place.
|
||||||
|
|
||||||
|
Enforced by an `assertions` entry below rather than `readOnly`:
|
||||||
|
the `config` block gives this option its own `mkDefault`
|
||||||
|
definition (lowest priority) so any real override — an operator's,
|
||||||
|
or the pre-rename `swarm.matrix.registrationTokenFile` shim's —
|
||||||
|
still resolves cleanly instead of crashing eval with nixpkgs'
|
||||||
|
generic "read-only, set multiple times" message; the assertion
|
||||||
|
then names the actual problem. (`readOnly` was the first attempt —
|
||||||
|
atlas caught that it only rejects a *second* definition,
|
||||||
|
so with nothing else defining the option a lone override sailed
|
||||||
|
through silently, same desync as before with a lock that wasn't
|
||||||
|
locked. Fixed by defining the value here instead of leaving it on
|
||||||
|
`default` alone, but a proper `assertions` message beat re-adding
|
||||||
|
`readOnly` on top once the option had a real definition either way.)
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -507,6 +534,13 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf deployCfg.matrix.enable {
|
config = lib.mkIf deployCfg.matrix.enable {
|
||||||
|
# The option's own value, defined explicitly rather than left on its
|
||||||
|
# bare `default` — `mkDefault` so a real override (an operator's, or
|
||||||
|
# the pre-rename shim's) still resolves cleanly rather than crashing
|
||||||
|
# eval; the `assertions` entry below is what actually rejects it, with
|
||||||
|
# a message naming the reason. See that option's own comment above.
|
||||||
|
services.hyperhive.deploy.matrix.registrationTokenFile = lib.mkDefault registrationTokenPath;
|
||||||
|
|
||||||
# Matrix's own gateway surface: the sub-domain vhost, the name the
|
# Matrix's own gateway surface: the sub-domain vhost, the name the
|
||||||
# hive resolver answers for, and the Accept-header map that vhost's
|
# hive resolver answers for, and the Accept-header map that vhost's
|
||||||
# SPA fallback reads. All three are matrix knowledge and none of
|
# SPA fallback reads. All three are matrix knowledge and none of
|
||||||
|
|
@ -676,6 +710,27 @@ in
|
||||||
httpPort and has no such name.
|
httpPort and has no such name.
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
# mkDefault above lets an override resolve instead of crashing
|
||||||
|
# eval — this is the actual rejection, with a message that names
|
||||||
|
# the real fix instead of nixpkgs' generic conflicting-definition
|
||||||
|
# text. Covers both the current path and the pre-rename
|
||||||
|
# `swarm.matrix.registrationTokenFile` shim in one check, since
|
||||||
|
# both land on the same merged option.
|
||||||
|
assertion = deployCfg.matrix.registrationTokenFile == registrationTokenPath;
|
||||||
|
message = ''
|
||||||
|
services.hyperhive.deploy.matrix.registrationTokenFile is fixed at
|
||||||
|
${registrationTokenPath} and cannot be moved — hive-c0re's Rust
|
||||||
|
side derives this same path independently and has no way to learn
|
||||||
|
an override, so moving it desyncs the two silently instead of
|
||||||
|
loudly.
|
||||||
|
|
||||||
|
Integrating an externally-managed registration token? Deliver it
|
||||||
|
by writing into ${registrationTokenPath} instead of pointing this
|
||||||
|
option elsewhere — see glue-matrix-bao-token.nix, which does
|
||||||
|
exactly that from the swarm secret store.
|
||||||
|
'';
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
# One declaration, two readers. The homeserver knows its own callback
|
# One declaration, two readers. The homeserver knows its own callback
|
||||||
|
|
|
||||||
|
|
@ -296,6 +296,21 @@ let
|
||||||
builtins.elem httpPort ports
|
builtins.elem httpPort ports
|
||||||
&& matrixOldPath.containers.hive-matrix.bindMounts ? "/etc/matrix/register.token";
|
&& matrixOldPath.containers.hive-matrix.bindMounts ? "/etc/matrix/register.token";
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
# registrationTokenFile lost its override capability entirely
|
||||||
|
# (bao-delivered secrets don't need one — glue-matrix-bao-token.nix
|
||||||
|
# already writes into the fixed path instead of moving it), unlike its
|
||||||
|
# five siblings in the same rename. `matrixOldPath` above proves the
|
||||||
|
# override still *resolves* (mkDefault, not a crash) — this proves it
|
||||||
|
# also gets *rejected*, by a named assertion rather than nixpkgs' generic
|
||||||
|
# conflicting-definition text. Reads `.assertions` directly (cheap: a
|
||||||
|
# list of `{assertion; message;}`, not `system.build.toplevel`) rather
|
||||||
|
# than forcing a real build just to observe a boolean.
|
||||||
|
name = "overriding registrationTokenFile (even via the pre-rename shim) trips a named assertion, not a silent desync";
|
||||||
|
ok = lib.any (
|
||||||
|
a: !a.assertion && lib.hasInfix "registrationTokenFile" a.message
|
||||||
|
) matrixOldPath.assertions;
|
||||||
|
}
|
||||||
{
|
{
|
||||||
# Reads the DELIVERY UNIT, not the options: `responderConfigured` gates
|
# Reads the DELIVERY UNIT, not the options: `responderConfigured` gates
|
||||||
# whether it exists at all, and the seed path is interpolated into its
|
# whether it exists at all, and the seed path is interpolated into its
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue