feat(pm): block missing nginx host connections #98

closed
opened by a.starrysky.fyi targeting main from private/minion/push-zoqyltwplryx

We previously returned One Of The Websites when nginx was accessed from a host that we didn't know about. That included direct IP address access as well as things which have been CNAMEd to us (either through a starred record or due to past services) but which aren't actually hosted by us.

This leads to a number of undesireable effects:

  • User confusion ("why does the aux docs website have Stalwart?")
  • Incorrect SSL certificates ("your blog seems to have an invalid certificate")
  • SSL being offered via direct IPs, which isn't possible to sign on the public internet

We can block this by making a default server to take control whenever nothing matches, and setting that default server to block all connections and reject all SSL handshakes

We need to have a certificate for this, but it needn't actually be valid for anything so let's self sign stuff...

Changed files
+51 -3
packetmix
systems
+30
packetmix/systems/common/nginx.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, ... }: { 6 + # By default, nginx will serve a "best-effort" site even if there is no matching vhost 7 + # We can disable this by making a matching vhost and returning 444... 8 + # Notice how we don't enable nginx here: that makes this safe to deploy even on places that don't currently run nginx. We're effectively changing the default behavior 9 + services.nginx.virtualHosts."missinghost.invalid" = { 10 + default = true; 11 + 12 + addSSL = true; 13 + enableACME = true; 14 + acmeRoot = null; 15 + 16 + locations."/".return = "444"; 17 + 18 + extraConfig = '' 19 + ssl_reject_handshake on; 20 + ''; 21 + }; 22 + 23 + systemd.services."acme-missinghost.invalid".enable = false; 24 + systemd.timers."acme-missinghost.invalid".enable = false; 25 + 26 + systemd.targets."acme-finished-missinghost.invalid" = { 27 + requires = lib.mkForce [ "acme-selfsigned-missinghost.invalid.service" ]; 28 + after = lib.mkForce [ "acme-selfsigned-missinghost.invalid.service" ]; 29 + }; 30 + }
+1 -3
packetmix/systems/umber/silverbullet.nix
··· 28 28 services.nginx.virtualHosts."silverbullet.starrysky.fyi" = { 29 29 listenAddresses = [ "localhost.tailscale" ]; 30 30 31 - addSSL = true; 31 + onlySSL = true; 32 32 enableACME = true; 33 33 acmeRoot = null; 34 34 35 - serverAliases = [ "umber.clicks.domains" ]; 36 - 37 35 locations."/" = { 38 36 proxyPass = "http://$silverbullet_upstream_minion_only"; 39 37 recommendedProxySettings = true;
+20
packetmix/systems/umber/grocy.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, ... }: { 6 + services.grocy = { 7 + enable = true; 8 + hostName = "grocy.starrysky.fyi"; 9 + 10 + settings.currency = "GBP"; 11 + }; 12 + 13 + services.nginx.virtualHosts."grocy.starrysky.fyi" = { 14 + acmeRoot = null; 15 + forceSSL = lib.mkForce false; 16 + onlySSL = true; 17 + }; 18 + 19 + clicks.storage.impermanence.persist.directories = [ "/var/lib/grocy" ]; 20 + }