feat(host-agent): RVS_SNI — auf interne IP verbinden, aber Cert-Hostname im TLS
Fuer Agenten im selben Netz wie der RVS: direkt auf die interne IP verbinden (kein NAT-Hairpin ueber den externen Hostnamen), aber im TLS-Handshake den Namen praesentieren, fuer den das Caddy-Zertifikat gilt. Ohne das schickt der Client die IP als SNI -> Caddy findet kein Cert -> 'tlsv1 alert internal error'. RVS_HOST=<interne-ip> + RVS_SNI=<zert-name>. server_hostname im ssl-Context. Leeres RVS_SNI = Verhalten wie bisher. Robuster als /etc/hosts (ueberlebt Reboots, wichtig auf Live-ISOs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,12 @@ RVS_HOST=rvs.example.de
|
|||||||
RVS_PORT=443
|
RVS_PORT=443
|
||||||
RVS_TLS=true
|
RVS_TLS=true
|
||||||
RVS_TOKEN=
|
RVS_TOKEN=
|
||||||
|
# RVS_SNI: nur noetig, wenn RVS_HOST eine IP ist (Agent im selben Netz wie der
|
||||||
|
# RVS, direkt auf die interne IP). Dann hier den Zertifikats-/Hostnamen angeben,
|
||||||
|
# damit der TLS-Handshake (SNI) passt. Sonst leer lassen.
|
||||||
|
# RVS_HOST=172.0.2.34
|
||||||
|
# RVS_SNI=mobil.hacker-net.de
|
||||||
|
RVS_SNI=
|
||||||
|
|
||||||
# ─── Identitaet dieses Rechners ────────────────────────────────────
|
# ─── Identitaet dieses Rechners ────────────────────────────────────
|
||||||
# HOST_ID = technisch eindeutig (a-z0-9-_), Default = Hostname-Slug.
|
# HOST_ID = technisch eindeutig (a-z0-9-_), Default = Hostname-Slug.
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ RVS_HOST = os.environ.get("RVS_HOST", "")
|
|||||||
RVS_PORT = int(os.environ.get("RVS_PORT", "443") or "443")
|
RVS_PORT = int(os.environ.get("RVS_PORT", "443") or "443")
|
||||||
RVS_TLS = _env_bool("RVS_TLS", True)
|
RVS_TLS = _env_bool("RVS_TLS", True)
|
||||||
RVS_TOKEN = os.environ.get("RVS_TOKEN", "")
|
RVS_TOKEN = os.environ.get("RVS_TOKEN", "")
|
||||||
|
# TLS-Hostname (SNI + Zertifikatspruefung), falls RVS_HOST eine IP ist — z.B. der
|
||||||
|
# Agent laeuft im selben Netz wie der RVS und verbindet direkt auf die interne IP,
|
||||||
|
# das Caddy-Zertifikat gilt aber fuer den Namen. Dann: RVS_HOST=<interne-ip>,
|
||||||
|
# RVS_SNI=<zert-name>. Leer = SNI = RVS_HOST (Normalfall).
|
||||||
|
RVS_SNI = os.environ.get("RVS_SNI", "").strip()
|
||||||
|
|
||||||
HOST_ID = (os.environ.get("HOST_ID") or _default_id()).strip()
|
HOST_ID = (os.environ.get("HOST_ID") or _default_id()).strip()
|
||||||
HOST_NAME = (os.environ.get("HOST_NAME") or HOST_ID).strip()
|
HOST_NAME = (os.environ.get("HOST_NAME") or HOST_ID).strip()
|
||||||
@@ -400,13 +405,21 @@ class HostAgent:
|
|||||||
logger.error("RVS_HOST und RVS_TOKEN sind Pflicht (siehe .env.example).")
|
logger.error("RVS_HOST und RVS_TOKEN sind Pflicht (siehe .env.example).")
|
||||||
return
|
return
|
||||||
backoff = 1
|
backoff = 1
|
||||||
|
# SNI-/Cert-Override: auf eine IP verbinden, aber den Namen im TLS-
|
||||||
|
# Handshake praesentieren (Caddy-Cert gilt fuer den Namen).
|
||||||
|
connect_kwargs = {"max_size": 16 * 1024 * 1024,
|
||||||
|
"ping_interval": 20, "ping_timeout": 20}
|
||||||
|
if RVS_TLS and RVS_SNI:
|
||||||
|
import ssl
|
||||||
|
connect_kwargs["ssl"] = ssl.create_default_context()
|
||||||
|
connect_kwargs["server_hostname"] = RVS_SNI
|
||||||
while True:
|
while True:
|
||||||
proto = "wss" if RVS_TLS else "ws"
|
proto = "wss" if RVS_TLS else "ws"
|
||||||
url = f"{proto}://{RVS_HOST}:{RVS_PORT}?token={RVS_TOKEN}"
|
url = f"{proto}://{RVS_HOST}:{RVS_PORT}?token={RVS_TOKEN}"
|
||||||
try:
|
try:
|
||||||
logger.info("Verbinde mit RVS %s://%s:%s …", proto, RVS_HOST, RVS_PORT)
|
logger.info("Verbinde mit RVS %s://%s:%s%s …", proto, RVS_HOST, RVS_PORT,
|
||||||
async with websockets.connect(url, max_size=16 * 1024 * 1024,
|
f" (SNI {RVS_SNI})" if RVS_SNI else "")
|
||||||
ping_interval=20, ping_timeout=20) as ws:
|
async with websockets.connect(url, **connect_kwargs) as ws:
|
||||||
self.ws = ws
|
self.ws = ws
|
||||||
backoff = 1
|
backoff = 1
|
||||||
await self._hello(log=True)
|
await self._hello(log=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user