diff --git a/flake.lock b/flake.lock index 78c0194..3348a05 100644 --- a/flake.lock +++ b/flake.lock @@ -30,11 +30,11 @@ ] }, "locked": { - "lastModified": 1774779250, - "narHash": "sha256-n7zH1dk+mcUt59i5FDSF3q6G398NzKVt/E/lM2DZY8A=", + "lastModified": 1772058043, + "narHash": "sha256-m1cmQgb6tBcHkndKZ8BSsw6PRNJMG89FZwoYVOuKi34=", "owner": "AdnanHodzic", "repo": "auto-cpufreq", - "rev": "a620b9971963730978a0e5fc9d92bf613218d21e", + "rev": "5d600d710bb2aa331e1a4370e08476bcdea1cab5", "type": "github" }, "original": { @@ -50,11 +50,11 @@ ] }, "locked": { - "lastModified": 1776613567, - "narHash": "sha256-gC9Cp5ibBmGD5awCA9z7xy6MW6iJufhazTYJOiGlCUI=", + "lastModified": 1773889306, + "narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=", "owner": "nix-community", "repo": "disko", - "rev": "32f4236bfc141ae930b5ba2fb604f561fed5219d", + "rev": "5ad85c82cc52264f4beddc934ba57f3789f28347", "type": "github" }, "original": { @@ -144,11 +144,11 @@ ] }, "locked": { - "lastModified": 1775425411, - "narHash": "sha256-KY6HsebJHEe5nHOWP7ur09mb0drGxYSzE3rQxy62rJo=", + "lastModified": 1774559029, + "narHash": "sha256-deix7yg3j6AhjMPnFDCmWB3f83LsajaaULP5HH2j34k=", "owner": "nix-community", "repo": "home-manager", - "rev": "0d02ec1d0a05f88ef9e74b516842900c41f0f2fe", + "rev": "a0bb0d11514f92b639514220114ac8063c72d0a3", "type": "github" }, "original": { @@ -181,11 +181,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1776983936, - "narHash": "sha256-ZOQyNqSvJ8UdrrqU1p7vaFcdL53idK+LOM8oRWEWh6o=", + "lastModified": 1774465523, + "narHash": "sha256-4v7HPm63Q90nNn4fgkgKsjW1AH2Klw7XzPtHJr562nM=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "2096f3f411ce46e88a79ae4eafcfc9df8ed41c61", + "rev": "de895be946ad1d8aafa0bb6dfc7e7e0e9e466a29", "type": "github" }, "original": { @@ -197,11 +197,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1776734388, - "narHash": "sha256-vl3dkhlE5gzsItuHoEMVe+DlonsK+0836LIRDnm6MXQ=", + "lastModified": 1774388614, + "narHash": "sha256-tFwzTI0DdDzovdE9+Ras6CUss0yn8P9XV4Ja6RjA+nU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "10e7ad5bbcb421fe07e3a4ad53a634b0cd57ffac", + "rev": "1073dad219cb244572b74da2b20c7fe39cb3fa9e", "type": "github" }, "original": { @@ -261,11 +261,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1776877367, - "narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=", + "lastModified": 1774386573, + "narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=", "owner": "nixos", "repo": "nixpkgs", - "rev": "0726a0ecb6d4e08f6adced58726b95db924cef57", + "rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9", "type": "github" }, "original": { @@ -307,11 +307,11 @@ "utils": "utils" }, "locked": { - "lastModified": 1775228763, - "narHash": "sha256-8fKOEOouCaPZLBTdWPS+uU0bxsPp1OmfloDNoNSiu8w=", + "lastModified": 1769870714, + "narHash": "sha256-wjwCj70iiFXoAasQto+3jTaA4wCMOAs/rdX+nsmtBrQ=", "owner": "SaumonNet", "repo": "proxmox-nixos", - "rev": "e803cb839e5e5207fa37d92bc6ac7290f4dba633", + "rev": "c1f79f104930347a0b84abbca0d42884063a8c09", "type": "github" }, "original": { @@ -338,11 +338,11 @@ "secrets": { "flake": false, "locked": { - "lastModified": 1777076192, - "narHash": "sha256-N7n2OPN2IRWwL73Cr6mc5nNhucHmMeFas9hQ/NF0bFg=", + "lastModified": 1774571252, + "narHash": "sha256-NU/vfItTMSjaRTXe0UDzbWR8UnhkBUFU47OpqEpxKb4=", "ref": "refs/heads/main", - "rev": "34ff1c4b0460a2e103a8fec183f53f274dc123ed", - "revCount": 32, + "rev": "7965907ae885d77acb3c4ecc11cee096a12af868", + "revCount": 25, "type": "git", "url": "ssh://git@git.jfreudenberger.de/JuliusFreudenberger/nix-private.git" }, diff --git a/flake.nix b/flake.nix index 74a3034..3fa246c 100644 --- a/flake.nix +++ b/flake.nix @@ -167,24 +167,6 @@ ]; }; - 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 = { diff --git a/hosts/srv03/default.nix b/hosts/srv03/default.nix deleted file mode 100644 index b56205c..0000000 --- a/hosts/srv03/default.nix +++ /dev/null @@ -1,91 +0,0 @@ -{ 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? -} diff --git a/hosts/srv03/hardware-configuration.nix b/hosts/srv03/hardware-configuration.nix deleted file mode 100644 index 729ee98..0000000 --- a/hosts/srv03/hardware-configuration.nix +++ /dev/null @@ -1,24 +0,0 @@ -# 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..useDHCP`. - networking.useDHCP = lib.mkDefault true; - # networking.interfaces.enp1s0.useDHCP = lib.mkDefault true; - - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; -} diff --git a/hosts/srv03/secrets.nix b/hosts/srv03/secrets.nix deleted file mode 100644 index 2a119d0..0000000 --- a/hosts/srv03/secrets.nix +++ /dev/null @@ -1,7 +0,0 @@ -{ inputs, ... }: -{ - age.secrets = { - inwx.file = "${inputs.secrets}/secrets/dns-management/inwx"; - pocket-id.file = "${inputs.secrets}/secrets/srv03/pocket-id"; - }; -} diff --git a/modules/administration.nix b/modules/administration.nix index a68b589..233f194 100644 --- a/modules/administration.nix +++ b/modules/administration.nix @@ -7,7 +7,6 @@ remmina teleport_17.client - opkssh ]; } diff --git a/modules/disko/legacy-full-ext4-swap.nix b/modules/disko/legacy-full-ext4-swap.nix deleted file mode 100644 index c06727f..0000000 --- a/modules/disko/legacy-full-ext4-swap.nix +++ /dev/null @@ -1,45 +0,0 @@ -{ - 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; - }; - }; - }; - }; - }; - }; - }; -} diff --git a/modules/dockhand.nix b/modules/dockhand.nix index 1e8f57b..7eeaf8e 100644 --- a/modules/dockhand.nix +++ b/modules/dockhand.nix @@ -38,7 +38,7 @@ in { }; extraOptions = [ ''--mount=type=volume,source=dockhand-data,target=/app/data,volume-driver=local'' - ''--group-add=${config.ids.gids.docker}'' + ''--group-add=131'' # docker group ]; }; }; diff --git a/modules/laptop.nix b/modules/laptop.nix index 49e7492..9a95899 100644 --- a/modules/laptop.nix +++ b/modules/laptop.nix @@ -4,7 +4,7 @@ ... }: { - boot.kernelPackages = pkgs.linuxKernel.packages.linux_zen; + boot.kernelPackages = pkgs.linuxPackages_latest; services.logind.settings.Login = { HandleLidSwitch= "suspend-then-hibernate"; diff --git a/modules/pocket-id.nix b/modules/pocket-id.nix deleted file mode 100644 index 48ac9cb..0000000 --- a/modules/pocket-id.nix +++ /dev/null @@ -1,58 +0,0 @@ -{ - 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" - ]; - }; - - }; -} diff --git a/modules/traefik-oidc.nix b/modules/traefik-oidc.nix deleted file mode 100644 index 92b1204..0000000 --- a/modules/traefik-oidc.nix +++ /dev/null @@ -1,221 +0,0 @@ -{ - 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: - - _OIDC_AUTH_SECRET - - _OIDC_AUTH_PROVIDER_CLIENT_ID - - _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"; - - }; -} diff --git a/modules/traefik.nix b/modules/traefik.nix index 8bea41f..92b1204 100644 --- a/modules/traefik.nix +++ b/modules/traefik.nix @@ -7,7 +7,15 @@ let cfg = config.services.traefik-docker; - version = "3.6.14"; + + 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 { @@ -21,40 +29,105 @@ in { description = "Secrets for DNS providers."; type = lib.types.listOf lib.types.anything; }; - dnsChallengeProvider = lib.mkOption { - description = "Name of provider for DNS challenge."; + 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: + - _OIDC_AUTH_SECRET + - _OIDC_AUTH_PROVIDER_CLIENT_ID + - _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:v${version}"; + image = "traefik:v3.6.6"; cmd = [ "--providers.docker=true" - "--providers.docker.endpoint=http://docker-socket-proxy:2375" "--providers.docker.exposedByDefault=false" - "--providers.docker.network=webproxy" + "--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=0" - "--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0" - "--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0" - "--serverstransport.forwardingtimeouts.responseheadertimeout=0s" - "--serverstransport.forwardingtimeouts.idleconntimeout=0s" + "--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.email=contact@jfreudenberger.de" "--certificatesresolvers.letsencrypt.acme.storage=/certs/acme.json" "--certificatesresolvers.letsencrypt.acme.dnschallenge=true" - "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=${cfg.dnsChallengeProvider}" - "--providers.file.filename=/dynamic-config/providers.yaml" + "--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 = [ @@ -62,17 +135,54 @@ in { "443:443" ]; networks = [ - "webproxy" - "docker-socket" + "traefik" ]; - environmentFiles = lib.forEach cfg.dnsSecrets (secret: secret.path); + 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 - traefik-providers-config = (pkgs.formats.yaml {}).generate "traefik-providers-config" { - tcp.serversTransports.pp-v2.proxyProtocol.version = 2; + 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-providers-config}:/dynamic-config/providers.yaml:ro" + "${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'' @@ -84,49 +194,24 @@ 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-webproxy.service" - "docker-network-docker-socket.service" + "docker-network-traefik.service" ]; requires = [ - "docker-network-webproxy.service" - "docker-network-docker-socket.service" + "docker-network-traefik.service" ]; }; - systemd.services."docker-network-webproxy" = { + systemd.services."docker-network-traefik" = { path = [ pkgs.docker ]; serviceConfig = { Type = "oneshot"; }; script = '' - 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 + docker network inspect traefik || docker network create traefik --ipv4 --ipv6 --subnet=172.18.0.0/16 --gateway=172.18.0.1 ''; };