refactor(#2427): extract shared hive-CA trust bind-mount helper
hive-ci and hive-forge both bind the runtime-generated hive CA cert
read-only and order their container@ unit after hive-tls-ca.service so
the bind source exists before nspawn sets the mount up — the same
bind-mount + ordering + rationale duplicated verbatim in two modules.
Extract that language-agnostic half into a pure helper,
nix/host-modules/lib/hive-ca-trust.nix, taking a container name and
returning { useSelfSigned, caContainerPath, bindMount, containerOrdering }.
The per-runtime consumption stays at each call site (hive-ci's additive
NODE_EXTRA_CA_CERTS, hive-forge's Go SSL_CERT_FILE concat). hive-ci folds
containerOrdering into its existing mkMerge alongside the TimeoutStartSec
bump.
The helper is a pure function, not a module: host-modules/default.nix is
an explicit aggregator (not a glob) and the docs eval imports that same
aggregator, so the lib/ file is never picked up as a module. A third
outbound-TLS-trusting container no longer means a third copy-paste.
This commit is contained in:
parent
edf9fd036e
commit
6c4ef5f798
3 changed files with 92 additions and 49 deletions
|
|
@ -17,11 +17,12 @@ let
|
||||||
# `upload-artifact`, which POSTs to the ROOT_URL-derived artifact endpoint)
|
# `upload-artifact`, which POSTs to the ROOT_URL-derived artifact endpoint)
|
||||||
# reject the chain, since Node trusts only its bundled CA bundle, not the
|
# reject the chain, since Node trusts only its bundled CA bundle, not the
|
||||||
# system store. Trust the hive CA explicitly via NODE_EXTRA_CA_CERTS below.
|
# system store. Trust the hive CA explicitly via NODE_EXTRA_CA_CERTS below.
|
||||||
# `gateway.useSelfSigned` is the gateway module's single source of truth
|
# The bind-mount + `container@` ordering that makes the CA reachable are
|
||||||
# for the self-signed condition (no duplicated derivation here).
|
# shared with hive-forge via the `hive-ca-trust` helper; only the Node
|
||||||
useSelfSigned = gatewayCfg.useSelfSigned;
|
# `NODE_EXTRA_CA_CERTS` consumption is hive-ci-specific.
|
||||||
caHostPath = "${tlsCfg.stateDir}/ca.pem";
|
caTrust = import ./lib/hive-ca-trust.nix { inherit lib tlsCfg gatewayCfg; };
|
||||||
caContainerPath = "/run/hive-ca/ca.pem";
|
useSelfSigned = caTrust.useSelfSigned;
|
||||||
|
caContainerPath = caTrust.caContainerPath;
|
||||||
|
|
||||||
# hive-c0re writes its own admin token here on first forge startup.
|
# hive-c0re writes its own admin token here on first forge startup.
|
||||||
# The token has read:admin + write:admin scopes — sufficient to call
|
# The token has read:admin + write:admin scopes — sufficient to call
|
||||||
|
|
@ -347,15 +348,11 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# Self-signed mode: the CA cert the bind-mount above sources is
|
# `caTrust.containerOrdering` orders this unit after `hive-tls-ca.service`
|
||||||
# generated by the host `hive-tls-ca` service. Order the container after
|
# in self-signed mode (see the hive-ca-trust helper); merged with the
|
||||||
# it so the bind source exists before nspawn sets the mount up (a
|
# hive-ci-specific start-timeout bump below.
|
||||||
# condition-skipped/late CA would otherwise fail the container start).
|
|
||||||
systemd.services."container@hive-ci" = lib.mkMerge [
|
systemd.services."container@hive-ci" = lib.mkMerge [
|
||||||
(lib.mkIf useSelfSigned {
|
caTrust.containerOrdering
|
||||||
after = [ "hive-tls-ca.service" ];
|
|
||||||
requires = [ "hive-tls-ca.service" ];
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
# gitea-runner registration (hive-ci-prefetch, host-side)
|
# gitea-runner registration (hive-ci-prefetch, host-side)
|
||||||
# sits on the boot-critical path — the container's nspawn readiness
|
# sits on the boot-critical path — the container's nspawn readiness
|
||||||
|
|
@ -394,18 +391,11 @@ in
|
||||||
isReadOnly = true;
|
isReadOnly = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
# Self-signed mode: bind ONLY the public hive CA cert (never the
|
# Self-signed mode: bind the public hive CA cert read-only so the
|
||||||
# `hive-tls` state dir — it holds the CA + leaf private keys) so the
|
# runner's Node actions trust the gateway/forge leaf (consumed via
|
||||||
# runner's Node actions can trust the gateway/forge self-signed leaf
|
# NODE_EXTRA_CA_CERTS in the container config). Shared bind-mount +
|
||||||
# (see NODE_EXTRA_CA_CERTS in the container config). Source generated
|
# ordering come from the hive-ca-trust helper.
|
||||||
# by the host `hive-tls-ca` service; the container@hive-ci ordering
|
// caTrust.bindMount;
|
||||||
# below guarantees it exists before this mount is set up.
|
|
||||||
// lib.optionalAttrs useSelfSigned {
|
|
||||||
${caContainerPath} = {
|
|
||||||
hostPath = caHostPath;
|
|
||||||
isReadOnly = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config =
|
config =
|
||||||
{ pkgs, lib, ... }:
|
{ pkgs, lib, ... }:
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,13 @@ let
|
||||||
# so bind the public CA in and hand forgejo a combined bundle (system
|
# so bind the public CA in and hand forgejo a combined bundle (system
|
||||||
# CAs + hive CA) via SSL_CERT_FILE. Only active in self-signed mode;
|
# CAs + hive CA) via SSL_CERT_FILE. Only active in self-signed mode;
|
||||||
# with an operator cert / ACME the public chain already validates and
|
# with an operator cert / ACME the public chain already validates and
|
||||||
# this whole block drops out. `gateway.useSelfSigned` is the single
|
# this whole block drops out. The bind-mount + `container@` ordering
|
||||||
# source of truth for the self-signed condition (no duplicated logic).
|
# that make the CA reachable are shared with hive-ci via the
|
||||||
useSelfSigned = gatewayCfg.useSelfSigned;
|
# `hive-ca-trust` helper; only the Go SSL_CERT_FILE concat below is
|
||||||
caHostPath = "${tlsCfg.stateDir}/ca.pem";
|
# hive-forge-specific.
|
||||||
caContainerPath = "/run/hive-ca/ca.pem";
|
caTrust = import ../lib/hive-ca-trust.nix { inherit lib tlsCfg gatewayCfg; };
|
||||||
|
useSelfSigned = caTrust.useSelfSigned;
|
||||||
|
caContainerPath = caTrust.caContainerPath;
|
||||||
forgeCaBundle = "/run/hive-forge-ca/ca-bundle.crt";
|
forgeCaBundle = "/run/hive-forge-ca/ca-bundle.crt";
|
||||||
|
|
||||||
# ROOT_URL forgejo advertises in clone links + outbound URLs. When
|
# ROOT_URL forgejo advertises in clone links + outbound URLs. When
|
||||||
|
|
@ -319,14 +321,10 @@ in
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
# The hive CA cert is generated at runtime by the host `hive-tls-ca`
|
# `caTrust.containerOrdering` orders this unit after `hive-tls-ca.service`
|
||||||
# service. Order the container after it (self-signed mode only) so the
|
# in self-signed mode (see the hive-ca-trust helper), so the CA bind
|
||||||
# bind source exists before nspawn sets the CA mount up — a
|
# source exists before nspawn sets the mount up.
|
||||||
# condition-skipped/late CA would otherwise fail the container start.
|
systemd.services."container@hive-forge" = caTrust.containerOrdering;
|
||||||
systemd.services."container@hive-forge" = lib.mkIf useSelfSigned {
|
|
||||||
after = [ "hive-tls-ca.service" ];
|
|
||||||
requires = [ "hive-tls-ca.service" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
containers.hive-forge = {
|
containers.hive-forge = {
|
||||||
autoStart = true;
|
autoStart = true;
|
||||||
|
|
@ -336,17 +334,12 @@ in
|
||||||
# and agent containers (which also share host netns) reach it
|
# and agent containers (which also share host netns) reach it
|
||||||
# via plain `localhost`.
|
# via plain `localhost`.
|
||||||
privateNetwork = false;
|
privateNetwork = false;
|
||||||
# Self-signed mode: bind ONLY the public hive CA cert (never the
|
# Self-signed mode: bind the public hive CA cert read-only so forgejo
|
||||||
# `hive-tls` state dir — it holds the CA + leaf private keys) so
|
# can trust the gateway's self-signed leaf for outbound webhook
|
||||||
# forgejo can trust the gateway's self-signed leaf for outbound
|
# delivery (combined bundle assembled at container start by
|
||||||
# webhook delivery. The combined bundle is assembled at container
|
# hive-forge-ca-bundle below). Shared bind-mount + ordering come from
|
||||||
# start by hive-forge-ca-bundle below.
|
# the hive-ca-trust helper.
|
||||||
bindMounts = lib.optionalAttrs useSelfSigned {
|
bindMounts = caTrust.bindMount;
|
||||||
${caContainerPath} = {
|
|
||||||
hostPath = caHostPath;
|
|
||||||
isReadOnly = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config =
|
config =
|
||||||
{ pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
let
|
let
|
||||||
|
|
|
||||||
60
nix/host-modules/lib/hive-ca-trust.nix
Normal file
60
nix/host-modules/lib/hive-ca-trust.nix
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Shared hive-CA trust plumbing for containers that must trust the
|
||||||
|
# self-signed gateway/forge leaf for *outbound* TLS (webhook delivery,
|
||||||
|
# CI artifact upload, …). The hive CA is generated at runtime by the host
|
||||||
|
# `hive-tls-ca.service` (see `hive-tls.nix`) — it can't be baked into a
|
||||||
|
# derivation — so each such container binds the public `ca.pem` read-only
|
||||||
|
# and orders its `container@<name>` unit after `hive-tls-ca.service` so the
|
||||||
|
# bind source exists before nspawn sets the mount up.
|
||||||
|
#
|
||||||
|
# This is the language-agnostic half (bind-mount + systemd ordering). The
|
||||||
|
# *consumption* differs per runtime and stays at each call site: Node's
|
||||||
|
# `NODE_EXTRA_CA_CERTS` is additive (hive-ci), Go's `SSL_CERT_FILE` replaces
|
||||||
|
# the bundle so it needs a system-CAs+hive-CA concat step (hive-forge).
|
||||||
|
#
|
||||||
|
# Pure function — NOT a NixOS module (don't add it to the host-modules
|
||||||
|
# aggregator). Call it from a module's `let`:
|
||||||
|
#
|
||||||
|
# caTrust = import ./lib/hive-ca-trust.nix { inherit lib tlsCfg gatewayCfg; };
|
||||||
|
# # then, in the container:
|
||||||
|
# # bindMounts = { … } // caTrust.bindMount;
|
||||||
|
# # systemd.services."container@hive-ci" = lib.mkMerge [ caTrust.containerOrdering … ];
|
||||||
|
# # environment.NODE_EXTRA_CA_CERTS = caTrust.caContainerPath; # consumption, per-caller
|
||||||
|
#
|
||||||
|
# `tlsCfg` = config.services.hyperhive.tls
|
||||||
|
# `gatewayCfg` = config.services.hyperhive.gateway
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
tlsCfg,
|
||||||
|
gatewayCfg,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
# `gateway.useSelfSigned` is the single source of truth for the
|
||||||
|
# self-signed condition — no duplicated derivation.
|
||||||
|
useSelfSigned = gatewayCfg.useSelfSigned;
|
||||||
|
caHostPath = "${tlsCfg.stateDir}/ca.pem";
|
||||||
|
caContainerPath = "/run/hive-ca/ca.pem";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit useSelfSigned caContainerPath;
|
||||||
|
|
||||||
|
# Fold into the container's `bindMounts` via `//`. Binds ONLY the public
|
||||||
|
# CA cert (never the `hive-tls` state dir — it holds the CA + leaf private
|
||||||
|
# keys), read-only. Empty when not self-signed, so the whole trust path
|
||||||
|
# drops out cleanly.
|
||||||
|
bindMount = lib.optionalAttrs useSelfSigned {
|
||||||
|
${caContainerPath} = {
|
||||||
|
hostPath = caHostPath;
|
||||||
|
isReadOnly = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Fold into the caller's `container@<name>` unit (via `lib.mkMerge` if the
|
||||||
|
# caller adds its own keys, e.g. hive-ci's `TimeoutStartSec`). Orders the
|
||||||
|
# container after the host `hive-tls-ca.service` so the bind source exists
|
||||||
|
# before nspawn sets the mount up — a condition-skipped/late CA would
|
||||||
|
# otherwise fail the container start.
|
||||||
|
containerOrdering = lib.mkIf useSelfSigned {
|
||||||
|
after = [ "hive-tls-ca.service" ];
|
||||||
|
requires = [ "hive-tls-ca.service" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue