299 lines
11 KiB
Nix
299 lines
11 KiB
Nix
# Option declarations for `services.hyperhive.gateway.*`. The gateway
|
|
# is always run alongside hyperhive (it's the single nginx in front of
|
|
# every surface and the only thing exposed to the outside); there is
|
|
# no enable flag. An operator who wants their own reverse proxy in
|
|
# front points it at the gateway's `port`.
|
|
{
|
|
lib,
|
|
config,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.services.hyperhive.gateway;
|
|
in
|
|
{
|
|
imports = [
|
|
(lib.mkRemovedOptionModule [ "services" "hyperhive" "gateway" "selfSignedTls" ] ''
|
|
Self-signed TLS is the implicit default whenever neither
|
|
tls.certDir nor tls.acme is configured, and there is no http-only
|
|
mode. Remove the setting; configure `tls.certDir` or `tls.acme`
|
|
to override the self-signed default.
|
|
'')
|
|
];
|
|
|
|
options.services.hyperhive.gateway = {
|
|
port = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 80;
|
|
example = 8080;
|
|
description = ''
|
|
TCP port the gateway listens on. Default 80 (canonical web
|
|
port). nginx inside the container binds <1024 because the
|
|
container's init runs as root; if 80 is already taken on the
|
|
host (existing nginx, traefik, etc.) override to an unused
|
|
port like 8080 or move the conflicting service.
|
|
'';
|
|
};
|
|
|
|
upstreamHost = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "127.0.0.1";
|
|
description = ''
|
|
Host the gateway proxies non-static requests to. Defaults to
|
|
`127.0.0.1` because the gateway container shares the host
|
|
netns, so loopback resolves directly to hive-c0re.
|
|
'';
|
|
};
|
|
|
|
upstreamPort = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 7000;
|
|
description = ''
|
|
TCP port the gateway proxies non-static requests to. Defaults
|
|
to `7000` (hive-c0re's out-of-the-box dashboard port). Operators
|
|
who change `services.hyperhive.c0re.dashboardPort` should set
|
|
`upstreamPort` to match — kept as a hardcoded default rather
|
|
than a cross-reference to keep this module's options eval
|
|
independent of c0re's option tree shape.
|
|
'';
|
|
};
|
|
|
|
openFirewall = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
example = true;
|
|
description = ''
|
|
Open `port` in the host firewall. Off by default (secure-by-default).
|
|
Flip to `true` to expose the gateway to
|
|
the operator's browser / external clients — required for any
|
|
out-of-host reach, since the agents themselves talk to
|
|
hive-c0re via the per-agent unix sockets and don't need the
|
|
nginx vhost. Leave off when running behind another reverse
|
|
proxy (e.g. caddy / traefik on the host) that handles TLS
|
|
termination + forwards to `port`.
|
|
|
|
**Note**: this used to default to `true`. Add
|
|
`services.hyperhive.gateway.openFirewall = true;` to your host
|
|
config if external reach stopped working after a recent upgrade.
|
|
'';
|
|
};
|
|
|
|
localHostsEntry = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
example = true;
|
|
description = ''
|
|
Add an `/etc/hosts` entry mapping `services.hyperhive.domain`
|
|
to `127.0.0.1` on the host. Useful for local deployments +
|
|
tests where there's no real DNS for `services.hyperhive.domain`
|
|
but the operator (or browser-based tests) want to hit
|
|
`http://''${services.hyperhive.domain}` to exercise the
|
|
gateway shape. Off by default — operators running with real
|
|
DNS shouldn't have a stale `/etc/hosts` entry sticking
|
|
around. Requires `services.hyperhive.domain` to be set.
|
|
'';
|
|
};
|
|
|
|
useSelfSigned = lib.mkOption {
|
|
type = lib.types.bool;
|
|
internal = true;
|
|
readOnly = true;
|
|
default = cfg.tls.certDir == null && !cfg.tls.acme.enable;
|
|
defaultText = lib.literalExpression "tls.certDir == null && !tls.acme.enable";
|
|
description = ''
|
|
Read-only derived flag: `true` when the gateway serves the
|
|
self-signed (hive-CA-signed) leaf — i.e. neither `tls.certDir` nor
|
|
`tls.acme.enable` is configured. Single source of truth for the
|
|
self-signed condition; consumed by the `hive-tls` and `hive-ci`
|
|
modules so the derivation isn't duplicated. Internal — not meant to
|
|
be set by operators (use `tls.certDir` / `tls.acme` to override the
|
|
self-signed default).
|
|
'';
|
|
};
|
|
|
|
httpsPort = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 443;
|
|
example = 8443;
|
|
description = ''
|
|
TCP port for the TLS-terminated vhosts. Default 443. The gateway
|
|
always terminates TLS (self-signed is the implicit floor when no
|
|
`tls.certDir` / ACME is configured), so this port is always active
|
|
alongside the plain-http `port`.
|
|
'';
|
|
};
|
|
|
|
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, overriding the self-signed default — the
|
|
auto-generated hive-CA-signed leaf 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;
|
|
```
|
|
|
|
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 with `tls.acme.enable` — set one or the other,
|
|
not both.
|
|
'';
|
|
};
|
|
|
|
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.
|
|
'';
|
|
};
|
|
|
|
acme = {
|
|
enable = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
example = true;
|
|
description = ''
|
|
Let nginx inside the gateway container obtain and renew TLS
|
|
certificates automatically via ACME (Let's Encrypt). When
|
|
enabled, each vhost calls out to Let's Encrypt using the
|
|
HTTP-01 challenge on `port` (default 80) and stores certs
|
|
inside the gateway container's persistent state dir.
|
|
|
|
Requirements:
|
|
- `services.hyperhive.domain` must be set and publicly
|
|
DNS-resolvable to this host.
|
|
- `services.hyperhive.gateway.openFirewall = true` so
|
|
Let's Encrypt can reach `/.well-known/acme-challenge/`.
|
|
- `tls.acme.email` must be set (ACME account contact).
|
|
|
|
Mutual exclusion: `tls.certDir` set together with
|
|
`tls.acme.enable = true` fails at eval — pick one TLS source.
|
|
|
|
Typical setup:
|
|
```nix
|
|
services.hyperhive.gateway = {
|
|
openFirewall = true;
|
|
tls.acme = {
|
|
enable = true;
|
|
email = "admin@example.com";
|
|
};
|
|
};
|
|
```
|
|
|
|
After enabling, peer hives can omit `certFingerprint` in
|
|
`swarm.peers` — Let's Encrypt certs are CA-trusted
|
|
by default.
|
|
'';
|
|
};
|
|
|
|
email = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
example = "admin@example.com";
|
|
description = ''
|
|
Email address for the ACME account registration with
|
|
Let's Encrypt. Required when `tls.acme.enable = true`.
|
|
Let's Encrypt sends expiry warnings to this address.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
auth = {
|
|
enable = lib.mkEnableOption ''
|
|
HTTP basic auth on the gateway using an htpasswd file. When
|
|
enabled, every request to the gateway's main vhost requires a
|
|
valid username and password. nginx's built-in `auth_basic`
|
|
module validates credentials against
|
|
`/var/lib/hyperhive/gateway/gateway.htpasswd` on the host
|
|
(exposed as `/run/hive-state/gateway.htpasswd` inside the
|
|
container via the existing gateway state bind-mount). Off by default.
|
|
|
|
Manage users with `hivectl gateway create-user`, `delete-user`,
|
|
and `list-users` — see `hivectl gateway --help` for usage.
|
|
The htpasswd file is created automatically when auth is enabled;
|
|
add at least one user before enabling to avoid locking everyone out.
|
|
'';
|
|
|
|
realm = lib.mkOption {
|
|
type = lib.types.strMatching "[^\"$]*";
|
|
default = "hyperhive";
|
|
example = "my-hive";
|
|
description = ''
|
|
HTTP Basic auth `realm` value sent in the `WWW-Authenticate`
|
|
header when credentials are absent or rejected. Must not
|
|
contain `"` or `$` (nginx string metacharacters).
|
|
'';
|
|
};
|
|
};
|
|
|
|
hsts = {
|
|
enable = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Add `Strict-Transport-Security` to all gateway vhosts.
|
|
|
|
Disabled by default: HSTS pins HTTPS in the browser's HSTS
|
|
preload list; enabling it on a deployment that later loses TLS
|
|
will lock browsers out until the max-age expires. Only enable
|
|
this when you are certain TLS is permanent.
|
|
|
|
The gateway always terminates TLS (self-signed floor), so
|
|
HSTS is always served over https when enabled — but mind the
|
|
warning above: HSTS pins https in the browser, so only enable it
|
|
when TLS is permanent for this deployment.
|
|
'';
|
|
};
|
|
|
|
maxAge = lib.mkOption {
|
|
type = lib.types.ints.positive;
|
|
default = 31536000;
|
|
example = 86400;
|
|
description = ''
|
|
Value for the `max-age` directive in seconds.
|
|
Default: 31536000 (1 year), which is the value required for
|
|
HSTS preload list submission. Use a shorter value (e.g. 86400)
|
|
while testing so browsers forget the pin quickly.
|
|
'';
|
|
};
|
|
|
|
includeSubDomains = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = ''
|
|
Whether to include `includeSubDomains` in the HSTS header.
|
|
Only disable this if the gateway host has sub-domains that
|
|
intentionally serve plain HTTP.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
}
|