{ config, pkgs, lib, ... }: with lib; let # Container config name = "traefik"; # Version tagging traefikVersion = "v3.6.8"; # Helper helper = import ../service-helper.nix { inherit config pkgs lib; }; cfg = config.numbus-server.services.traefik; in helper.mkPodmanService { inherit name; description = "Traefik reverse proxy, one to rule them all"; defaultPort = "7780"; pod = "false"; startDelay = 10; dataDirEnabled = false; middlewares = [ "secureHeaders" ]; dependencies = [ "sops-install-secrets.service" "network-online.target" ]; dirPermissions = [ "100999:100 ${cfg.configDir}" "100999:100 ${cfg.configDir}/certs" ]; # Compose file good composeText = '' services: traefik: image: docker.io/library/traefik:${traefikVersion} container_name: traefik hostname: traefik user: '1000:1000' network_mode: pasta ports: - "${cfg.port}:8080/tcp" - "443:443/tcp" volumes: - ${config.sops.templates."traefik/config".path}:/etc/traefik/traefik.yaml:ro - ${cfg.configDir}/certs:/var/traefik/certs - /etc/traefik/rules:/etc/traefik/rules:ro environment: - CF_DNS_API_TOKEN=${config.sops.placeholder."traefik/cloudflare_api_token"} cap_add: - NET_BIND_SERVICE security_opt: - no-new-privileges:true restart: unless-stopped ''; extraConfig = { sops.secrets."traefik/cloudflare_api_token" = { sopsFile = /etc/nixos/secrets/podman/traefik.yaml; gid = "100"; uid = "1000"; mode = "0400"; }; sops.templates."traefik/config"= { gid = "100"; uid = "100999"; mode = "0400"; content = '' global: checkNewVersion: false sendAnonymousUsage: false log: level: ${cfg.logLevel} accesslog: {} api: dashboard: true insecure: false entryPoints: web: address: :80 http: redirections: entryPoint: to: websecure scheme: https websecure: address: :443 forwardedHeaders: trustedIPs: - "127.0.0.1/32" - "10.0.0.0/8" - "192.168.0.0/16" - "172.16.0.0/12" certificatesResolvers: cloudflare: acme: email: ${config.numbus-server.mail.adminAddress} storage: /var/traefik/certs/cloudflare-acme.json caServer: "https://acme-v02.api.letsencrypt.org/directory" dnsChallenge: provider: cloudflare resolvers: - "1.1.1.1:53" - "9.9.9.9:53" serversTransport: insecureSkipVerify: true providers: file: directory: "/etc/traefik/rules" watch: true ''; path = "/etc/traefik/traefik.yaml"; }; sops.templates."traefik/rules/secureHeaders" = { gid = "100"; uid = "100999"; mode = "0400"; content = '' http: middlewares: secureHeaders: headers: FrameDeny: true AccessControlAllowMethods: 'GET,OPTIONS,PUT' AccessControlAllowOriginList: - origin-list-or-null AccessControlMaxAge: 100 AddVaryHeader: true BrowserXssFilter: true ContentTypeNosniff: true ForceSTSHeader: true STSIncludeSubdomains: true STSPreload: true ContentSecurityPolicy: default-src 'self' 'unsafe-inline' CustomFrameOptionsValue: SAMEORIGIN ReferrerPolicy: same-origin PermissionsPolicy: vibrate 'self' STSSeconds: 315360000 ''; path = "/etc/traefik/rules/secureHeaders.yaml"; }; sops.templates."traefik/rules/secureTLS" = { gid = "100"; uid = "100999"; mode = "0400"; content = '' tls: options: secureTLS: minVersion: VersionTLS12 sniStrict: true curvePreferences: - CurveP521 - CurveP384 cipherSuites: - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 ''; path = "/etc/traefik/rules/secureTLS.yaml"; }; }; extraOptions = { enable.default = true; logLevel = mkOption { type = types.enum [ "TRACE" "DEBUG" "INFO" "WARN" "ERROR" "FATAL" ]; default = "ERROR"; example = "ERROR"; description = "The level of detail Traefik should print in the logs."; }; }; }