TLS-SNI reverse proxy für yxorp

Mir geht schon länger auf die nerven dass wir die private keys für alle unsre TLS zertifikate aufm yxorp liegen haben müssen. Hab mich jetzt hingesetzt und das mal gelöst:

Das problem das wir da haben ist folgendes: wir haben nur eine public IPv4 IP aber viele VMs. Das einfachste und das was wir aktuell machen ist dass auf v4 port 443 halt einfach der HTTP(s) reverse proxy horch, TLS aufmacht, und dann mit http unverschlüsselt an den internen upstream server weitergeht.

Was man jetzt machen kann ist dass der reverse proxy sich den hostnamen im TLS-SNI anschaut und dann abhängig davon den upstream server auswählt und dann aber den rohen TCP stream dorthin weiterschickt, d.h. der reverse proxy muss TLS dann garnicht aufmachen!

Das ist mit dem nginx ‘stream’ modul (docs) auch eigendlich schnell gemacht, nur hast du wieder ein problem: es kann nur ein nginx modul auf port 443 horchen. Da bin ich dann bei meinem ersten versuch an dem ganzen gescheitert weil ich ja den lokalen nginx auch weiterhin verwenden will und nur nach und nach VMs auf nativ TLS umstellen will.

Inzwischen ist mir aber die idee gekommen, man könnte doch einfach die lokalen sites einfach auf ner anderen port/IP horchen lassen. Also pro http seite immer so:

server {
  listen [::1]:443 ssl;
}

und dann im nginx.conf für den TLS-SNI stuff:

stream {
  map $ssl_preread_server_name $selected_upstream {
    default local_tls
  }
  upstream local_tls {
    server [::1]:443;
  }
  server {
    listen [2001:db8::1]:443;
    proxy_pass $selected_upstream;
  }
}

Das funktioniert dann soweit aber dann hat man das nächste problem: jetzt sieht es für alle server so aus als hätten die clients alle die ::1 als IP.

Nach etwas verzweifelt nginx doku lesen findet man aber dann die nette proxy_protocol option für die http listen direktive (docs). Damit kann man die client IP weiterreichen und die seiten sehen keinen unterschied.

Dafür dann in den site configs:

server {
  listen [::1]:443 ssl proxy_protocol;
}

im globalen http {...} oder in ner conf.d/* config schalten wir das dann frei:

set_real_ip_from ::1/128;
real_ip_header proxy_protocol;

Das set_real_ip_from ist ein safety feature damit nginx nicht einfach jedem unverschlüsselt dahergelaufenem plaintext PROXY header glaubt. Hier erlauben wir das einfach nur von localhost. Das real_ip_header proxy_protocol aktiviert dann das ersetzen der nginx internen client IP mit dem was mit dem PROXY protocol daher kommt.

Zuletzt schalten wir noch in der stream modul config das senden der PROXY infos ein:

stream {
  server {
    proxy_protocol on;
  }
}

alles andere bleibt gleich.

(Das hier könnte man evtl. zu nem blogpost machen wenn man das noch etwas erweitert und noch wer proofen will)

Hab das auch so mal aufm yxorp deployed, scheint zu funktionieren. Bitte schreien wenn was kaputt is.

Blogpost TODO:

  • map stuff erklären
  • PROXY proto genauer anschauen?
  • Proofread. Post is ein wiki, einfach fixen wer was findet oder verbessern mag.

Kennst du sslh - https://github.com/yrutschle/sslh ? Das nehme ich her, um meine IPv4 Adresse auf mehrere VMs mit deren $zoigs zu verteilen. Der sslh server braucht keine Zertificate, sondern horcht auf ALPN/SNI Hostnames und verteilt diese dann gemäß vorgegebenen Regeln.

z.b. /etc/sslh.cfg for host_a.acme, host_b.acme, host_c.acme und host_c_1.acme:

# Where the sslh server is listening
listen:
(
    { host: "1.2.3.4"; port: "443"; keepalive: true; },
);

# Protocols and rules that will be applied
protocols:
(
     ## https
     # match BOTH ALPN/SNI
     { name: "tls"; host: "192.168.0.1"; port: "443"; alpn_protocols: [ "http/1.1", "http/1.0", "http/2.0" ]; sni_hostnames: [ "host_a.acme" ]; log_level: 0; tfo_ok: true },
     { name: "tls"; host: "192.168.0.2"; port: "443"; alpn_protocols: [ "http/1.1", "http/1.0", "http/2.0" ]; sni_hostnames: [ "host_b.acme" ]; log_level: 0; tfo_ok: true },
     { name: "tls"; host: "192.168.0.3"; port: "443"; alpn_protocols: [ "http/1.1", "http/1.0", "http/2.0" ]; sni_hostnames: [ "host_c.acme" ]; log_level: 0; tfo_ok: true },
     { name: "tls"; host: "192.168.0.3"; port: "443"; alpn_protocols: [ "http/1.1", "http/1.0", "http/2.0" ]; sni_hostnames: [ "host_c_1.acme" ]; log_level: 0; tfo_ok: true },
);

Ja kenn ich, hab ich früher verwendet um SSH/TLS aufm gleichen port zu machen. War damals zumindest ultra segfaulty und will ich eigendlich nimma verwenden.

Wenn nginx genau das kann wiso sollt ich dann noch nen server? :slight_smile:

Soweit ich versteh hat dein setup dann aber eben genau das problem dass ich hier mit dem PROXY zeug gelöst hab, dass es für die services hinter sslh so ausschaut als würden die connections vom sslh server kommen statt von der client IP. Das is halt kake.

Bei mir tut der sslh brav und der kann auch transparent weiterleiten (was ich aber nicht verwende). Wollte auch nur darauf hinweisen als alternative zum nginx.

Solange es tut, ist ja wurscht was hergenommen wird :slight_smile:

4 posts were merged into an existing topic: Dokumentation im Discourse

das heisst also bei gelegenheit mal le direkt am web und gitea einrichten?