Add cert-resolver

This commit is contained in:
Natsu Kagami 2021-11-01 15:50:30 -04:00
parent 0842bd53a2
commit 071889c976
Signed by: nki
GPG key ID: 7306B3D3C3AD6E51
7 changed files with 475 additions and 37 deletions

View file

@ -0,0 +1,46 @@
{ pkgs, config, lib, ... }:
with lib;
let
cfg = config.cloud.traefik.certsDumper;
in
{
options.cloud.traefik.certsDumper = {
enable = mkEnableOption "Dump certs onto a given directory ";
package = mkOption {
type = types.package;
default = pkgs.unstable.traefik-certs-dumper;
description = "The certs dumper package to use";
};
destination = mkOption {
type = types.str;
default = "/var/lib/traefik-certs";
description = "The destination folder to dump certs onto";
};
};
config.systemd.services.traefik-certs-dumper = mkIf cfg.enable {
after = [ "traefik.service" ];
path = with pkgs; [ openssl ];
wantedBy = [ "multi-user.target" ];
description = "Dump certificates generated by traefik to a destination folder";
serviceConfig =
let
user = config.systemd.services.traefik.serviceConfig.User;
group = config.systemd.services.traefik.serviceConfig.Group;
certsPath = config.cloud.traefik.certsPath;
in
{
User = user;
Group = group;
ExecStart = "${cfg.package}/bin/traefik-certs-dumper file --watch --domain-subdir=true --version v2 --source ${certsPath} --dest ${cfg.destination} --post-hook 'chmod -R +r ${cfg.destination}'";
LimitNOFILE = "1048576";
PrivateTmp = "true";
PrivateDevices = "true";
ProtectHome = "true";
ProtectSystem = "strict";
StateDirectory = "traefik-certs";
};
};
}

View file

@ -7,18 +7,20 @@ let
# Copied from traefik.nix
jsonValue = with types;
let
valueType = nullOr (oneOf [
bool
int
float
str
(lazyAttrsOf valueType)
(listOf valueType)
]) // {
valueType = nullOr
(oneOf [
bool
int
float
str
(lazyAttrsOf valueType)
(listOf valueType)
]) // {
description = "JSON value";
emptyValue.value = { };
};
in valueType;
in
valueType;
hostType = with types; submodule {
options = {
@ -41,49 +43,76 @@ let
description = "The port that the service is listening on";
};
entrypoints = mkOption {
type = listOf (enum ["http" "https" "smtp-submission" "imap"]);
type = listOf (enum [ "http" "https" "smtp-submission" "smtp-submission-ssl" "imap" ]);
default = [ "https" ];
description = "The entrypoints that will serve the host";
};
middlewares = mkOption {
type = listOf jsonValue;
default = [];
default = [ ];
description = "The middlewares to be used with the host.";
};
protocol = mkOption {
type = enum [ "http" "tcp" ];
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";
};
};
};
# Returns the filter given a host configuration
filterOfHost = host :
filterOfHost = host:
let
hostFilter = if host.protocol == "http" then "Host" else "HostSNI";
in
if host.filter != null then host.filter
else if host.path == null then "Host(`${host.host}`)"
else "Host(`${host.host}`) && Path(`${host.path}`)";
else if host.path == null then "${hostFilter}(`${host.host}`)"
else "${hostFilter}(`${host.host}`) && Path(`${host.path}`)";
# Turns a host configuration into dynamic traefik configuration
hostToConfig = name : host : {
http.routers."${name}-router" = {
rule = filterOfHost host;
entryPoints = host.entrypoints;
tls.certResolver = "le";
service = "${name}-service";
middlewares = lists.imap0 (id: m: "${name}-middleware-${toString id}") host.middlewares;
};
http.services."${name}-service".loadBalancer.servers = [
{ url = "http://localhost:${toString host.port}"; }
];
http.middlewares = builtins.listToAttrs (lists.imap0 (id: v: {
name = "${name}-middleware-${toString id}";
value = v;
}) host.middlewares);
hostToConfig = name: host: {
"${host.protocol}" = {
routers."${name}-router" = {
rule = filterOfHost host;
entryPoints = host.entrypoints;
tls = { certResolver = "le"; } // (if host.protocol == "tcp" then { passthrough = if (host ? tlsPassthrough) then host.tlsPassthrough else true; } else { });
service = "${name}-service";
} // (
if host.protocol == "http" then
{ middlewares = lists.imap0 (id: m: "${name}-middleware-${toString id}") host.middlewares; }
else if host.middlewares == [ ] then
{ }
else abort "Cannot have middlewares on tcp routers"
);
services."${name}-service".loadBalancer.servers = [
(if host.protocol == "http" then
{ url = "http://localhost:${toString host.port}"; }
else { address = "127.0.0.1:${toString host.port}"; }
)
];
} // (if (host.middlewares != [ ]) then {
middlewares = builtins.listToAttrs (lists.imap0
(id: v: {
name = "${name}-middleware-${toString id}";
value = v;
})
host.middlewares);
} else { });
};
in
{
options.cloud.traefik.hosts = mkOption {
type = types.attrsOf hostType;
default = {};
default = { };
description = "The HTTP hosts to run on the server";
};
config.cloud.traefik.config = builtins.foldl' attrsets.recursiveUpdate {} (attrsets.mapAttrsToList hostToConfig cfg.hosts);
config.cloud.traefik.config = builtins.foldl' attrsets.recursiveUpdate { } (attrsets.mapAttrsToList hostToConfig cfg.hosts);
}

View file

@ -35,6 +35,7 @@ in
entryPoints = [ "https" ];
middlewares = [ "dashboard-auth" ];
service = "api@internal";
tls.certResolver = "le";
};
};
}

View file

@ -21,7 +21,7 @@ let
cfg = config.cloud.traefik;
in
{
imports = [ ./config.nix ./dashboard.nix ];
imports = [ ./config.nix ./dashboard.nix ./certs-dumper.nix ];
options.cloud.traefik = {
cloudflareKeyFile = mkOption {
type = types.path;
@ -33,6 +33,12 @@ in
default = {};
description = "The dynamic configuration to be passed to traefik";
};
certsPath = mkOption {
type = types.str;
default = "/var/lib/traefik/acme.json";
description = "The location to read and write the certificates file onto";
};
};
config.services.traefik = {
@ -52,6 +58,7 @@ in
## IMAP and SMTP
entrypoints.imap.address = ":993";
entrypoints.smtp-submission.address = ":587";
entrypoints.smtp-submission-ssl.address = ":465";
# Logging
# -------
@ -62,7 +69,7 @@ in
# ------------------
certificatesResolvers.le.acme = {
email = "natsukagami@gmail.com";
storage = "/var/lib/traefik/acme.json";
storage = cfg.certsPath;
dnsChallenge.provider = "cloudflare";
dnsChallenge.delayBeforeCheck = 10;
};
@ -74,6 +81,6 @@ in
config.systemd.services.traefik.environment.CF_DNS_API_TOKEN_FILE = cfg.cloudflareKeyFile;
# Set up firewall to allow traefik traffic.
config.networking.firewall.allowedTCPPorts = [ 80 443 993 587 ];
config.networking.firewall.allowedTCPPorts = [ 80 443 993 587 465 ];
config.networking.firewall.allowedUDPPorts = [ 443 ]; # QUIC
}