fix(#2417): trust hive ca in forge container for self-signed webhook delivery

This commit is contained in:
damocles 2026-07-14 18:17:16 +02:00 committed by mara
commit 15e56986ad

View file

@ -8,6 +8,22 @@ let
cfg = config.services.hyperhive.forge;
gatewayCfg = config.services.hyperhive.gateway;
hyperhiveDomain = config.services.hyperhive.domain;
tlsCfg = config.services.hyperhive.tls;
# Self-signed gateway TLS: forgejo (Go) validates outbound webhook
# deliveries (e.g. the config-PR webhook to https://<domain>/webhook/...)
# against its system cert store, which lacks the runtime-generated hive
# CA — so delivery fails with an x509 "unknown authority". Go has no
# additive trust env var (SSL_CERT_FILE *replaces* the default bundle),
# 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;
# with an operator cert / ACME the public chain already validates and
# this whole block drops out. `gateway.useSelfSigned` is the single
# source of truth for the self-signed condition (no duplicated logic).
useSelfSigned = gatewayCfg.useSelfSigned;
caHostPath = "${tlsCfg.stateDir}/ca.pem";
caContainerPath = "/run/hive-ca/ca.pem";
forgeCaBundle = "/run/hive-forge-ca/ca-bundle.crt";
# ROOT_URL forgejo advertises in clone links + outbound URLs. When
# served behind the gateway, `cfg.domain` doubles as both the
@ -303,6 +319,15 @@ in
}
];
# The hive CA cert is generated at runtime by the host `hive-tls-ca`
# service. Order the container after it (self-signed mode only) so the
# bind source exists before nspawn sets the CA mount up — a
# condition-skipped/late CA would otherwise fail the container start.
systemd.services."container@hive-forge" = lib.mkIf useSelfSigned {
after = [ "hive-tls-ca.service" ];
requires = [ "hive-tls-ca.service" ];
};
containers.hive-forge = {
autoStart = true;
ephemeral = false;
@ -311,6 +336,17 @@ in
# and agent containers (which also share host netns) reach it
# via plain `localhost`.
privateNetwork = false;
# Self-signed mode: bind ONLY the public hive CA cert (never the
# `hive-tls` state dir — it holds the CA + leaf private keys) so
# forgejo can trust the gateway's self-signed leaf for outbound
# webhook delivery. The combined bundle is assembled at container
# start by hive-forge-ca-bundle below.
bindMounts = lib.optionalAttrs useSelfSigned {
${caContainerPath} = {
hostPath = caHostPath;
isReadOnly = true;
};
};
config =
{ pkgs, ... }:
let
@ -447,6 +483,36 @@ in
"d /var/lib/forgejo/data/actions_artifacts 0750 forgejo forgejo - -"
];
# Self-signed mode: assemble the combined TLS trust bundle
# (system CAs + the bind-mounted hive CA) forgejo's Go HTTP
# client validates outbound webhook deliveries against. Go's
# SSL_CERT_FILE *replaces* the default bundle, so we concatenate
# rather than point at the CA alone — otherwise mirror fetches
# from public hosts would lose their trust anchors. Runs before
# forgejo each boot; /run is tmpfs so the bundle is rebuilt from
# the current CA every start.
systemd.services.hive-forge-ca-bundle = lib.mkIf useSelfSigned {
description = "assemble forgejo TLS trust bundle (system CAs + hive CA)";
wantedBy = [ "forgejo.service" ];
before = [ "forgejo.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
SyslogIdentifier = "hive-forge-ca-bundle";
};
path = [ pkgs.coreutils ];
script = ''
set -euo pipefail
install -d -m 0755 /run/hive-forge-ca
cat /etc/ssl/certs/ca-certificates.crt ${caContainerPath} \
> ${forgeCaBundle}
chmod 0644 ${forgeCaBundle}
'';
};
# Point forgejo's Go TLS stack at the combined bundle so webhook
# delivery to the self-signed gateway validates.
systemd.services.forgejo.environment.SSL_CERT_FILE = lib.mkIf useSelfSigned forgeCaBundle;
# Ensure Forgejo has a usable GPG signing key so UI merges / CRUD
# commits are signed instead of erroring "does not have a signing
# key". This service (a) generates a key in forgejo's persistent