fix(#3396): move grafana and matrix onto the shared trust-bundle helper

Both hand-rolled the same concat with wantedBy + before and no requires,
so a failed assembly let the consumer start against a missing file and
trust nothing at all -- which fails every outbound TLS call while the unit
looks healthy. The helper puts requires on the consumer and verifies the
assembled bundle before moving it into place.

Removes each module's now-dead useSelfSigned binding: its only use was the
mkIf on the deleted block, and nix does not warn about an unused let
binding. Also corrects two comments the deletion orphaned -- one described
the removed path binding, the other pointed at a bundle service that no
longer exists.
This commit is contained in:
atlas 2026-08-17 20:29:22 +02:00
commit 9073c9b3f7
2 changed files with 31 additions and 74 deletions

View file

@ -14,13 +14,10 @@ let
# Same runtime→build-time bridge hive-ci and hive-forge already cross:
# binds the hive trust bundle (which folds in the swarm root) into the
# container and orders the container after `hive-tls-ca.service`. The
# *consumption* is per-runtime and stays here — see the bundle service
# in the container config below.
# assembled bundle itself comes from `caTrust.trustBundle`, imported in
# the container config below.
caTrust = import ./lib/hive-ca-trust.nix { inherit lib tlsCfg gatewayCfg; };
useSelfSigned = caTrust.useSelfSigned;
# tuwunel's own combined bundle, assembled at start. /run is tmpfs, so
# it is rebuilt from the current CA every boot rather than going stale.
matrixCaBundle = "/run/hive-matrix-ca/ca-bundle.crt";
swarmDomain = config.services.hyperhive.swarm.domain;
# `url` is the half of the authelia module that exists on EVERY hive —
@ -790,6 +787,24 @@ in
config =
{ ... }:
{
imports = [
# tuwunel's rustls verifier resolves through `rustls-native-certs`
# → `openssl-probe`, which reads `SSL_CERT_FILE` — so the
# openssl-shaped variable is the lever despite tuwunel linking no
# openssl.
#
# ⚠️ The helper CONCATENATES, and that is load-bearing here beyond
# the usual reason: `SSL_CERT_FILE` replaces the default location,
# so naming the hive anchor alone would drop every public CA and
# break federation with the wider matrix network — trading a small
# outage for a much larger one.
(caTrust.trustBundle {
inherit pkgs;
name = cfg.machine;
consumers = [ "tuwunel" ];
})
];
system.stateVersion = "26.05";
# Shared host netns: this container's own firewall.service
@ -933,41 +948,6 @@ in
# the unit's start — a credentials path does.
++ lib.optional cfg.sso.enable "oidc_client_secret:${toString cfg.sso.clientSecretFile}";
# Federation TLS against a peer whose cert chains to the swarm
# root: tuwunel's outbound client is reqwest with the `rustls`
# feature, which builds a `rustls_platform_verifier::Verifier`
# and — because tuwunel calls `tls_certs_merge` rather than
# `tls_certs_only` — keeps the platform roots alongside its
# compiled-in webpki set. On Linux that verifier resolves through
# `rustls-native-certs` → `openssl-probe`, which reads
# `SSL_CERT_FILE`. So the openssl-shaped variable IS the lever
# here, despite tuwunel linking no openssl.
#
# ⚠️ CONCATENATE, never point at the anchor alone. `openssl-probe`
# uses `SSL_CERT_FILE` *instead of* the default location, so
# naming just the hive bundle would drop every public CA and
# break federation with the wider matrix network — trading a
# small outage for a much larger one.
systemd.services.hive-matrix-ca-bundle = lib.mkIf useSelfSigned {
description = "assemble tuwunel TLS trust bundle (system CAs + hive CA)";
wantedBy = [ "tuwunel.service" ];
before = [ "tuwunel.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
SyslogIdentifier = "hive-matrix-ca-bundle";
};
path = [ pkgs.coreutils ];
script = ''
set -euo pipefail
install -d -m 0755 /run/hive-matrix-ca
cat /etc/ssl/certs/ca-certificates.crt ${caTrust.caContainerPath} \
> ${matrixCaBundle}
chmod 0644 ${matrixCaBundle}
'';
};
systemd.services.tuwunel.environment.SSL_CERT_FILE = lib.mkIf useSelfSigned matrixCaBundle;
environment.systemPackages = [ cfg.package ];
};
};

View file

@ -23,7 +23,6 @@ let
swarmDomain = hyperhiveCfg.swarm.domain;
caTrust = import ./lib/hive-ca-trust.nix { inherit lib tlsCfg gatewayCfg; };
useSelfSigned = caTrust.useSelfSigned;
# Total on a null swarm domain for the same reason every sibling module is:
# the required-domain assertion in hive-network.nix should be what an
@ -61,7 +60,6 @@ let
# the authelia client and Grafana itself.
redirectUri = "https://${cfg.domain}/login/generic_oauth";
grafanaCaBundle = "/run/swarm-grafana-ca/ca-bundle.crt";
in
{
options.services.hyperhive.swarm.grafana = {
@ -357,6 +355,16 @@ in
inherit (networkCfg) bridgeIp;
dnsConsumers = [ "grafana.service" ];
})
# Grafana is Go, and `SSL_CERT_FILE` *replaces* the default store
# rather than adding to it — so it needs the system CAs and the
# hive CA concatenated, not the CA alone. Without it the browser
# half of the login succeeds and the server-to-server token
# exchange fails `x509: unknown authority`.
(caTrust.trustBundle {
inherit pkgs;
name = cfg.machine;
consumers = [ "grafana" ];
})
];
system.stateVersion = "26.05";
@ -371,37 +379,6 @@ in
# the boundary after start.
networking.resolvconf.enable = lib.mkForce false;
# Self-signed mode: Grafana is Go, and Go's `SSL_CERT_FILE`
# *replaces* the default bundle rather than adding to it — so
# concatenate the system CAs with the bind-mounted hive CA instead
# of pointing at the CA alone, which would lose every public
# anchor. /run is tmpfs, so this is rebuilt from the current CA
# each boot rather than going stale.
#
# Without it the browser half of the login succeeds and the
# server-to-server token exchange fails with an x509 "unknown
# authority" — the same shape of failure the swarm queue hit.
systemd.services.swarm-grafana-ca-bundle = lib.mkIf useSelfSigned {
description = "assemble Grafana TLS trust bundle (system CAs + hive CA)";
wantedBy = [ "grafana.service" ];
before = [ "grafana.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
SyslogIdentifier = "swarm-grafana-ca-bundle";
};
path = [ pkgs.coreutils ];
script = ''
set -euo pipefail
install -d -m 0755 /run/swarm-grafana-ca
cat /etc/ssl/certs/ca-certificates.crt ${caTrust.caContainerPath} \
> ${grafanaCaBundle}
chmod 0644 ${grafanaCaBundle}
'';
};
systemd.services.grafana.environment.SSL_CERT_FILE = lib.mkIf useSelfSigned grafanaCaBundle;
# Grafana's `secret_key` has **no default in nixpkgs** and an
# assertion refuses the build without one — which is how the first
# version of this module broke a deploy. It signs the datasource