diff --git a/hosts/kima/modules.nix b/hosts/kima/modules.nix index ad14d777..ea6c4542 100644 --- a/hosts/kima/modules.nix +++ b/hosts/kima/modules.nix @@ -214,6 +214,9 @@ scheduler = "scx_lavd"; flags = "--performance"; }; + tailscale = { + enable = true + }; udisks = { enable = true; }; diff --git a/hosts/sobotka/modules.nix b/hosts/sobotka/modules.nix index 47daf837..770172cc 100644 --- a/hosts/sobotka/modules.nix +++ b/hosts/sobotka/modules.nix @@ -3,8 +3,8 @@ boot = { kernel = { variant = "latest"; - hardware = [ "amd" ]; - extraKernelParams = [ ]; + hardware = ["amd"]; + extraKernelParams = []; }; loader = { default = { @@ -213,6 +213,9 @@ scheduler = "scx_lavd"; flags = "--performance"; }; + tailscale = { + enable = true; + }; udisks = { enable = true; }; diff --git a/hosts/sobotka/server.nix b/hosts/sobotka/server.nix index 4eb841d8..08323b7c 100644 --- a/hosts/sobotka/server.nix +++ b/hosts/sobotka/server.nix @@ -8,16 +8,9 @@ uid = 994; gid = 993; - authentik = { - enable = true; - }; traefik = { enable = true; }; - www = { - enable = true; - url = "cnst.dev"; - }; gitea = { enable = true; }; @@ -62,6 +55,22 @@ credentialsFile = config.age.secrets.vaultwardenCloudflared.path; }; }; + www = { + enable = true; + url = "cnst.dev"; + cloudflared = { + tunnelId = "e5076186-efb7-405a-998c-6155af7fb221"; + credentialsFile = config.age.secrets.wwwCloudflared.path; + }; + }; + authentik = { + enable = true; + url = "auth.cnst.dev"; + cloudflared = { + tunnelId = "b66f9368-db9e-4302-8b48-527cda34a635"; + credentialsFile = config.age.secrets.authentikCloudflared.path; + }; + }; nextcloud = { enable = true; adminpassFile = config.age.secrets.nextcloudAdminPass.path; diff --git a/modules/default.nix b/modules/default.nix index 3373a05d..f1c8d7be 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -113,6 +113,7 @@ ./nixos/services/udisks ./nixos/services/xserver ./nixos/services/zram + ./nixos/services/tailscale ./nixos/system/fonts ./nixos/system/locale diff --git a/modules/nixos/services/tailscale/default.nix b/modules/nixos/services/tailscale/default.nix new file mode 100644 index 00000000..81a3a9e3 --- /dev/null +++ b/modules/nixos/services/tailscale/default.nix @@ -0,0 +1,16 @@ +{ + config, + lib, + ... +}: +with lib; let + cfg = config.nixos.services.tailscale; +in { + options.nixos.services.tailscale = { + enable = mkEnableOption "Enable tailscale"; + }; + + config = mkIf cfg.enable { + services.tailscale.enable = true; + }; +} diff --git a/modules/server/authentik/default.nix b/modules/server/authentik/default.nix index 1ec92fba..63980cc8 100644 --- a/modules/server/authentik/default.nix +++ b/modules/server/authentik/default.nix @@ -15,7 +15,21 @@ in { }; url = lib.mkOption { type = lib.types.str; - default = "auth.${srv.domain}"; + default = "auth.${srv.www.domain}"; + }; + cloudflared = { + credentialsFile = lib.mkOption { + type = lib.types.str; + example = lib.literalExpression '' + pkgs.writeText "cloudflare-credentials.json" ''' + {"AccountTag":"secret"."TunnelSecret":"secret","TunnelID":"secret"} + ''' + ''; + }; + tunnelId = lib.mkOption { + type = lib.types.str; + example = "00000000-0000-0000-0000-000000000000"; + }; }; homepage.name = lib.mkOption { type = lib.types.str; @@ -36,10 +50,28 @@ in { }; config = lib.mkIf cfg.enable { - age.secrets.authentikEnv = { - file = "${self}/secrets/authentikEnv.age"; - owner = "authentik"; + age.secrets = { + authentikEnv = { + file = "${self}/secrets/authentikEnv.age"; + owner = "authentik"; + }; + authentikCloudflared = { + file = "${self}/secrets/authentikCloudflared.age"; + owner = "authentik"; + }; }; + + server = { + fail2ban = lib.mkIf cfg.enable { + jails = { + authentik = { + serviceName = "${cfg.url}"; + failRegex = "^.*Username or password is incorrect. Try again. IP: . Username: .*.$"; + }; + }; + }; + }; + services = { authentik = { enable = true; @@ -52,6 +84,15 @@ in { }; }; + cloudflared = { + enable = true; + tunnels.${cfg.cloudflared.tunnelId} = { + credentialsFile = cfg.cloudflared.credentialsFile; + default = "http_status:404"; + ingress."${cfg.url}".service = "http://127.0.0.1:9000"; + }; + }; + traefik = { dynamicConfigOptions = { http = { @@ -89,7 +130,7 @@ in { routers = { auth = { entryPoints = ["websecure"]; - rule = "Host(`${cfg.url}`) || HostRegexp(`{subdomain:[a-z0-9]+}.${srv.domain}`) && PathPrefix(`/outpost.goauthentik.io/`)"; + rule = "Host(`${cfg.url}`) || HostRegexp(`{subdomain:[a-z0-9]+}.${srv.www.url}`) && PathPrefix(`/outpost.goauthentik.io/`)"; service = "auth"; tls.certResolver = "letsencrypt"; }; diff --git a/modules/server/www/default.nix b/modules/server/www/default.nix index 675f9d9d..fc1737a6 100644 --- a/modules/server/www/default.nix +++ b/modules/server/www/default.nix @@ -2,6 +2,7 @@ lib, config, pkgs, + self, ... }: let inherit (lib) mkOption mkEnableOption mkIf types; @@ -16,29 +17,97 @@ in { default = ""; type = types.str; description = '' - Public domain name to be used to access the server services via Caddy reverse proxy + Public domain name to be used to access the server services via Traefik reverse proxy ''; }; + cloudflared = { + credentialsFile = lib.mkOption { + type = lib.types.str; + example = lib.literalExpression '' + pkgs.writeText "cloudflare-credentials.json" ''' + {"AccountTag":"secret"."TunnelSecret":"secret","TunnelID":"secret"} + ''' + ''; + }; + tunnelId = lib.mkOption { + type = lib.types.str; + example = "00000000-0000-0000-0000-000000000000"; + }; + }; }; - config = mkIf cfg.enable { - services.caddy.virtualHosts."${cfg.url}" = { - useACMEHost = cfg.url; - extraConfig = '' - handle_path /.well-known/webfinger { - header Content-Type application/jrd+json - respond `{ - "subject": "acct:adam@${cfg.url}", - "links": [ - { - "rel": "http://openid.net/specs/connect/1.0/issuer", - "href": "https://login.${cfg.url}/realms/cnix" - } - ] - }` - } - reverse_proxy http://127.0.0.1:8283 - ''; + config = mkIf cfg.enable { + age.secrets = { + wwwCloudflared.file = "${self}/secrets/wwwCloudflared.age"; + }; + + server = { + fail2ban = lib.mkIf config.server.www.enable { + jails = { + www = { + serviceName = "cnst.dev"; + failRegex = "^.*Username or password is incorrect. Try again. IP: . Username: .*.$"; + }; + }; + }; + }; + + services = { + nginx = { + enable = true; + defaultListen = [ + { + addr = "127.0.0.1"; + port = 8283; + } + ]; + virtualHosts."webfinger" = { + forceSSL = false; + serverName = cfg.url; + root = "/etc/webfinger"; + locations."= /.well-known/webfinger" = { + root = "/etc/webfinger"; + extraConfig = '' + default_type application/jrd+json; + try_files /.well-known/webfinger =404; + ''; + }; + }; + }; + + cloudflared = { + enable = true; + tunnels.${cfg.cloudflared.tunnelId} = { + credentialsFile = cfg.cloudflared.credentialsFile; + default = "http_status:404"; + ingress."${cfg.url}".service = "http://127.0.0.1:8283"; + }; + }; + }; + + environment.etc."webfinger/.well-known/webfinger".text = '' + { + "subject": "acct:adam@${cfg.url}", + "links": [ + { + "rel": "http://openid.net/specs/connect/1.0/issuer", + "href": "https://auth.${cfg.url}/application/o/tailscale/" + } + ] + } + ''; + + services.traefik.dynamicConfigOptions.http = { + routers.webfinger = { + entryPoints = ["websecure"]; + rule = "Host(`${cfg.url}`) && Path(`/.well-known/webfinger`)"; + service = "webfinger"; + tls.certResolver = "letsencrypt"; + }; + + services.webfinger.loadBalancer.servers = [ + {url = "http://127.0.0.1:8283";} + ]; }; }; } diff --git a/secrets/authentikCloudflared.age b/secrets/authentikCloudflared.age new file mode 100644 index 00000000..4fa789f4 --- /dev/null +++ b/secrets/authentikCloudflared.age @@ -0,0 +1,14 @@ +age-encryption.org/v1 +-> ssh-ed25519 t9iOEg 2oTh42u4hxJGAypwwLJwDCPMngauHB8BhKA83xAXr1M +Sr6Hbfnd52F0dUk5RO3wxxJ7RGi3+NUCBq/MzDbKR7s +-> ssh-ed25519 KUYMFA O2j6gYY1QR1ZlFiWw+7y6nKUeE658Wp3PdV6dsMqwTU +NYwnTkZX5PHnNtL1vqJqIsYzIFUY43AVso8ecMAHvWs +-> ssh-ed25519 76RhUQ VTzoQh0fHrG41Gr0YnPY7Jz7yFFugigm/DpUUE/Ny18 +SITvKJf5+ql4DhpJoPVvEXdLGIBeKnlLlm8u4QPr0RY +-> ssh-ed25519 Jf8sqw oVI2y3zqpswvyZoNwklrKI1ZbxMJ5a1kzc43RErkbD8 +aHNuHMH2XNQ7+9sfsA8LMhBSgTDmvmI1wY26V2j+lsE +--- 0UL0vxM2f5IeVhDO1Cg7SUmhuvpFh+GsEEW4g5JEORU +)q$*bX` %f +_%%1ݗ)fT٧&`+KqI\=M !7b]X_lri_;R +)cH5. p :m_&Vj/Ra|MUbyElnS9"گ+< + dP*|VACpR^| \ No newline at end of file diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 793af1ef..fff8f319 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -33,8 +33,7 @@ let rziggy ]; all = kima ++ bunk ++ sobotka ++ ziggy; -in -{ +in { # Generic "cnstssh.age".publicKeys = kima; "cnixssh.age".publicKeys = kima; @@ -60,6 +59,8 @@ in "slskd.age".publicKeys = kima ++ sobotka; "authentikEnv.age".publicKeys = kima ++ sobotka; "traefikEnv.age".publicKeys = kima ++ sobotka; + "wwwCloudflared.age".publicKeys = kima ++ sobotka; + "authentikCloudflared.age".publicKeys = kima ++ sobotka; # Ziggy-specific "cloudflareDnsCredentialsZiggy.age".publicKeys = kima ++ ziggy; diff --git a/secrets/wwwCloudflared.age b/secrets/wwwCloudflared.age new file mode 100644 index 00000000..fab746b8 --- /dev/null +++ b/secrets/wwwCloudflared.age @@ -0,0 +1,11 @@ +age-encryption.org/v1 +-> ssh-ed25519 t9iOEg CWarcJM8RPjJW+e3BQ99KEUnOZQUDEIIeygeh/8MZUw +xux60KMmyOVvgiuEqyEPXM1Wr2ne8AyHT6CAWKMOcKo +-> ssh-ed25519 KUYMFA AThOlxHT41vsczkSGzJmT+VmWC2dAnLiIcTJP+YySkc +Jy8HyRuzIFtGYMimxsQNm2NnbluVwS6ZuXhq4uRfabY +-> ssh-ed25519 76RhUQ dKyDJ4DCNtYWQ2+cC7gwa+14aw99S+mU38tpQrlOmFc +0mD5Qcv8b8Bh1e4mbqdH26UtCJaUe7C7dDDSXJd1iRY +-> ssh-ed25519 Jf8sqw To2I/347gMqYx0PxMgYqbGekUpfqWOQwtgJ+0AFilTw +nIo4dH9JnOuWo48a17Kjyee5sQV8HN+PNXCWDT4fjIg +--- SuE6Z9ipbuWhxoaULMf6OGtG3BNkQ1BpWXkgfAI7Y6Y +Ru1dژdʋ(s )M0vѹZVqiiEc* {~teP{DmA~Ŭc.TbƝ}