feat(#594): gateway operator-cert TLS mode (tls.certDir)
add services.hyperhive.gateway.tls.certDir option: operators with a CA-signed cert (Let's Encrypt, corporate CA) point at the ACME output dir instead of using the auto-generated self-signed cert. - tls.certDir: host path bind-mounted r/o at /run/hive-tls/ in gateway - tls.certName / tls.keyName: filenames within certDir (default: cert.pem / key.pem, matches nixpkgs security.acme layout) - hasTls = selfSignedTls || certDir != null: publicScheme=https in both cases - assertion: selfSignedTls=true + certDir set together is an error - openFirewall: httpsPort opened in both TLS modes - docs/gateway.md: TLS modes table + operator-cert section - docs updated in swarm.md peer config reference in the cert TLS section when using operator cert, swarm peers can omit certFingerprint — standard CA bundle handles trust automatically.
This commit is contained in:
parent
79265e04f8
commit
44122c66de
2 changed files with 144 additions and 26 deletions
|
|
@ -191,13 +191,66 @@ in
|
|||
default = 443;
|
||||
example = 8443;
|
||||
description = ''
|
||||
TCP port for the TLS-terminated vhosts when `selfSignedTls`
|
||||
is enabled. Default 443. Setting `selfSignedTls = false`
|
||||
renders this option inert (the gateway listens on `port`
|
||||
only).
|
||||
TCP port for the TLS-terminated vhosts. Active when
|
||||
`selfSignedTls = true` OR `tls.certDir` is set. Default 443.
|
||||
Setting `selfSignedTls = false` and leaving `tls.certDir = null`
|
||||
renders this inert (the gateway listens on `port` only).
|
||||
'';
|
||||
};
|
||||
|
||||
tls = {
|
||||
certDir = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
example = lib.literalExpression ''"/var/lib/acme/example.com"'';
|
||||
description = ''
|
||||
Path to a host directory containing a TLS certificate and
|
||||
private key for nginx. When set, nginx listens on `httpsPort`
|
||||
and uses this cert, making `selfSignedTls` unnecessary —
|
||||
the auto-generated self-signed cert is skipped entirely.
|
||||
|
||||
The directory is bind-mounted read-only into the gateway
|
||||
container at `/run/hive-tls/`. nginx reads
|
||||
`<certDir>/<tls.certName>` and `<certDir>/<tls.keyName>`.
|
||||
Default filenames (`cert.pem` / `key.pem`) match the output
|
||||
layout of nixpkgs's `security.acme` module.
|
||||
|
||||
Typical ACME setup:
|
||||
```nix
|
||||
security.acme.certs."example.com" = { ... };
|
||||
services.hyperhive.gateway.tls.certDir =
|
||||
config.security.acme.certs."example.com".directory;
|
||||
services.hyperhive.gateway.selfSignedTls = false;
|
||||
```
|
||||
|
||||
When using an external CA cert, peer hives can declare this
|
||||
hive in `services.hyperhive.swarm.peers` without
|
||||
`certFingerprint` — the standard CA bundle validates.
|
||||
|
||||
Mutual exclusion: `selfSignedTls = true` and `tls.certDir`
|
||||
set together fails an assertion at eval time.
|
||||
'';
|
||||
};
|
||||
|
||||
certName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "cert.pem";
|
||||
description = ''
|
||||
Filename of the TLS certificate within `tls.certDir`. Defaults
|
||||
to `cert.pem` which matches nixpkgs's `security.acme` output.
|
||||
'';
|
||||
};
|
||||
|
||||
keyName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "key.pem";
|
||||
description = ''
|
||||
Filename of the TLS private key within `tls.certDir`. Defaults
|
||||
to `key.pem` which matches nixpkgs's `security.acme` output.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
auth = {
|
||||
enable = lib.mkEnableOption ''
|
||||
HTTP basic auth on the gateway using an htpasswd file. When
|
||||
|
|
@ -238,6 +291,15 @@ in
|
|||
or leave `localHostsEntry` at its default of false.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = !(cfg.selfSignedTls && cfg.tls.certDir != null);
|
||||
message = ''
|
||||
services.hyperhive.gateway.selfSignedTls = true and
|
||||
services.hyperhive.gateway.tls.certDir are mutually exclusive.
|
||||
Set `selfSignedTls = false` when providing an external cert
|
||||
via `tls.certDir`.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
# Ensure bind-mount sources exist at host boot before the gateway
|
||||
|
|
@ -291,23 +353,44 @@ in
|
|||
hostPath = "/var/lib/hyperhive/gateway";
|
||||
isReadOnly = true;
|
||||
};
|
||||
# Operator-provided TLS cert dir (e.g. Let's Encrypt / ACME).
|
||||
# Only mounted when `tls.certDir` is set; mutually exclusive with
|
||||
# `selfSignedTls = true` (assertion above). nginx reads cert +
|
||||
# key from `/run/hive-tls/<certName>` and `/run/hive-tls/<keyName>`.
|
||||
bindMounts."/run/hive-tls" = lib.mkIf (cfg.tls.certDir != null) {
|
||||
hostPath = cfg.tls.certDir;
|
||||
isReadOnly = true;
|
||||
};
|
||||
config =
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
tlsDir = "/var/lib/hive-gateway/tls";
|
||||
tlsCert = "${tlsDir}/cert.pem";
|
||||
tlsKey = "${tlsDir}/key.pem";
|
||||
# TLS cert + key paths inside the container.
|
||||
# - selfSignedTls=true: generated cert stored in persistent state dir.
|
||||
# - tls.certDir set: operator-provided cert bind-mounted at /run/hive-tls.
|
||||
tlsCert =
|
||||
if cfg.tls.certDir != null then
|
||||
"/run/hive-tls/${cfg.tls.certName}"
|
||||
else
|
||||
"${tlsDir}/cert.pem";
|
||||
tlsKey =
|
||||
if cfg.tls.certDir != null then
|
||||
"/run/hive-tls/${cfg.tls.keyName}"
|
||||
else
|
||||
"${tlsDir}/key.pem";
|
||||
# True when nginx should listen with TLS (either mode).
|
||||
hasTls = cfg.selfSignedTls || cfg.tls.certDir != null;
|
||||
# Listen addresses every vhost shares. Plain http on `cfg.port`
|
||||
# always; `cfg.httpsPort` with TLS sits beside it when
|
||||
# `cfg.selfSignedTls` is on. See `docs/gateway.md`
|
||||
# ("Self-signed TLS") for the cert lifecycle.
|
||||
# always; `cfg.httpsPort` with TLS sits beside it when TLS is
|
||||
# active (either self-signed or operator-provided cert).
|
||||
# See `docs/gateway.md` ("Self-signed TLS") for cert lifecycle.
|
||||
vhostListen = [
|
||||
{
|
||||
addr = "0.0.0.0";
|
||||
port = cfg.port;
|
||||
}
|
||||
]
|
||||
++ lib.optional cfg.selfSignedTls {
|
||||
++ lib.optional hasTls {
|
||||
addr = "0.0.0.0";
|
||||
port = cfg.httpsPort;
|
||||
ssl = true;
|
||||
|
|
@ -322,7 +405,7 @@ in
|
|||
# empty, so the explicit listen wins and there's no
|
||||
# duplicate-listen risk. Empty otherwise so the http-only
|
||||
# path stays identical.
|
||||
vhostTls = lib.optionalAttrs cfg.selfSignedTls {
|
||||
vhostTls = lib.optionalAttrs hasTls {
|
||||
addSSL = true;
|
||||
sslCertificate = tlsCert;
|
||||
sslCertificateKey = tlsKey;
|
||||
|
|
@ -331,14 +414,13 @@ in
|
|||
# Public-facing scheme + port-suffix for URLs the gateway
|
||||
# mints into responses (well-known JSON, the deprecated
|
||||
# `<hive>/matrix/*` 301 redirect, future absolute-URL needs).
|
||||
# When self-signed TLS is on, prefer `https://<host>` (matrix-
|
||||
# spec compliance) — 443 elides the port. Otherwise fall back
|
||||
# to the plain-http listen with the bare port. See
|
||||
# `docs/gateway.md` ("Self-signed TLS"). Shared at this scope
|
||||
# Shared to avoid repetition.
|
||||
publicScheme = if cfg.selfSignedTls then "https" else "http";
|
||||
publicPort = if cfg.selfSignedTls then cfg.httpsPort else cfg.port;
|
||||
publicPortDefault = if cfg.selfSignedTls then 443 else 80;
|
||||
# When TLS is active (self-signed OR operator cert), prefer
|
||||
# `https://<host>` (matrix-spec compliance) — 443 elides the
|
||||
# port. Otherwise fall back to the plain-http listen with the
|
||||
# bare port. See `docs/gateway.md` ("Self-signed TLS").
|
||||
publicScheme = if hasTls then "https" else "http";
|
||||
publicPort = if hasTls then cfg.httpsPort else cfg.port;
|
||||
publicPortDefault = if hasTls then 443 else 80;
|
||||
publicPortSuffix = if publicPort == publicPortDefault then "" else ":${toString publicPort}";
|
||||
in
|
||||
{
|
||||
|
|
@ -764,7 +846,8 @@ in
|
|||
};
|
||||
|
||||
networking.firewall = lib.mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.port ] ++ lib.optional cfg.selfSignedTls cfg.httpsPort;
|
||||
allowedTCPPorts =
|
||||
[ cfg.port ] ++ lib.optional (cfg.selfSignedTls || cfg.tls.certDir != null) cfg.httpsPort;
|
||||
};
|
||||
|
||||
# `/etc/hosts` entries for local dev — bare hive domain + any
|
||||
|
|
|
|||
Loading…
Reference in a new issue