Compare commits

..

7 commits

12 changed files with 541 additions and 161 deletions

50
flake.lock generated
View file

@ -30,11 +30,11 @@
]
},
"locked": {
"lastModified": 1772058043,
"narHash": "sha256-m1cmQgb6tBcHkndKZ8BSsw6PRNJMG89FZwoYVOuKi34=",
"lastModified": 1774779250,
"narHash": "sha256-n7zH1dk+mcUt59i5FDSF3q6G398NzKVt/E/lM2DZY8A=",
"owner": "AdnanHodzic",
"repo": "auto-cpufreq",
"rev": "5d600d710bb2aa331e1a4370e08476bcdea1cab5",
"rev": "a620b9971963730978a0e5fc9d92bf613218d21e",
"type": "github"
},
"original": {
@ -50,11 +50,11 @@
]
},
"locked": {
"lastModified": 1773889306,
"narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=",
"lastModified": 1776613567,
"narHash": "sha256-gC9Cp5ibBmGD5awCA9z7xy6MW6iJufhazTYJOiGlCUI=",
"owner": "nix-community",
"repo": "disko",
"rev": "5ad85c82cc52264f4beddc934ba57f3789f28347",
"rev": "32f4236bfc141ae930b5ba2fb604f561fed5219d",
"type": "github"
},
"original": {
@ -144,11 +144,11 @@
]
},
"locked": {
"lastModified": 1774559029,
"narHash": "sha256-deix7yg3j6AhjMPnFDCmWB3f83LsajaaULP5HH2j34k=",
"lastModified": 1775425411,
"narHash": "sha256-KY6HsebJHEe5nHOWP7ur09mb0drGxYSzE3rQxy62rJo=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "a0bb0d11514f92b639514220114ac8063c72d0a3",
"rev": "0d02ec1d0a05f88ef9e74b516842900c41f0f2fe",
"type": "github"
},
"original": {
@ -181,11 +181,11 @@
},
"nixos-hardware": {
"locked": {
"lastModified": 1774465523,
"narHash": "sha256-4v7HPm63Q90nNn4fgkgKsjW1AH2Klw7XzPtHJr562nM=",
"lastModified": 1776983936,
"narHash": "sha256-ZOQyNqSvJ8UdrrqU1p7vaFcdL53idK+LOM8oRWEWh6o=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "de895be946ad1d8aafa0bb6dfc7e7e0e9e466a29",
"rev": "2096f3f411ce46e88a79ae4eafcfc9df8ed41c61",
"type": "github"
},
"original": {
@ -197,11 +197,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1774388614,
"narHash": "sha256-tFwzTI0DdDzovdE9+Ras6CUss0yn8P9XV4Ja6RjA+nU=",
"lastModified": 1776734388,
"narHash": "sha256-vl3dkhlE5gzsItuHoEMVe+DlonsK+0836LIRDnm6MXQ=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "1073dad219cb244572b74da2b20c7fe39cb3fa9e",
"rev": "10e7ad5bbcb421fe07e3a4ad53a634b0cd57ffac",
"type": "github"
},
"original": {
@ -261,11 +261,11 @@
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1774386573,
"narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=",
"lastModified": 1776877367,
"narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9",
"rev": "0726a0ecb6d4e08f6adced58726b95db924cef57",
"type": "github"
},
"original": {
@ -307,11 +307,11 @@
"utils": "utils"
},
"locked": {
"lastModified": 1769870714,
"narHash": "sha256-wjwCj70iiFXoAasQto+3jTaA4wCMOAs/rdX+nsmtBrQ=",
"lastModified": 1775228763,
"narHash": "sha256-8fKOEOouCaPZLBTdWPS+uU0bxsPp1OmfloDNoNSiu8w=",
"owner": "SaumonNet",
"repo": "proxmox-nixos",
"rev": "c1f79f104930347a0b84abbca0d42884063a8c09",
"rev": "e803cb839e5e5207fa37d92bc6ac7290f4dba633",
"type": "github"
},
"original": {
@ -338,11 +338,11 @@
"secrets": {
"flake": false,
"locked": {
"lastModified": 1774571252,
"narHash": "sha256-NU/vfItTMSjaRTXe0UDzbWR8UnhkBUFU47OpqEpxKb4=",
"lastModified": 1777076192,
"narHash": "sha256-N7n2OPN2IRWwL73Cr6mc5nNhucHmMeFas9hQ/NF0bFg=",
"ref": "refs/heads/main",
"rev": "7965907ae885d77acb3c4ecc11cee096a12af868",
"revCount": 25,
"rev": "34ff1c4b0460a2e103a8fec183f53f274dc123ed",
"revCount": 32,
"type": "git",
"url": "ssh://git@git.jfreudenberger.de/JuliusFreudenberger/nix-private.git"
},

View file

@ -167,6 +167,24 @@
];
};
srv03 = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
specialArgs = {
inherit inputs outputs;
pkgs-unstable = import nixpkgs-unstable {
inherit system;
config.allowUnfree = true;
};
};
modules = [
disko.nixosModules.disko
agenix.nixosModules.default
./hosts/srv03
];
};
};
homeConfigurations = {

91
hosts/srv03/default.nix Normal file
View file

@ -0,0 +1,91 @@
{ inputs, outputs, config, lib, pkgs, ... }:
{
imports =
[
./secrets.nix
../../modules/disko/legacy-full-ext4-swap.nix
../../users/julius/nixos-server.nix
../../modules/nix.nix
../../modules/locale.nix
../../modules/server-cli.nix
../../modules/sshd.nix
../../modules/qemu-guest.nix
../../modules/docker.nix
../../modules/traefik.nix
../../modules/pocket-id.nix
../../modules/auto-upgrade.nix
"${inputs.secrets}/modules/opkssh.nix"
# Include the results of the hardware scan.
./hardware-configuration.nix
];
#services.openssh.openFirewall = false;
services = {
traefik-docker = {
enable = true;
dashboardUrl = "traefik.netbird.jfreudenberger.de";
dnsChallengeProvider = "inwx";
dnsSecrets = [
config.age.secrets.inwx
];
};
pocket-id-docker.enable = true;
pocket-id = {
settings = {
APP_URL = "https://login.jfreudenberger.de";
TRUST_PROXY = true;
};
environmentFile = config.age.secrets.pocket-id.path;
};
};
systemd.network = {
enable = true;
networks."10-wan" = {
matchConfig.Name = "enp1s0";
networkConfig.DHCP = "no";
address = [
"46.224.47.24/32"
"2a01:4f8:c013:bf68::1/64"
];
routes = [
{ Gateway = "172.31.1.1"; GatewayOnLink = true; }
{ Gateway = "fe80::1"; GatewayOnLink = true; }
];
dns = [ "9.9.9.9" ];
};
};
boot = {
tmp.cleanOnBoot = true;
growPartition = true;
kernelParams = [ "console=ttyS0" ];
loader = {
grub.enable = true;
};
};
# Disable classic networking configuration
networking.useDHCP = lib.mkForce false;
networking.hostName = "srv03"; # Define your hostname.
# This option defines the first version of NixOS you have installed on this particular machine,
# and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
# Most users should NEVER change this value after the initial install, for any reason,
# even if you've upgraded your system to a new NixOS release.
# This value does NOT affect the Nixpkgs version your packages and OS are pulled from,
# so changing it will NOT upgrade your system - see https://nixos.org/manual/nixos/stable/#sec-upgrading for how
# to actually do that.
# This value being lower than the current NixOS release does NOT mean your system is
# out of date, out of support, or vulnerable.
# Do NOT change this value unless you have manually inspected all the changes it would make to your configuration,
# and migrated your data accordingly.
# For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
system.stateVersion = "25.05"; # Did you read the comment?
}

View file

@ -0,0 +1,24 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/profiles/qemu-guest.nix")
];
boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "virtio_scsi" "sd_mod" "sr_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
networking.useDHCP = lib.mkDefault true;
# networking.interfaces.enp1s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}

7
hosts/srv03/secrets.nix Normal file
View file

@ -0,0 +1,7 @@
{ inputs, ... }:
{
age.secrets = {
inwx.file = "${inputs.secrets}/secrets/dns-management/inwx";
pocket-id.file = "${inputs.secrets}/secrets/srv03/pocket-id";
};
}

View file

@ -7,6 +7,7 @@
remmina
teleport_17.client
opkssh
];
}

View file

@ -0,0 +1,45 @@
{
disko.devices = {
disk = {
sda = {
type = "disk";
device = "/dev/sda";
content = {
type = "gpt";
partitions = {
MBR = {
type = "EF02"; # for grub MBR
size = "1M";
priority = 1; # Needs to be first partition
};
ESP = {
size = "1G";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
};
root = {
end = "-1G";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
encryptedSwap = {
size = "100%";
content = {
type = "swap";
randomEncryption = true;
priority = 100;
};
};
};
};
};
};
};
}

View file

@ -38,7 +38,7 @@ in {
};
extraOptions = [
''--mount=type=volume,source=dockhand-data,target=/app/data,volume-driver=local''
''--group-add=131'' # docker group
''--group-add=${config.ids.gids.docker}''
];
};
};

View file

@ -4,7 +4,7 @@
...
}: {
boot.kernelPackages = pkgs.linuxPackages_latest;
boot.kernelPackages = pkgs.linuxKernel.packages.linux_zen;
services.logind.settings.Login = {
HandleLidSwitch= "suspend-then-hibernate";

58
modules/pocket-id.nix Normal file
View file

@ -0,0 +1,58 @@
{
config,
lib,
...
}:
let
cfg = config.services.pocket-id-docker;
pocketidCfg = config.services.pocket-id;
version = "2.6.2";
in {
options.services.pocket-id-docker = {
enable = lib.mkEnableOption "Pocket ID server hosted as OCI container";
};
config = lib.mkIf cfg.enable {
virtualisation.oci-containers.containers = {
pocket-id = {
image = "ghcr.io/pocket-id/pocket-id:v${version}";
autoStart = true;
networks = [
"webproxy"
];
environment = {
APP_URL = pocketidCfg.settings.APP_URL;
TRUST_PROXY = lib.boolToString pocketidCfg.settings.TRUST_PROXY;
ANALYTICS_DISABLED = lib.boolToString pocketidCfg.settings.ANALYTICS_DISABLED;
};
environmentFiles = [ pocketidCfg.environmentFile ];
extraOptions = [
''--mount=type=volume,source=data,target=/app/data,volume-driver=local''
"--health-cmd=/app/pocket-id healthcheck"
"--health-interval=1m30s"
"--health-timeout=5s"
"--health-retries=2"
"--health-start-period=10s"
];
labels = {
"traefik.enable" = "true";
"traefik.http.routers.pocket-id.rule" = "Host(`${lib.removePrefix "https://" pocketidCfg.settings.APP_URL}`)";
"traefik.http.routers.pocket-id.entrypoints" = "websecure";
};
};
};
systemd.services."docker-pocket-id" = {
after = [
"docker-traefik.service"
];
requires = [
"docker-traefik.service"
];
};
};
}

221
modules/traefik-oidc.nix Normal file
View file

@ -0,0 +1,221 @@
{
pkgs,
config,
lib,
...
}:
let
cfg = config.services.traefik-docker;
mapOidcClientNameToEnv = stringToReplace: lib.replaceString "-" "_" (lib.toUpper stringToReplace);
traefik-mtls-config = (pkgs.formats.yaml { }).generate "traefik-mtls-config" {
tls.options.default.clientAuth = {
caFiles = "caFiles/root_ca.crt";
clientAuthType = "VerifyClientCertIfGiven";
};
};
in {
options.services.traefik-docker = {
enable = lib.mkEnableOption "traefik web server hosted as OCI container";
dashboardUrl = lib.mkOption {
description = "External URL the traefik dashboard will be reachable from, without protocol";
type = lib.types.str;
};
dnsSecrets = lib.mkOption {
description = "Secrets for DNS providers.";
type = lib.types.listOf lib.types.anything;
};
mTLSCaCertSecret = lib.mkOption {
description = "Agenix secret containing the CA file to verify client certificates against.";
};
oidcAuthProviderUrl = lib.mkOption {
description = "Provider URL of OIDC auth provider.";
type = lib.types.str;
};
oidcClients = lib.mkOption {
example = ''
immich = {
scopes = [
"openid"
"email"
"profile"
];
enableBypassUsingClientCertificate = true;
usePkce = true;
};
'';
description = "Attribute set of OIDC clients with their configurations.";
type = lib.types.attrsOf (
lib.types.submodule {
options = {
secret = lib.mkOption {
description = ''Agenix secret containing the following needed environment variables in dotenv notation:
- <clientName>_OIDC_AUTH_SECRET
- <clientName>_OIDC_AUTH_PROVIDER_CLIENT_ID
- <clientName>_OIDC_CLIENT_SECRET
'';
};
scopes = lib.mkOption {
default = [ "openid" ];
example = [ "openid" "email" "profile" "groups" ];
description = "OIDC scopes to request from auth provider.";
type = lib.types.listOf lib.types.str;
};
usePkce = lib.mkOption {
default = true;
description = "Whether to enable PKCE for this provider.";
type = lib.types.bool;
};
enableBypassUsingClientCertificate = lib.mkOption {
default = false;
description = "Whether to allow bypassing OIDC protection when a verified client certificate is presented.";
type = lib.types.bool;
};
useClaimsFromUserInfo = lib.mkOption {
default = false;
description = "When enabled, an additional request to the provider's userinfo_endpoint is made to validate the token and to retrieve additional claims. The userinfo claims are merged directly into the token claims, with userinfo values overriding token values for non-security-critical claims.";
type = lib.types.bool;
};
headers = lib.mkOption {
default = [];
description = "Headers to be added to the upstream request. Templating is possible. Documentation can be found here: https://traefik-oidc-auth.sevensolutions.cc/docs/getting-started/middleware-configuration";
type = lib.types.listOf (lib.types.submodule {
options = {
Name = lib.mkOption {
description = "The name of the header which should be added to the upstream request.";
type = lib.types.str;
};
Value = lib.mkOption {
description = "The value of the header, which can use Go-Templates.";
type = lib.types.str;
};
};
});
};
};
}
);
};
};
config = lib.mkIf cfg.enable {
virtualisation.oci-containers.containers = {
traefik = {
image = "traefik:v3.6.6";
cmd = [
"--providers.docker=true"
"--providers.docker.exposedByDefault=false"
"--providers.docker.network=traefik"
"--providers.file.directory=/dynamic-config"
"--log.level=INFO"
"--api=true"
"--ping=true"
"--entrypoints.web.address=:80"
"--entrypoints.websecure.address=:443"
"--entrypoints.websecure.transport.respondingTimeouts.readTimeout=600s"
"--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=600s"
"--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=600s"
"--entrypoints.web.http.redirections.entrypoint.to=websecure"
"--entrypoints.websecure.asDefault=true"
"--entrypoints.websecure.http.middlewares=strip-mtls-headers@docker,pass-tls-client-cert@docker"
"--entrypoints.websecure.http.tls.certresolver=letsencrypt"
"--certificatesresolvers.letsencrypt.acme.storage=/certs/acme.json"
"--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
"--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=netcup"
"--experimental.plugins.traefik-oidc-auth.modulename=github.com/sevensolutions/traefik-oidc-auth"
"--experimental.plugins.traefik-oidc-auth.version=v0.17.0"
];
autoStart = true;
ports = [
"80:80"
"443:443"
];
networks = [
"traefik"
];
environment = {
OIDC_AUTH_PROVIDER_URL = cfg.oidcAuthProviderUrl;
};
environmentFiles = lib.forEach cfg.dnsSecrets (secret: secret.path) ++ (lib.mapAttrsToList (oidcClientName: oidcClientConfig: oidcClientConfig.secret.path) cfg.oidcClients);
labels = {
"traefik.enable" = "true";
"traefik.http.routers.dashboard.rule" = "Host(`${cfg.dashboardUrl}`)";
"traefik.http.routers.dashboard.service" = "dashboard@internal";
"traefik.http.routers.dashboard.middlewares" = "traefik-dashboard-oidc-auth@file";
"traefik.http.routers.api.rule" = "Host(`${cfg.dashboardUrl}`) && (PathPrefix(`/api`) || PathPrefix(`/oidc/callback`))";
"traefik.http.routers.api.service" = "api@internal";
"traefik.http.routers.api.middlewares" = "traefik-dashboard-oidc-auth@file";
"traefik.http.middlewares.strip-mtls-headers.headers.customrequestheaders.X-Forwarded-Tls-Client-Cert" = "";
"traefik.http.middlewares.pass-tls-client-cert.passtlsclientcert.pem" = "true";
};
volumes = let
oidc-config = lib.mapAttrs' (
oidcClientName: oidcClientConfig:
lib.nameValuePair "${oidcClientName}-oidc-auth" {
plugin.traefik-oidc-auth = {
LogLevel = "INFO";
Secret = ''{{ env "${mapOidcClientNameToEnv oidcClientName}_OIDC_AUTH_SECRET" }}'';
Provider = {
Url = ''{{ env "OIDC_AUTH_PROVIDER_URL" }}'';
ClientId = ''{{ env "${mapOidcClientNameToEnv oidcClientName}_OIDC_AUTH_PROVIDER_CLIENT_ID" }}'';
ClientSecret = ''{{ env "${mapOidcClientNameToEnv oidcClientName}_OIDC_AUTH_PROVIDER_CLIENT_SECRET" }}'';
UsePkce = oidcClientConfig.usePkce;
UseClaimsFromUserInfo = oidcClientConfig.useClaimsFromUserInfo;
};
Scopes = oidcClientConfig.scopes;
LoginUrl = ''{{ env "OIDC_AUTH_PROVIDER_URL" }}'';
} // (lib.attrsets.optionalAttrs oidcClientConfig.enableBypassUsingClientCertificate {
BypassAuthenticationRule = "HeaderRegexp(`X-Forwarded-Tls-Client-Cert`, `.+`)";
}) // (lib.attrsets.optionalAttrs ((lib.length oidcClientConfig.headers) > 0) {
Headers = oidcClientConfig.headers;
});
}
) cfg.oidcClients;
traefik-oidc-authentication-config = (pkgs.formats.yaml {}).generate "traefik-oidc-auth" {
http.middlewares = oidc-config;
};
in [
"/var/run/docker.sock:/var/run/docker.sock"
"${traefik-oidc-authentication-config}:/dynamic-config/traefik-oidc-auth.yaml:ro"
"${traefik-mtls-config}:/dynamic-config/traefik-mtls.yaml:ro"
"${cfg.mTLSCaCertSecret.path}:/caFiles/root_ca.crt:ro"
];
extraOptions = [
''--mount=type=volume,source=certs,target=/certs,volume-driver=local''
"--add-host=host.docker.internal:host-gateway"
"--health-cmd=wget --spider --quiet http://localhost:8080/ping"
"--health-interval=10s"
"--health-timeout=5s"
"--health-retries=3"
"--health-start-period=5s"
];
};
};
systemd.services."docker-traefik" = {
after = [
"docker-network-traefik.service"
];
requires = [
"docker-network-traefik.service"
];
};
systemd.services."docker-network-traefik" = {
path = [ pkgs.docker ];
serviceConfig = {
Type = "oneshot";
};
script = ''
docker network inspect traefik || docker network create traefik --ipv4 --ipv6 --subnet=172.18.0.0/16 --gateway=172.18.0.1
'';
};
networking.firewall.extraCommands = "iptables -t nat -I PREROUTING -s 172.18.0.0/16 -d 172.18.0.0/16 -j MASQUERADE";
};
}

View file

@ -7,15 +7,7 @@
let
cfg = config.services.traefik-docker;
mapOidcClientNameToEnv = stringToReplace: lib.replaceString "-" "_" (lib.toUpper stringToReplace);
traefik-mtls-config = (pkgs.formats.yaml { }).generate "traefik-mtls-config" {
tls.options.default.clientAuth = {
caFiles = "caFiles/root_ca.crt";
clientAuthType = "VerifyClientCertIfGiven";
};
};
version = "3.6.14";
in {
@ -29,105 +21,40 @@ in {
description = "Secrets for DNS providers.";
type = lib.types.listOf lib.types.anything;
};
mTLSCaCertSecret = lib.mkOption {
description = "Agenix secret containing the CA file to verify client certificates against.";
};
oidcAuthProviderUrl = lib.mkOption {
description = "Provider URL of OIDC auth provider.";
dnsChallengeProvider = lib.mkOption {
description = "Name of provider for DNS challenge.";
type = lib.types.str;
};
oidcClients = lib.mkOption {
example = ''
immich = {
scopes = [
"openid"
"email"
"profile"
];
enableBypassUsingClientCertificate = true;
usePkce = true;
};
'';
description = "Attribute set of OIDC clients with their configurations.";
type = lib.types.attrsOf (
lib.types.submodule {
options = {
secret = lib.mkOption {
description = ''Agenix secret containing the following needed environment variables in dotenv notation:
- <clientName>_OIDC_AUTH_SECRET
- <clientName>_OIDC_AUTH_PROVIDER_CLIENT_ID
- <clientName>_OIDC_CLIENT_SECRET
'';
};
scopes = lib.mkOption {
default = [ "openid" ];
example = [ "openid" "email" "profile" "groups" ];
description = "OIDC scopes to request from auth provider.";
type = lib.types.listOf lib.types.str;
};
usePkce = lib.mkOption {
default = true;
description = "Whether to enable PKCE for this provider.";
type = lib.types.bool;
};
enableBypassUsingClientCertificate = lib.mkOption {
default = false;
description = "Whether to allow bypassing OIDC protection when a verified client certificate is presented.";
type = lib.types.bool;
};
useClaimsFromUserInfo = lib.mkOption {
default = false;
description = "When enabled, an additional request to the provider's userinfo_endpoint is made to validate the token and to retrieve additional claims. The userinfo claims are merged directly into the token claims, with userinfo values overriding token values for non-security-critical claims.";
type = lib.types.bool;
};
headers = lib.mkOption {
default = [];
description = "Headers to be added to the upstream request. Templating is possible. Documentation can be found here: https://traefik-oidc-auth.sevensolutions.cc/docs/getting-started/middleware-configuration";
type = lib.types.listOf (lib.types.submodule {
options = {
Name = lib.mkOption {
description = "The name of the header which should be added to the upstream request.";
type = lib.types.str;
};
Value = lib.mkOption {
description = "The value of the header, which can use Go-Templates.";
type = lib.types.str;
};
};
});
};
};
}
);
};
};
config = lib.mkIf cfg.enable {
virtualisation.oci-containers.containers = {
traefik = {
image = "traefik:v3.6.6";
image = "traefik:v${version}";
cmd = [
"--providers.docker=true"
"--providers.docker.endpoint=http://docker-socket-proxy:2375"
"--providers.docker.exposedByDefault=false"
"--providers.docker.network=traefik"
"--providers.docker.network=webproxy"
"--providers.file.directory=/dynamic-config"
"--log.level=INFO"
"--api=true"
"--ping=true"
"--entrypoints.web.address=:80"
"--entrypoints.websecure.address=:443"
"--entrypoints.websecure.transport.respondingTimeouts.readTimeout=600s"
"--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=600s"
"--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=600s"
"--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0"
"--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0"
"--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0"
"--serverstransport.forwardingtimeouts.responseheadertimeout=0s"
"--serverstransport.forwardingtimeouts.idleconntimeout=0s"
"--entrypoints.web.http.redirections.entrypoint.to=websecure"
"--entrypoints.websecure.asDefault=true"
"--entrypoints.websecure.http.middlewares=strip-mtls-headers@docker,pass-tls-client-cert@docker"
"--entrypoints.websecure.http.tls.certresolver=letsencrypt"
"--certificatesresolvers.letsencrypt.acme.email=contact@jfreudenberger.de"
"--certificatesresolvers.letsencrypt.acme.storage=/certs/acme.json"
"--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
"--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=netcup"
"--experimental.plugins.traefik-oidc-auth.modulename=github.com/sevensolutions/traefik-oidc-auth"
"--experimental.plugins.traefik-oidc-auth.version=v0.17.0"
"--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=${cfg.dnsChallengeProvider}"
"--providers.file.filename=/dynamic-config/providers.yaml"
];
autoStart = true;
ports = [
@ -135,54 +62,17 @@ in {
"443:443"
];
networks = [
"traefik"
"webproxy"
"docker-socket"
];
environment = {
OIDC_AUTH_PROVIDER_URL = cfg.oidcAuthProviderUrl;
};
environmentFiles = lib.forEach cfg.dnsSecrets (secret: secret.path) ++ (lib.mapAttrsToList (oidcClientName: oidcClientConfig: oidcClientConfig.secret.path) cfg.oidcClients);
labels = {
"traefik.enable" = "true";
"traefik.http.routers.dashboard.rule" = "Host(`${cfg.dashboardUrl}`)";
"traefik.http.routers.dashboard.service" = "dashboard@internal";
"traefik.http.routers.dashboard.middlewares" = "traefik-dashboard-oidc-auth@file";
"traefik.http.routers.api.rule" = "Host(`${cfg.dashboardUrl}`) && (PathPrefix(`/api`) || PathPrefix(`/oidc/callback`))";
"traefik.http.routers.api.service" = "api@internal";
"traefik.http.routers.api.middlewares" = "traefik-dashboard-oidc-auth@file";
"traefik.http.middlewares.strip-mtls-headers.headers.customrequestheaders.X-Forwarded-Tls-Client-Cert" = "";
"traefik.http.middlewares.pass-tls-client-cert.passtlsclientcert.pem" = "true";
};
environmentFiles = lib.forEach cfg.dnsSecrets (secret: secret.path);
volumes = let
oidc-config = lib.mapAttrs' (
oidcClientName: oidcClientConfig:
lib.nameValuePair "${oidcClientName}-oidc-auth" {
plugin.traefik-oidc-auth = {
LogLevel = "INFO";
Secret = ''{{ env "${mapOidcClientNameToEnv oidcClientName}_OIDC_AUTH_SECRET" }}'';
Provider = {
Url = ''{{ env "OIDC_AUTH_PROVIDER_URL" }}'';
ClientId = ''{{ env "${mapOidcClientNameToEnv oidcClientName}_OIDC_AUTH_PROVIDER_CLIENT_ID" }}'';
ClientSecret = ''{{ env "${mapOidcClientNameToEnv oidcClientName}_OIDC_AUTH_PROVIDER_CLIENT_SECRET" }}'';
UsePkce = oidcClientConfig.usePkce;
UseClaimsFromUserInfo = oidcClientConfig.useClaimsFromUserInfo;
};
Scopes = oidcClientConfig.scopes;
LoginUrl = ''{{ env "OIDC_AUTH_PROVIDER_URL" }}'';
} // (lib.attrsets.optionalAttrs oidcClientConfig.enableBypassUsingClientCertificate {
BypassAuthenticationRule = "HeaderRegexp(`X-Forwarded-Tls-Client-Cert`, `.+`)";
}) // (lib.attrsets.optionalAttrs ((lib.length oidcClientConfig.headers) > 0) {
Headers = oidcClientConfig.headers;
});
}
) cfg.oidcClients;
traefik-oidc-authentication-config = (pkgs.formats.yaml {}).generate "traefik-oidc-auth" {
http.middlewares = oidc-config;
traefik-providers-config = (pkgs.formats.yaml {}).generate "traefik-providers-config" {
tcp.serversTransports.pp-v2.proxyProtocol.version = 2;
};
in [
"/var/run/docker.sock:/var/run/docker.sock"
"${traefik-oidc-authentication-config}:/dynamic-config/traefik-oidc-auth.yaml:ro"
"${traefik-mtls-config}:/dynamic-config/traefik-mtls.yaml:ro"
"${cfg.mTLSCaCertSecret.path}:/caFiles/root_ca.crt:ro"
"${traefik-providers-config}:/dynamic-config/providers.yaml:ro"
];
extraOptions = [
''--mount=type=volume,source=certs,target=/certs,volume-driver=local''
@ -194,24 +84,49 @@ in {
"--health-start-period=5s"
];
};
docker-socket-proxy = {
image = "tecnativa/docker-socket-proxy:v0.4.2";
autoStart = true;
networks = [
"docker-socket"
];
environment = {
CONTAINERS = "1";
};
volumes = [
"/var/run/docker.sock:/var/run/docker.sock:ro"
];
};
};
systemd.services."docker-traefik" = {
after = [
"docker-network-traefik.service"
"docker-network-webproxy.service"
"docker-network-docker-socket.service"
];
requires = [
"docker-network-traefik.service"
"docker-network-webproxy.service"
"docker-network-docker-socket.service"
];
};
systemd.services."docker-network-traefik" = {
systemd.services."docker-network-webproxy" = {
path = [ pkgs.docker ];
serviceConfig = {
Type = "oneshot";
};
script = ''
docker network inspect traefik || docker network create traefik --ipv4 --ipv6 --subnet=172.18.0.0/16 --gateway=172.18.0.1
docker network inspect webproxy || docker network create webproxy --ipv4 --ipv6 --subnet=172.18.0.0/16 --gateway=172.18.0.1
'';
};
systemd.services."docker-network-docker-socket" = {
path = [ pkgs.docker ];
serviceConfig = {
Type = "oneshot";
};
script = ''
docker network inspect docker-socket || docker network create docker-socket --ipv4 --ipv6 --subnet=172.19.0.0/16 --gateway=172.19.0.1
'';
};