nix-home/modules/cloud/traefik/config.nix

174 lines
5.4 KiB
Nix
Raw Normal View History

2021-11-01 18:41:55 +00:00
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.cloud.traefik;
2023-05-26 21:50:47 +00:00
tlsNoCloudflare = {
options = "no-cloudflare";
};
2021-11-01 19:26:11 +00:00
# Copied from traefik.nix
jsonValue = with types;
let
2021-11-01 19:50:30 +00:00
valueType = nullOr
(oneOf [
bool
int
float
str
(lazyAttrsOf valueType)
(listOf valueType)
]) // {
2021-11-01 19:26:11 +00:00
description = "JSON value";
emptyValue.value = { };
};
2021-11-01 19:50:30 +00:00
in
valueType;
2021-11-01 19:26:11 +00:00
2021-11-01 18:41:55 +00:00
hostType = with types; submodule {
options = {
host = mkOption {
type = str;
description = "The host for the router filter";
};
path = mkOption {
type = nullOr str;
default = null;
description = "The path for the router filter (exact path is matched)";
};
filter = mkOption {
type = nullOr str;
default = null;
description = "The filter syntax for the router. Overrides `host` and `path` if provided";
};
2022-06-11 19:53:34 +00:00
localHost = mkOption {
type = types.nullOr types.str;
description = "The local host of the service. Must be an IP if protocol is TCP. Default to localhost/127.0.0.1";
default = null;
};
2021-11-01 18:41:55 +00:00
port = mkOption {
type = types.port;
description = "The port that the service is listening on";
};
entrypoints = mkOption {
2023-04-27 00:32:10 +00:00
type = listOf (enum [ "http" "https" "smtp-submission" "smtp-submission-ssl" "imap" "wireguard" ]);
2021-11-01 18:41:55 +00:00
default = [ "https" ];
description = "The entrypoints that will serve the host";
};
2021-11-01 19:26:11 +00:00
middlewares = mkOption {
2021-11-01 19:44:19 +00:00
type = listOf jsonValue;
2021-11-01 19:50:30 +00:00
default = [ ];
2021-11-01 19:26:11 +00:00
description = "The middlewares to be used with the host.";
};
2021-11-01 19:50:30 +00:00
protocol = mkOption {
2023-04-27 00:32:10 +00:00
type = enum [ "http" "tcp" "udp" ];
2021-11-01 19:50:30 +00:00
default = "http";
description = "The protocol of the router and service";
};
tlsPassthrough = mkOption {
type = types.bool;
default = true;
description = "Sets the TCP passthrough value. Defaults to `true` if the connection is tcp";
};
2023-05-26 21:50:47 +00:00
noCloudflare = mkOption {
type = types.bool;
default = false;
description = "Bypasses the client cert requirement, enable if you don't route things through cloudflare";
};
2021-11-01 18:41:55 +00:00
};
};
# Returns the filter given a host configuration
2021-11-01 19:50:30 +00:00
filterOfHost = host:
let
hostFilter = if host.protocol == "http" then "Host" else "HostSNI";
in
2021-11-01 18:41:55 +00:00
if host.filter != null then host.filter
2021-11-01 19:50:30 +00:00
else if host.path == null then "${hostFilter}(`${host.host}`)"
else "${hostFilter}(`${host.host}`) && Path(`${host.path}`)";
2021-11-01 18:41:55 +00:00
# Turns a host configuration into dynamic traefik configuration
2021-11-01 19:50:30 +00:00
hostToConfig = name: host: {
"${host.protocol}" = {
2023-04-27 00:32:10 +00:00
routers."${name}-router" = (if (host.protocol != "udp") then {
2021-11-01 19:50:30 +00:00
rule = filterOfHost host;
2023-05-26 21:50:47 +00:00
tls = { certResolver = "le"; }
// (if host.protocol == "tcp" then { passthrough = if (host ? tlsPassthrough) then host.tlsPassthrough else true; } else { })
// (if host.noCloudflare then tlsNoCloudflare else { });
2023-04-27 00:32:10 +00:00
} else { }) // {
entryPoints = host.entrypoints;
2021-11-01 19:50:30 +00:00
service = "${name}-service";
} // (
if host.protocol == "http" then
{ middlewares = lists.imap0 (id: m: "${name}-middleware-${toString id}") host.middlewares; }
else if host.middlewares == [ ] then
{ }
2023-04-27 00:32:10 +00:00
else abort "Cannot have middlewares on non-http routers"
2021-11-01 19:50:30 +00:00
);
services."${name}-service".loadBalancer.servers = [
2022-06-11 19:53:34 +00:00
(
let
localhost =
if isNull host.localHost then
(
if host.protocol == "http" then "localhost"
else "127.0.0.1"
) else host.localHost;
in
if host.protocol == "http" then
{ url = "http://${localhost}:${toString host.port}"; }
else { address = "${localhost}:${toString host.port}"; }
2021-11-01 19:50:30 +00:00
)
];
} // (if (host.middlewares != [ ]) then {
middlewares = builtins.listToAttrs (lists.imap0
(id: v: {
name = "${name}-middleware-${toString id}";
value = v;
})
host.middlewares);
} else { });
2021-11-01 18:41:55 +00:00
};
2023-05-26 21:50:47 +00:00
tlsConfig = {
tls.options.default = {
sniStrict = true;
clientAuth = {
caFiles = [
(builtins.fetchurl {
url = "https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem";
sha256 = "sha256:0hxqszqfzsbmgksfm6k0gp0hsx9k1gqx24gakxqv0391wl6fsky1";
})
];
clientAuthType = "RequireAndVerifyClientCert";
};
};
tls.options.no-cloudflare = {
sniStrict = true;
clientAuth = {
caFiles = [
(builtins.fetchurl {
url = "https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem";
sha256 = "sha256:0hxqszqfzsbmgksfm6k0gp0hsx9k1gqx24gakxqv0391wl6fsky1";
})
];
clientAuthType = "VerifyClientCertIfGiven";
};
};
};
2021-11-01 18:41:55 +00:00
in
{
options.cloud.traefik.hosts = mkOption {
type = types.attrsOf hostType;
2021-11-01 19:50:30 +00:00
default = { };
2021-11-01 18:41:55 +00:00
description = "The HTTP hosts to run on the server";
};
2023-05-26 21:50:47 +00:00
config.cloud.traefik.config = builtins.foldl' attrsets.recursiveUpdate { } [
(builtins.foldl' attrsets.recursiveUpdate { } (attrsets.mapAttrsToList hostToConfig cfg.hosts))
tlsConfig
];
2021-11-01 18:41:55 +00:00
}