Lockdown intranet services behind tailscale
This commit is contained in:
parent
4d658e10d3
commit
d557820d6c
@ -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}";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -22,5 +22,6 @@
|
||||
./dashy.nix
|
||||
./librechat.nix
|
||||
./actualbudget.nix
|
||||
./unifi.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 ];
|
||||
};
|
||||
}
|
||||
|
25
common/server/unifi.nix
Normal file
25
common/server/unifi.nix
Normal file
@ -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.
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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" ];
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
11
secrets/digitalocean-dns-credentials.age
Normal file
11
secrets/digitalocean-dns-credentials.age
Normal file
@ -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
|
||||
DÑØQî¬5–-ô@<40>¢¿—ßÐN5<4E> Ãÿ$Ø‚™’Çž…êÐ<C3AA>X=ŒHŽDÁ`P×5ZA´÷¼YóäÓ?¡é^[³1”6ÕK*mP݈ªæ1æç÷ß›ƒ:$^ÑfDœ*î†ÿ“š-zi´"·Tàuÿüò
|
@ -51,4 +51,7 @@ with roles;
|
||||
|
||||
# Librechat
|
||||
"librechat-env-file.age".publicKeys = librechat;
|
||||
|
||||
# For ACME DNS Challenge
|
||||
"digitalocean-dns-credentials.age".publicKeys = server;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user