diff --git a/common/server/dashy.nix b/common/server/dashy.nix index 2e29af8..54cfb96 100644 --- a/common/server/dashy.nix +++ b/common/server/dashy.nix @@ -37,17 +37,5 @@ in ]; }; }; - - services.nginx.enable = true; - services.nginx.virtualHosts."s0.koi-bebop.ts.net" = { - default = true; - addSSL = true; - serverAliases = [ "s0" ]; - sslCertificate = "/secret/ssl/s0.koi-bebop.ts.net.crt"; - sslCertificateKey = "/secret/ssl/s0.koi-bebop.ts.net.key"; - locations."/" = { - proxyPass = "http://localhost:${toString cfg.port}"; - }; - }; }; } diff --git a/common/server/default.nix b/common/server/default.nix index af9df2c..6c202f4 100644 --- a/common/server/default.nix +++ b/common/server/default.nix @@ -22,5 +22,6 @@ ./dashy.nix ./librechat.nix ./actualbudget.nix + ./unifi.nix ]; } diff --git a/common/server/nginx.nix b/common/server/nginx.nix index 4cb984b..8676922 100644 --- a/common/server/nginx.nix +++ b/common/server/nginx.nix @@ -4,6 +4,10 @@ let cfg = config.services.nginx; in { + options.services.nginx = { + openFirewall = lib.mkEnableOption "Open firewall ports 80 and 443"; + }; + config = lib.mkIf cfg.enable { services.nginx = { recommendedGzipSettings = true; @@ -12,6 +16,8 @@ in recommendedTlsSettings = true; }; - networking.firewall.allowedTCPPorts = [ 80 443 ]; + services.nginx.openFirewall = lib.mkDefault true; + + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ 80 443 ]; }; } diff --git a/common/server/unifi.nix b/common/server/unifi.nix new file mode 100644 index 0000000..3c1cd91 --- /dev/null +++ b/common/server/unifi.nix @@ -0,0 +1,25 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.unifi; +in +{ + options.services.unifi = { + # Open select Unifi ports instead of using openFirewall to avoid opening access to unifi's control panel + openMinimalFirewall = lib.mkEnableOption "Open bare minimum firewall ports"; + }; + + config = lib.mkIf cfg.enable { + services.unifi.unifiPackage = pkgs.unifi8; + + networking.firewall = lib.mkIf cfg.openMinimalFirewall { + allowedUDPPorts = [ + 3478 # STUN + 10001 # used for device discovery. + ]; + allowedTCPPorts = [ + 8080 # Used for device and application communication. + ]; + }; + }; +} diff --git a/machines/ponyo/default.nix b/machines/ponyo/default.nix index 7d757e3..a2da7c5 100644 --- a/machines/ponyo/default.nix +++ b/machines/ponyo/default.nix @@ -118,14 +118,6 @@ # proxied web services services.nginx.enable = true; - services.nginx.virtualHosts."jellyfin.neet.cloud" = { - enableACME = true; - forceSSL = true; - locations."/" = { - proxyPass = "http://s0.koi-bebop.ts.net"; - proxyWebsockets = true; - }; - }; services.nginx.virtualHosts."navidrome.neet.cloud" = { enableACME = true; forceSSL = true; diff --git a/machines/storage/s0/dashy.yaml b/machines/storage/s0/dashy.yaml index dc11ec6..48e93c0 100644 --- a/machines/storage/s0/dashy.yaml +++ b/machines/storage/s0/dashy.yaml @@ -60,73 +60,65 @@ sections: - &ref_0 title: Jellyfin icon: hl-jellyfin - url: http://s0:8097 + url: https://jellyfin.s0.neet.dev target: sametab - statusCheck: true - statusCheckUrl: http://jellyfin.s0 + statusCheck: false id: 0_1956_jellyfin - &ref_1 title: Sonarr description: Manage TV icon: hl-sonarr - url: http://s0:8989 + url: https://sonarr.s0.neet.dev target: sametab - statusCheck: true - statusCheckUrl: http://sonarr.s0 + statusCheck: false id: 1_1956_sonarr - &ref_2 title: Radarr description: Manage Movies icon: hl-radarr - url: http://s0:7878 + url: https://radarr.s0.neet.dev target: sametab - statusCheck: true - statusCheckUrl: http://radarr.s0 + statusCheck: false id: 2_1956_radarr - &ref_3 title: Lidarr description: Manage Music icon: hl-lidarr - url: http://s0:8686 + url: https://lidarr.s0.neet.dev target: sametab - statusCheck: true - statusCheckUrl: http://lidarr.s0 + statusCheck: false id: 3_1956_lidarr - &ref_4 title: Prowlarr description: Indexers icon: hl-prowlarr - url: http://prowlarr.s0 + url: https://prowlarr.s0.neet.dev target: sametab - statusCheck: true - statusCheckUrl: http://prowlarr.s0 + statusCheck: false id: 4_1956_prowlarr - &ref_5 title: Bazarr description: Subtitles icon: hl-bazarr - url: http://s0:6767 + url: https://bazarr.s0.neet.dev target: sametab - statusCheck: true - statusCheckUrl: http://bazarr.s0 + statusCheck: false id: 5_1956_bazarr - &ref_6 title: Navidrome description: Play Music icon: hl-navidrome - url: http://s0:4534 + url: https://music.s0.neet.dev target: sametab - statusCheck: true - statusCheckUrl: http://music.s0 + statusCheck: false id: 6_1956_navidrome - &ref_7 title: Transmission description: Torrenting icon: hl-transmission - url: http://s0:9091 + url: https://transmission.s0.neet.dev target: sametab - statusCheck: true - statusCheckUrl: http://transmission.s0 + statusCheck: false id: 7_1956_transmission filteredItems: - *ref_0 diff --git a/machines/storage/s0/default.nix b/machines/storage/s0/default.nix index 2599ca6..bec798a 100644 --- a/machines/storage/s0/default.nix +++ b/machines/storage/s0/default.nix @@ -165,61 +165,96 @@ }; # nginx - services.nginx.enable = true; - services.nginx.virtualHosts."bazarr.s0" = { - listen = [{ addr = "0.0.0.0"; port = 6767; } { addr = "0.0.0.0"; port = 80; }]; - locations."/".proxyPass = "http://vpn.containers:6767"; - }; - services.nginx.virtualHosts."radarr.s0" = { - listen = [{ addr = "0.0.0.0"; port = 7878; } { addr = "0.0.0.0"; port = 80; }]; - locations."/".proxyPass = "http://vpn.containers:7878"; - }; - services.nginx.virtualHosts."lidarr.s0" = { - listen = [{ addr = "0.0.0.0"; port = 8686; } { addr = "0.0.0.0"; port = 80; }]; - locations."/".proxyPass = "http://vpn.containers:8686"; - }; - services.nginx.virtualHosts."sonarr.s0" = { - listen = [{ addr = "0.0.0.0"; port = 8989; } { addr = "0.0.0.0"; port = 80; }]; - locations."/".proxyPass = "http://vpn.containers:8989"; - }; - services.nginx.virtualHosts."prowlarr.s0" = { - listen = [{ addr = "0.0.0.0"; port = 9696; } { addr = "0.0.0.0"; port = 80; }]; - locations."/".proxyPass = "http://vpn.containers:9696"; - }; - services.nginx.virtualHosts."music.s0" = { - listen = [{ addr = "0.0.0.0"; port = 4534; } { addr = "0.0.0.0"; port = 80; }]; - locations."/".proxyPass = "http://localhost:4533"; - }; - services.nginx.virtualHosts."jellyfin.s0" = { - listen = [{ addr = "0.0.0.0"; port = 8097; } { addr = "0.0.0.0"; port = 80; }]; - locations."/" = { - proxyPass = "http://localhost:8096"; - proxyWebsockets = true; - }; - }; - services.nginx.virtualHosts."jellyfin.neet.cloud".locations."/" = { - proxyPass = "http://localhost:8096"; - proxyWebsockets = true; - }; - services.nginx.virtualHosts."transmission.s0" = { - listen = [{ addr = "0.0.0.0"; port = 9091; } { addr = "0.0.0.0"; port = 80; }]; - locations."/" = { - proxyPass = "http://vpn.containers:9091"; - proxyWebsockets = true; + services.nginx = { + enable = true; + openFirewall = false; # All nginx services are internal + virtualHosts = + let + mkVirtualHost = external: internal: + { + ${external} = { + useACMEHost = "s0.neet.dev"; # Use wildcard cert + forceSSL = true; + locations."/" = { + proxyPass = internal; + proxyWebsockets = true; + }; + }; + }; + in + lib.mkMerge [ + (mkVirtualHost "bazarr.s0.neet.dev" "http://vpn.containers:6767") + (mkVirtualHost "radarr.s0.neet.dev" "http://vpn.containers:7878") + (mkVirtualHost "lidarr.s0.neet.dev" "http://vpn.containers:8686") + (mkVirtualHost "sonarr.s0.neet.dev" "http://vpn.containers:8989") + (mkVirtualHost "prowlarr.s0.neet.dev" "http://vpn.containers:9696") + (mkVirtualHost "transmission.s0.neet.dev" "http://vpn.containers:9091") + (mkVirtualHost "unifi.s0.neet.dev" "https://localhost:8443") + (mkVirtualHost "music.s0.neet.dev" "http://localhost:4533") + (mkVirtualHost "jellyfin.s0.neet.dev" "http://localhost:8096") + (mkVirtualHost "s0.neet.dev" "http://localhost:56815") + (mkVirtualHost "ha.s0.neet.dev" "http://localhost:8123") # home assistant + (mkVirtualHost "esphome.s0.neet.dev" "http://localhost:6052") + (mkVirtualHost "zigbee.s0.neet.dev" "http://localhost:55834") + { + # Landing page LAN redirect + "s0" = { + default = true; + redirectCode = 302; + globalRedirect = "s0.neet.dev"; + }; + "frigate.s0.neet.dev" = { + # Just configure SSL, frigate module configures the rest of nginx + useACMEHost = "s0.neet.dev"; + forceSSL = true; + }; + } + ]; + + # Problem #1: Keeping certain programs from being accessed from certain external networks/VLANs + # Solution #1: Isolate that service in a container system that automatically fowards the ports to the right network interface(s) + # Solution #2: Don't open the firewall for these services, manually open the ports instead for the specific network interface(s) (trickier and easy to miss ports or ports can change) + # Untrusted network list: + # - VLANs [cameras] + + # Problem #2: Untrusted internal services. Prevent them from accessing certain internal services (usually key unauth'd services like frigate) + # Solution #1: Isolate the untrusted services into their own container + # Untrusted services list: + # - Unifi? (it already has access to the cameras anyway?) + # - torrenting, *arr (worried about vulns) + + + tailscaleAuth = { + enable = true; + virtualHosts = [ + "bazarr.s0.neet.dev" + "radarr.s0.neet.dev" + "lidarr.s0.neet.dev" + "sonarr.s0.neet.dev" + "prowlarr.s0.neet.dev" + "transmission.s0.neet.dev" + "unifi.s0.neet.dev" + # "music.s0.neet.dev" # messes up navidrome + "jellyfin.s0.neet.dev" + "s0.neet.dev" + # "ha.s0.neet.dev" # messes up home assistant + "esphome.s0.neet.dev" + "zigbee.s0.neet.dev" + ]; + expectedTailnet = "koi-bebop.ts.net"; }; }; - networking.firewall.allowedTCPPorts = [ - 6767 - 7878 - 8686 - 8989 - 9696 - 4534 - 8097 - 9091 - 8443 # unifi - ]; + # Get wildcard cert + security.acme.certs."s0.neet.dev" = { + dnsProvider = "digitalocean"; + credentialsFile = "/run/agenix/digitalocean-dns-credentials"; + extraDomainNames = [ "*.s0.neet.dev" ]; + group = "nginx"; + dnsResolver = "1.1.1.1:53"; + dnsPropagationCheck = false; # sadly this erroneously fails + }; + age.secrets.digitalocean-dns-credentials.file = ../../../secrets/digitalocean-dns-credentials.age; virtualisation.oci-containers.backend = "podman"; virtualisation.podman.dockerSocket.enable = true; # TODO needed? @@ -230,8 +265,7 @@ services.unifi = { enable = true; - openFirewall = true; - unifiPackage = pkgs.unifi8; + openMinimalFirewall = true; }; boot.binfmt.emulatedSystems = [ "aarch64-linux" "armv7l-linux" ]; diff --git a/machines/storage/s0/home-automation.nix b/machines/storage/s0/home-automation.nix index b57c9f4..eacc1e6 100644 --- a/machines/storage/s0/home-automation.nix +++ b/machines/storage/s0/home-automation.nix @@ -1,8 +1,7 @@ { config, lib, pkgs, ... }: let - frigateHostname = "frigate.s0"; - frigatePort = 61617; + frigateHostname = "frigate.s0.neet.dev"; mkEsp32Cam = address: { ffmpeg = { @@ -41,9 +40,6 @@ in { networking.firewall.allowedTCPPorts = [ # 1883 # mqtt - 55834 # mqtt zigbee frontend - frigatePort - 4180 # oauth proxy ]; services.frigate = { @@ -80,21 +76,7 @@ in # Coral PCIe driver kernel.enableGasketKernelModule = true; - # Allow accessing frigate UI on a specific port in addition to by hostname - services.nginx.virtualHosts.${frigateHostname} = { - listen = [{ addr = "0.0.0.0"; port = frigatePort; } { addr = "0.0.0.0"; port = 80; }]; - }; - - services.esphome = { - enable = true; - address = "0.0.0.0"; - openFirewall = true; - }; - # TODO remove after upgrading nixos version - systemd.services.esphome.serviceConfig.ProcSubset = lib.mkForce "all"; - systemd.services.esphome.serviceConfig.ProtectHostname = lib.mkForce false; - systemd.services.esphome.serviceConfig.ProtectKernelLogs = lib.mkForce false; - systemd.services.esphome.serviceConfig.ProtectKernelTunables = lib.mkForce false; + services.esphome.enable = true; # TODO lock down services.mosquitto = { @@ -121,7 +103,7 @@ in # base_topic = "zigbee2mqtt"; }; frontend = { - host = "0.0.0.0"; + host = "localhost"; port = 55834; }; }; @@ -129,7 +111,6 @@ in services.home-assistant = { enable = true; - openFirewall = true; configWritable = true; extraComponents = [ "esphome" @@ -143,46 +124,15 @@ in # Includes dependencies for a basic setup # https://www.home-assistant.io/integrations/default_config/ default_config = { }; - }; - }; - # TODO need services.oauth2-proxy.cookie.domain ? - services.oauth2-proxy = - let - nextcloudServer = "https://neet.cloud/"; - in - { - enable = true; - - httpAddress = "http://0.0.0.0:4180"; - - nginx.domain = frigateHostname; - # nginx.virtualHosts = [ - # frigateHostname - # ]; - - email.domains = [ "*" ]; - - cookie.secure = false; - - provider = "nextcloud"; - - # redirectURL = "http://s0:4180/oauth2/callback"; # todo forward with nginx? - clientID = "4FfhEB2DNzUh6wWhXTjqQQKu3Ibm6TeYpS8TqcHe55PJC1DorE7vBZBELMKDjJ0X"; - keyFile = "/run/agenix/oauth2-proxy-env"; - - loginURL = "${nextcloudServer}/index.php/apps/oauth2/authorize"; - redeemURL = "${nextcloudServer}/index.php/apps/oauth2/api/v1/token"; - validateURL = "${nextcloudServer}/ocs/v2.php/cloud/user?format=json"; - - # todo --cookie-refresh - - extraConfig = { - # cookie-csrf-per-request = true; - # cookie-csrf-expire = "5m"; - # user-id-claim = "preferred_username"; + # Enable reverse proxy support + http = { + use_x_forwarded_for = true; + trusted_proxies = [ + "127.0.0.1" + "::1" + ]; }; }; - - age.secrets.oauth2-proxy-env.file = ../../../secrets/oauth2-proxy-env.age; + }; } diff --git a/secrets/digitalocean-dns-credentials.age b/secrets/digitalocean-dns-credentials.age new file mode 100644 index 0000000..62e3c66 --- /dev/null +++ b/secrets/digitalocean-dns-credentials.age @@ -0,0 +1,11 @@ +age-encryption.org/v1 +-> ssh-ed25519 WBT1Hw wjZGPvilRXGZsC2+7dWm/Nbau8Allv29WwQCr0XSAWU +uTOf/sokutOGDyc8fbTbBWXqCVQCFhGdHxwA6SXqhdA +-> ssh-ed25519 6AT2/g NU068qwqOWiKk0QwqP9vU4xJaND2OR4bo8xkmdWATgY +uGd0sb5PH+rREn9pgLOFwk29CX66aPBQMvr4rBazylc +-> ssh-ed25519 hPp1nw r2JRiZ7fsHPYDlte6Oh2Gx1KkugekFeeg3xSjziI+hQ +xnO0gscMdR25mj5uAX7D42FCbCQhqbU0wkiLX4OmVqk +-> ssh-ed25519 w3nu8g F03mPU63WwEs1SLUFErLOVCkARoggGIvvz9TFZfMOBY +HOdVA3xW9pqUPhclO6VueSfXg3ux06Ch3fucF6Vr4hM +--- niyo231HPT/+2dzflP+zhYjL9XiWsk7svesCYdkU1jA +DQ5-@N5 $؂ǞАX=HD`P5ZAY?^[16K*mP݈1:$^fD*-zi"Tu \ No newline at end of file diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 4c119e1..b13338e 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -51,4 +51,7 @@ with roles; # Librechat "librechat-env-file.age".publicKeys = librechat; + + # For ACME DNS Challenge + "digitalocean-dns-credentials.age".publicKeys = server; }