nginx: strict public default server; keep oc off 0.0.0.0 (copy)
Sync of the canonical module. The atproto vhost listens on 0.0.0.0 only, so it never shares an address group with oc.commoninternet.net (which is the default server on 100.84.190.30:80 and has no auth in front of it). A new public-default-reject vhost owns the public addresses — return 444 on 80, ssl_reject_handshake on 443 — so unmatched, absent or spoofed Host headers from the internet get nothing, now and after any future vhost is added. The tailnet is untouched: oc.commoninternet.net still serves opencode there. Verified with tools/check-exposure.sh in the project repo — 9/9 ok. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SmEK2voMnBa23495aLk1Ce
This commit is contained in:
co-authored by
Claude Opus 5
parent
7d72c05ae7
commit
21dfa22f9a
+54
-28
@@ -1,9 +1,9 @@
|
|||||||
# atproto-likes — the "most-liked accounts" web UI, run as a docker-compose stack behind nginx.
|
# atproto-likes — the "most-liked accounts" web UI, run as a docker-compose stack
|
||||||
#
|
#
|
||||||
# ⚠️ COPY. Canonical source:
|
# ⚠️ COPY. Canonical source:
|
||||||
# /srv/project-orchestrator/projects/notplants-atproto/nix/atproto-likes.nix
|
# /srv/project-orchestrator/projects/notplants-atproto/nix/atproto-likes.nix
|
||||||
# A flake cannot import an absolute path outside its own tree under pure
|
# Pure evaluation cannot import a path outside the flake tree. Re-copy after editing.
|
||||||
# evaluation, so the module is copied here. Re-copy after editing the original.
|
# behind the host's nginx.
|
||||||
#
|
#
|
||||||
# Import this from the host configuration:
|
# Import this from the host configuration:
|
||||||
# imports = [ /srv/project-orchestrator/projects/notplants-atproto/nix/atproto-likes.nix ];
|
# imports = [ /srv/project-orchestrator/projects/notplants-atproto/nix/atproto-likes.nix ];
|
||||||
@@ -18,13 +18,19 @@
|
|||||||
# DNS: atproto.commoninternet.net resolves to 143.244.213.108 — the operator's
|
# DNS: atproto.commoninternet.net resolves to 143.244.213.108 — the operator's
|
||||||
# gateway (tailnet peer `gateway-server-aug2-9pm`, 100.80.66.110), which fronts this
|
# gateway (tailnet peer `gateway-server-aug2-9pm`, 100.80.66.110), which fronts this
|
||||||
# host rather than pointing at it directly. So what arrives here is the gateway's
|
# host rather than pointing at it directly. So what arrives here is the gateway's
|
||||||
# back-end hop, and the gateway needs a route for this hostname to either
|
# back-end hop. It must point at
|
||||||
# http://100.84.190.30 (tailnet, preferred)
|
#
|
||||||
# http://168.119.126.100 (public)
|
# http://168.119.126.100 (the PUBLIC address — never 100.84.190.30)
|
||||||
# preserving the Host header, passing Upgrade/Connection through (the loading page
|
#
|
||||||
# is a websocket), and allowing a long read timeout (a cold scan runs minutes).
|
# because the tailscale address is where the opencode UI lives and is that address
|
||||||
# ACME here can only succeed if the gateway forwards /.well-known/acme-challenge/;
|
# group's default server; a hop there with a missing or wrong Host header would be
|
||||||
# otherwise the gateway should own the certificate and `enableACME` can go false.
|
# served opencode. See the listen comments below. The gateway should also pass
|
||||||
|
# Upgrade/Connection through (the loading page is a websocket) and allow a long
|
||||||
|
# read timeout (a cold scan runs minutes).
|
||||||
|
#
|
||||||
|
# The gateway forwards everything, including /.well-known/acme-challenge/, so ACME
|
||||||
|
# HTTP-01 here will start succeeding as soon as the gateway has a route for this
|
||||||
|
# hostname. Until then nginx serves the self-signed placeholder NixOS installs.
|
||||||
{ config, pkgs, lib, ... }:
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
@@ -73,25 +79,23 @@ in
|
|||||||
# resolve straight back to the gateway: a redirect loop. Serve both schemes
|
# resolve straight back to the gateway: a redirect loop. Serve both schemes
|
||||||
# and let the gateway decide where TLS terminates.
|
# and let the gateway decide where TLS terminates.
|
||||||
addSSL = true;
|
addSSL = true;
|
||||||
# Keeps retrying; it can only succeed if the gateway forwards
|
# The gateway forwards everything, so once it has a route for this hostname
|
||||||
# /.well-known/acme-challenge/ here, since the A record is the gateway's.
|
# the HTTP-01 challenge reaches us and this starts succeeding on its own.
|
||||||
# Until then nginx uses the self-signed placeholder NixOS installs, and the
|
# Until then the acme-atproto… unit sits in `failed` and nginx uses the
|
||||||
# acme-atproto… unit sits in `failed`. Set this false if the gateway is to
|
# self-signed placeholder; nothing else on the host is affected.
|
||||||
# own the certificate permanently.
|
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
# The `oc.commoninternet.net` vhost binds explicitly to the tailscale IP on
|
# PUBLIC INTERFACE ONLY — deliberately not the tailscale address.
|
||||||
# port 80. nginx prefers the most specific listen address, so a request to
|
#
|
||||||
# 100.84.190.30:80 was only ever matched against THAT server block — this
|
# nginx groups servers by the connection's local address and prefers the most
|
||||||
# vhost, listening on 0.0.0.0, never got a look in, and a gateway hop over
|
# specific listen. `oc.commoninternet.net` (the opencode UI) binds explicitly
|
||||||
# the tailnet was served the opencode UI instead. Listing the tailscale
|
# to 100.84.190.30:80, so that address has its own group in which oc is the
|
||||||
# address explicitly here puts both vhosts on that socket, so server_name
|
# DEFAULT server. Anything arriving there without a matching Host — a proxy
|
||||||
# decides, which is what we want.
|
# that drops the header, or sends `Host: 100.84.190.30` — is served opencode.
|
||||||
listen = [
|
# So the gateway must hop to the PUBLIC address, never the tailscale one:
|
||||||
{ addr = "0.0.0.0"; port = 80; ssl = false; }
|
# this vhost lives on 0.0.0.0, where the only servers are itself and the
|
||||||
{ addr = "0.0.0.0"; port = 443; ssl = true; }
|
# reject-everything default below. Keeping the two names on disjoint
|
||||||
{ addr = "100.84.190.30"; port = 80; ssl = false; }
|
# addresses is what makes "opencode cannot leak publicly" structural rather
|
||||||
{ addr = "100.84.190.30"; port = 443; ssl = true; }
|
# than a matter of getting a Host header right.
|
||||||
];
|
|
||||||
locations."/" = {
|
locations."/" = {
|
||||||
proxyPass = "http://127.0.0.1:${toString hostPort}";
|
proxyPass = "http://127.0.0.1:${toString hostPort}";
|
||||||
# The loading page streams scan progress over a websocket.
|
# The loading page streams scan progress over a websocket.
|
||||||
@@ -104,6 +108,28 @@ in
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Strict default server for the PUBLIC addresses. Without an explicit
|
||||||
|
# default_server, nginx promotes the first server block in the group — so
|
||||||
|
# every future vhost added to 0.0.0.0 silently becomes the thing that answers
|
||||||
|
# unmatched/absent/spoofed Host headers from the internet. This closes that
|
||||||
|
# door once: only names we deliberately serve get a response here.
|
||||||
|
#
|
||||||
|
# port 80 -> 444 (close the connection, no response at all)
|
||||||
|
# port 443 -> ssl_reject_handshake, so an unknown SNI never even gets TLS
|
||||||
|
#
|
||||||
|
# It listens only on 0.0.0.0, so the tailscale address group is untouched and
|
||||||
|
# oc.commoninternet.net keeps behaving exactly as before on the tailnet.
|
||||||
|
virtualHosts."public-default-reject" = {
|
||||||
|
default = true;
|
||||||
|
serverName = null;
|
||||||
|
rejectSSL = true;
|
||||||
|
listen = [
|
||||||
|
{ addr = "0.0.0.0"; port = 80; ssl = false; }
|
||||||
|
{ addr = "0.0.0.0"; port = 443; ssl = true; }
|
||||||
|
];
|
||||||
|
extraConfig = "return 444;";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
security.acme = {
|
security.acme = {
|
||||||
|
|||||||
Reference in New Issue
Block a user