ops: add guest iot network preflight
This commit is contained in:
@@ -0,0 +1,99 @@
|
|||||||
|
# Guest / IoT Network Runbook
|
||||||
|
|
||||||
|
Stand: 2026-06-06
|
||||||
|
|
||||||
|
Dieses Runbook beschreibt den sicheren Weg, das FRITZ!Box-Gastnetz zu aktivieren,
|
||||||
|
ohne versehentlich Homelab-Admin-Ports aus dem Gastsegment erreichbar zu machen.
|
||||||
|
|
||||||
|
## Zielbild
|
||||||
|
|
||||||
|
- Normales LAN bleibt `192.168.178.0/24`.
|
||||||
|
- Kallilabcore bleibt im normalen LAN unter `192.168.178.58`.
|
||||||
|
- FRITZ!Box-Gast-WLAN darf Internetzugang haben, aber keinen Zugriff auf
|
||||||
|
`192.168.178.0/24`.
|
||||||
|
- Homelab-Admin-Pfade bleiben Operator-only:
|
||||||
|
- Tailscale fuer Admin-Zugriff
|
||||||
|
- Authelia/2FA fuer geschuetzte Web-UIs
|
||||||
|
- keine LAN-Admin-Ports aus dem Gastnetz
|
||||||
|
|
||||||
|
## Vorbedingungen
|
||||||
|
|
||||||
|
Vor dem Einschalten des Gast-WLANs muessen diese Preflights gruen sein:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
G:\Gitea_Clone\homelab-infra\ops\maintenance\check-guest-iot-isolation.ps1 -Mode LanPreflight
|
||||||
|
```
|
||||||
|
|
||||||
|
Erwartung im normalen LAN:
|
||||||
|
|
||||||
|
- `192.168.178.58:8082` ist blockiert (AdGuard Admin nur Tailscale).
|
||||||
|
- `192.168.178.58:8181` ist blockiert (InfluxDB nicht LAN-exponiert).
|
||||||
|
- `192.168.178.58:80`, `443`, `222` koennen im normalen LAN erreichbar sein.
|
||||||
|
|
||||||
|
Auf Unraid zusaetzlich:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/mnt/user/services/homelab-infra/ops/maintenance/check-guest-iot-preflight.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## FRITZ!Box Schritte
|
||||||
|
|
||||||
|
In der FRITZ!Box UI:
|
||||||
|
|
||||||
|
1. `WLAN -> Gastzugang` oeffnen.
|
||||||
|
2. `Gastzugang aktiv` einschalten.
|
||||||
|
3. WPA2/WPA3-Verschluesselung aktiv lassen.
|
||||||
|
4. Eigenen Gast-SSID-Namen setzen, z. B. `Fritzi-Gast`.
|
||||||
|
5. Starkes Passwort setzen und in Vaultwarden ablegen.
|
||||||
|
6. Option `Geraete im Gastnetz duerfen miteinander kommunizieren` deaktiviert
|
||||||
|
lassen, sofern nicht bewusst gebraucht.
|
||||||
|
7. Option fuer Zugriff auf das Heimnetz / private Netzwerk deaktiviert lassen.
|
||||||
|
8. Gastzugang speichern.
|
||||||
|
|
||||||
|
Wichtig: Die genaue FRITZ!OS-8.25-UI-Beschriftung kann leicht variieren. Der
|
||||||
|
entscheidende Punkt ist: Gastgeraete duerfen keinen Zugriff auf das Heimnetz
|
||||||
|
haben.
|
||||||
|
|
||||||
|
## Verifikation
|
||||||
|
|
||||||
|
Ein Handy oder Laptop mit dem Gast-WLAN verbinden, dann auf diesem Geraet testen:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
G:\Gitea_Clone\homelab-infra\ops\maintenance\check-guest-iot-isolation.ps1 -Mode Guest
|
||||||
|
```
|
||||||
|
|
||||||
|
Erwartung aus dem Gast-WLAN:
|
||||||
|
|
||||||
|
- `192.168.178.58:80` blockiert
|
||||||
|
- `192.168.178.58:443` blockiert
|
||||||
|
- `192.168.178.58:222` blockiert
|
||||||
|
- `192.168.178.58:8082` blockiert
|
||||||
|
- `192.168.178.58:8181` blockiert
|
||||||
|
- `192.168.178.1:80` blockiert oder nur Gast-Gateway-Ansicht
|
||||||
|
|
||||||
|
Wenn der Test `Risk count: 0` meldet, ist die Isolation fuer die getesteten
|
||||||
|
Homelab-Admin-Pfade ausreichend.
|
||||||
|
|
||||||
|
## Betrieb
|
||||||
|
|
||||||
|
- Familien-/Gaestegeraete kommen ins Gast-WLAN, wenn sie keinen direkten Zugriff
|
||||||
|
auf LAN-Geraete brauchen.
|
||||||
|
- Homelab-Apps fuer Familie laufen perspektivisch ueber HTTPS/OIDC, nicht ueber
|
||||||
|
direkten LAN-Zugriff.
|
||||||
|
- Geraete, die lokale Discovery brauchen (z. B. manche Smart-TV/Plex-Szenarien),
|
||||||
|
bleiben im normalen LAN oder bekommen eine separate bewusste Entscheidung.
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
Wenn nach Aktivierung etwas Unerwartetes passiert:
|
||||||
|
|
||||||
|
1. FRITZ!Box: `WLAN -> Gastzugang` oeffnen.
|
||||||
|
2. Gastzugang deaktivieren.
|
||||||
|
3. Speichern.
|
||||||
|
4. Normalen LAN-Zugriff pruefen:
|
||||||
|
```powershell
|
||||||
|
G:\Gitea_Clone\homelab-infra\ops\maintenance\check-guest-iot-isolation.ps1 -Mode LanPreflight
|
||||||
|
```
|
||||||
|
|
||||||
|
Es werden durch dieses Runbook keine Docker-Stacks, Secrets oder produktiven
|
||||||
|
Appdaten veraendert.
|
||||||
+1
-1
@@ -27,7 +27,7 @@ Host-/Entscheidungsaufgaben beim **Operator**.
|
|||||||
| Restore-Test Unraid OS Flash (Stick-Boot) | Operator | Artefakt-Validierung am 2026-06-05 erledigt (`ops/maintenance/check-unraid-flash-backup.sh`, sha256 OK, 8 Kern-Configs). **Verbleibt:** physischer Ersatzstick-Boot-Test, wenn ein Wegwerf-Stick bereitliegt | `docs/RESTORE_MATRIX.md` Abschnitt "Unraid OS Flash" |
|
| Restore-Test Unraid OS Flash (Stick-Boot) | Operator | Artefakt-Validierung am 2026-06-05 erledigt (`ops/maintenance/check-unraid-flash-backup.sh`, sha256 OK, 8 Kern-Configs). **Verbleibt:** physischer Ersatzstick-Boot-Test, wenn ein Wegwerf-Stick bereitliegt | `docs/RESTORE_MATRIX.md` Abschnitt "Unraid OS Flash" |
|
||||||
| Restore-Test Tailscale | Operator | Runbook-Stub abarbeiten: State-Validierung + Reconnect nur auf Wegwerf-Host/VM, danach Geraet in Tailscale-Admin entfernen | `docs/RESTORE_MATRIX.md` Abschnitt "Tailscale" |
|
| Restore-Test Tailscale | Operator | Runbook-Stub abarbeiten: State-Validierung + Reconnect nur auf Wegwerf-Host/VM, danach Geraet in Tailscale-Admin entfernen | `docs/RESTORE_MATRIX.md` Abschnitt "Tailscale" |
|
||||||
| Authelia OIDC fuer Apps | Operator/Claude | **Plan + Runbook erstellt 2026-06-06** (`docs/AUTHELIA_OIDC_PLAN.md`): v4.39-Client-Schema, Issuer/Endpoints, Secret-Erzeugung, Rollout-Reihenfolge. **Naechster konkreter Schritt:** Stufe-1-Proof **Grafana** (`monitoring`) ausrollen — Authelia-Client + `GF_AUTH_GENERIC_OAUTH_*`; lokaler Grafana-Admin bleibt Fallback (kein Lockout). Danach Familien-Apps (Immich/Nextcloud/Mealie/Paperless) | `docs/AUTHELIA_OIDC_PLAN.md`, `security/authelia/configuration.yml` |
|
| Authelia OIDC fuer Apps | Operator/Claude | **Plan + Runbook erstellt 2026-06-06** (`docs/AUTHELIA_OIDC_PLAN.md`): v4.39-Client-Schema, Issuer/Endpoints, Secret-Erzeugung, Rollout-Reihenfolge. **Naechster konkreter Schritt:** Stufe-1-Proof **Grafana** (`monitoring`) ausrollen — Authelia-Client + `GF_AUTH_GENERIC_OAUTH_*`; lokaler Grafana-Admin bleibt Fallback (kein Lockout). Danach Familien-Apps (Immich/Nextcloud/Mealie/Paperless) | `docs/AUTHELIA_OIDC_PLAN.md`, `security/authelia/configuration.yml` |
|
||||||
| Gast-/IoT-Netz einrichten | Operator | Entscheidung 2026-06-06: **aktivieren/planen.** Reihenfolge: (1) **zuerst** LAN-Admin-Ports (`192.168.178.58:8082` AdGuard, weitere) per FRITZ!Box-Netzwerkfilter/Kindersicherung gegen das Gastsegment sperren, (2) dann Gast-WLAN/IoT in der FRITZ!Box aktivieren, (3) Trennung verifizieren (Gastgeraet darf LAN-Admin nicht erreichen) | `docs/NETWORK_INVENTORY.md` |
|
| Gast-/IoT-Netz einrichten | Operator/Codex | **Preflight erledigt, Operator-UI-Schritt offen.** Runbook `docs/GUEST_IOT_NETWORK.md` und Checks vorhanden. LAN-Preflight von `baerchen` gruen (`8082`/`8181` blockiert). Naechster Schritt: FRITZ!Box-Gastzugang aktivieren, Heimnetz-Zugriff im Gastnetz deaktiviert lassen, danach von einem Gast-WLAN-Geraet `ops/maintenance/check-guest-iot-isolation.ps1 -Mode Guest` ausfuehren | `docs/GUEST_IOT_NETWORK.md`, `docs/NETWORK_INVENTORY.md` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ docker network inspect backend_net | jq '.[0].Internal'
|
|||||||
| FRITZ!Box-Portfreigaben mit Repo-Soll abgleichen | **erledigt 2026-06-01** | Bereinigt: `80/tcp` entfernt (Cloudflare-DNS-Challenge ersetzt HTTP-01; Mobilfunk-Test bestaetigt Timeout auf `http://`, `https://` weiter ok). `222/tcp` bleibt bewusst nicht eingerichtet (Tailscale-only-Linie). UPnP-Selbstfreigaben sind aus. Aktiver Soll-Stand: ausschliesslich `443/tcp -> 192.168.178.58`. |
|
| FRITZ!Box-Portfreigaben mit Repo-Soll abgleichen | **erledigt 2026-06-01** | Bereinigt: `80/tcp` entfernt (Cloudflare-DNS-Challenge ersetzt HTTP-01; Mobilfunk-Test bestaetigt Timeout auf `http://`, `https://` weiter ok). `222/tcp` bleibt bewusst nicht eingerichtet (Tailscale-only-Linie). UPnP-Selbstfreigaben sind aus. Aktiver Soll-Stand: ausschliesslich `443/tcp -> 192.168.178.58`. |
|
||||||
| FRITZ!Box-Dienste aus dem Internet | **erledigt 2026-06-01** | `Internet -> Freigaben -> FRITZ!Box-Dienste`: HTTPS-Zugriff auf die FRITZ!Box aus dem Internet aus; FTP/FTPS auf Speichermedien aus. |
|
| FRITZ!Box-Dienste aus dem Internet | **erledigt 2026-06-01** | `Internet -> Freigaben -> FRITZ!Box-Dienste`: HTTPS-Zugriff auf die FRITZ!Box aus dem Internet aus; FTP/FTPS auf Speichermedien aus. |
|
||||||
| FRITZ!OS Update und Konfig-Backup | **erledigt 2026-06-01** | TR-064 meldet `154.08.25`; Konfig-Export liegt extern/off-system in Vaultwarden, Kennwort und Datei bleiben ausserhalb des Repos. |
|
| FRITZ!OS Update und Konfig-Backup | **erledigt 2026-06-01** | TR-064 meldet `154.08.25`; Konfig-Export liegt extern/off-system in Vaultwarden, Kennwort und Datei bleiben ausserhalb des Repos. |
|
||||||
| Gast-/IoT-Zugriff auf Admin-Ports | **Entscheidungspunkt: kein Gast-/IoT-Netz aktivieren, solange nicht gebraucht** | Aktuell entschaerft, weil Gast-WLAN inaktiv ist und kein IoT-VLAN existiert. Risiko entsteht erst bei Aktivierung. Harte Vorbedingung fuer eine spaetere Aktivierung: **vor** dem Einschalten von Gast-WLAN/IoT muessen `192.168.178.58:8082` (AdGuard-Admin, ohnehin Tailscale-gebunden), `192.168.178.58:8181` (InfluxDB, bereits `127.0.0.1`-bound) und alle weiteren LAN-Admin-Ports per FRITZ!Box-Netzwerkfilter/Kindersicherung gegen das Gastsegment gesperrt sein. Bis dahin bewusst kein Gastnetz. |
|
| Gast-/IoT-Zugriff auf Admin-Ports | **Preflight vorbereitet 2026-06-06** | Runbook `docs/GUEST_IOT_NETWORK.md` und Checks `ops/maintenance/check-guest-iot-isolation.ps1` sowie `ops/maintenance/check-guest-iot-preflight.sh` vorhanden. LAN-Preflight von `baerchen` am 2026-06-06 gruen: `192.168.178.58:8082` und `:8181` blockiert. Naechster Schritt: FRITZ!Box-Gastzugang aktivieren, Heimnetz-Zugriff deaktiviert lassen, danach `check-guest-iot-isolation.ps1 -Mode Guest` aus dem Gast-WLAN fahren. |
|
||||||
| IPv6 Exposure | technisch und per UI entschaerft | Public DNS liefert keine AAAA-Records fuer `*.kaleschke.info`; Host hat keine globale Provider-IPv6. TR-064 meldet IPv6-Firewall aktiv und Pinholes grundsaetzlich erlaubt; FRITZ!Box-UI zeigt keine aktiven IPv6-Freigaben, keine Admin-/SSH-Freigaben. |
|
| IPv6 Exposure | technisch und per UI entschaerft | Public DNS liefert keine AAAA-Records fuer `*.kaleschke.info`; Host hat keine globale Provider-IPv6. TR-064 meldet IPv6-Firewall aktiv und Pinholes grundsaetzlich erlaubt; FRITZ!Box-UI zeigt keine aktiven IPv6-Freigaben, keine Admin-/SSH-Freigaben. |
|
||||||
| WAN-Ausfallschutz | **geparkt: spaeter evaluieren** (Operator-Entscheidung 2026-06-05) | Mobilfunk-Stick-Failover an FRITZ!Box bleibt vorerst inaktiv. Folgen sind bewusst akzeptiert: Internet-Ausfall = ACME/DDNS pausieren, lokale Apps laufen weiter. Review-Trigger: haeufigere oder laengere DSL-Ausfaelle, oder wenn externer Remote-Zugang (statt nur lokalem Betrieb) geschaeftskritisch wird. Erst dann Mobilfunk-Failover technisch bewerten. |
|
| WAN-Ausfallschutz | **geparkt: spaeter evaluieren** (Operator-Entscheidung 2026-06-05) | Mobilfunk-Stick-Failover an FRITZ!Box bleibt vorerst inaktiv. Folgen sind bewusst akzeptiert: Internet-Ausfall = ACME/DDNS pausieren, lokale Apps laufen weiter. Review-Trigger: haeufigere oder laengere DSL-Ausfaelle, oder wenn externer Remote-Zugang (statt nur lokalem Betrieb) geschaeftskritisch wird. Erst dann Mobilfunk-Failover technisch bewerten. |
|
||||||
| Home Assistant InfluxDB Bind | validiert 2026-05-31 | `docker-proxy` bindet `127.0.0.1:8181`; keine LAN-Exposure. Wenn Home Assistant nicht lokal auf dem Host schreibt, braucht das eine bewusste Bind-Aenderung. |
|
| Home Assistant InfluxDB Bind | validiert 2026-05-31 | `docker-proxy` bindet `127.0.0.1:8181`; keine LAN-Exposure. Wenn Home Assistant nicht lokal auf dem Host schreibt, braucht das eine bewusste Bind-Aenderung. |
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ Diese Datei trennt aktive Betriebsdokumentation von historischer Arbeitsdoku. Ne
|
|||||||
| `AUTHELIA_OIDC_PLAN.md` | Plan & Runbook fuer app-uebergreifendes SSO via Authelia OIDC |
|
| `AUTHELIA_OIDC_PLAN.md` | Plan & Runbook fuer app-uebergreifendes SSO via Authelia OIDC |
|
||||||
| `HARDWARE_INVENTORY.md` | Host-, Disk-, SMART-, USV- und Power-Baseline |
|
| `HARDWARE_INVENTORY.md` | Host-, Disk-, SMART-, USV- und Power-Baseline |
|
||||||
| `NETWORK_INVENTORY.md` | Router, DNS, Tailscale, Portfreigaben und Netzthemen |
|
| `NETWORK_INVENTORY.md` | Router, DNS, Tailscale, Portfreigaben und Netzthemen |
|
||||||
|
| `GUEST_IOT_NETWORK.md` | Sicherer Ablauf fuer FRITZ!Box-Gastnetz / IoT-Isolation |
|
||||||
| `EXTERNAL_DEPENDENCIES.md` | Provider, Konten und externe Abhaengigkeiten |
|
| `EXTERNAL_DEPENDENCIES.md` | Provider, Konten und externe Abhaengigkeiten |
|
||||||
| `EXTERNAL_OPERATOR_RUNBOOK.md` | Hetzner-/Borg-/FRITZ!Box-Betreibercheck |
|
| `EXTERNAL_OPERATOR_RUNBOOK.md` | Hetzner-/Borg-/FRITZ!Box-Betreibercheck |
|
||||||
| `CAPACITY_AND_LIFECYCLE.md` | Kapazitaet, Wachstum und Upgrade-Trigger |
|
| `CAPACITY_AND_LIFECYCLE.md` | Kapazitaet, Wachstum und Upgrade-Trigger |
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
param(
|
||||||
|
[string]$HostLanIp = "192.168.178.58",
|
||||||
|
[string]$FritzBoxIp = "192.168.178.1",
|
||||||
|
[ValidateSet("LanPreflight", "Guest")]
|
||||||
|
[string]$Mode = "LanPreflight",
|
||||||
|
[string]$ReportPath = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Test-TcpPort {
|
||||||
|
param(
|
||||||
|
[string]$RemoteHost,
|
||||||
|
[int]$Port,
|
||||||
|
[int]$TimeoutMs = 1500
|
||||||
|
)
|
||||||
|
|
||||||
|
$client = [System.Net.Sockets.TcpClient]::new()
|
||||||
|
try {
|
||||||
|
$async = $client.BeginConnect($RemoteHost, $Port, $null, $null)
|
||||||
|
$ok = $async.AsyncWaitHandle.WaitOne($TimeoutMs, $false)
|
||||||
|
if (-not $ok) {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
$client.EndConnect($async)
|
||||||
|
return $true
|
||||||
|
} catch {
|
||||||
|
return $false
|
||||||
|
} finally {
|
||||||
|
$client.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Add-Result {
|
||||||
|
param(
|
||||||
|
[System.Collections.Generic.List[object]]$Results,
|
||||||
|
[string]$Name,
|
||||||
|
[string]$Target,
|
||||||
|
[bool]$Reachable,
|
||||||
|
[string]$ExpectedGuest,
|
||||||
|
[string]$Risk
|
||||||
|
)
|
||||||
|
|
||||||
|
$Results.Add([pscustomobject]@{
|
||||||
|
Name = $Name
|
||||||
|
Target = $Target
|
||||||
|
Reachable = $Reachable
|
||||||
|
ExpectedFromGuest = $ExpectedGuest
|
||||||
|
RiskIfReachableFromGuest = $Risk
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$adapters = Get-NetIPConfiguration |
|
||||||
|
Where-Object { $_.IPv4Address -and $_.NetAdapter.Status -eq "Up" } |
|
||||||
|
Select-Object InterfaceAlias,
|
||||||
|
@{Name="IPv4";Expression={$_.IPv4Address.IPAddress -join ", "}},
|
||||||
|
@{Name="Gateway";Expression={$_.IPv4DefaultGateway.NextHop -join ", "}},
|
||||||
|
@{Name="DnsServer";Expression={$_.DNSServer.ServerAddresses -join ", "}}
|
||||||
|
|
||||||
|
$results = [System.Collections.Generic.List[object]]::new()
|
||||||
|
|
||||||
|
Add-Result $results "Unraid HTTP/LAN" "${HostLanIp}:80" (Test-TcpPort $HostLanIp 80) "blocked" "Guest can reach LAN web entrypoint directly"
|
||||||
|
Add-Result $results "Unraid HTTPS/LAN" "${HostLanIp}:443" (Test-TcpPort $HostLanIp 443) "blocked" "Guest can reach LAN HTTPS entrypoint directly"
|
||||||
|
Add-Result $results "Gitea SSH/LAN" "${HostLanIp}:222" (Test-TcpPort $HostLanIp 222) "blocked" "Guest can reach Git SSH"
|
||||||
|
Add-Result $results "AdGuard Admin/LAN" "${HostLanIp}:8082" (Test-TcpPort $HostLanIp 8082) "blocked" "Guest can reach AdGuard admin UI"
|
||||||
|
Add-Result $results "InfluxDB LAN" "${HostLanIp}:8181" (Test-TcpPort $HostLanIp 8181) "blocked" "Guest can reach InfluxDB writer endpoint"
|
||||||
|
Add-Result $results "FRITZ!Box LAN UI" "${FritzBoxIp}:80" (Test-TcpPort $FritzBoxIp 80) "blocked-or-guest-gateway-only" "Guest can reach main router UI"
|
||||||
|
|
||||||
|
$risk = if ($Mode -eq "Guest") {
|
||||||
|
$results | Where-Object {
|
||||||
|
$_.ExpectedFromGuest -like "blocked*" -and $_.Reachable
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$results | Where-Object {
|
||||||
|
$_.Name -in @("AdGuard Admin/LAN", "InfluxDB LAN") -and $_.Reachable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||||
|
$lines = [System.Collections.Generic.List[string]]::new()
|
||||||
|
$lines.Add("# Guest/IoT Isolation Check")
|
||||||
|
$lines.Add("")
|
||||||
|
$lines.Add("Timestamp: $timestamp")
|
||||||
|
$lines.Add("Mode: $Mode")
|
||||||
|
$lines.Add("Host LAN IP: $HostLanIp")
|
||||||
|
$lines.Add("FRITZ!Box IP: $FritzBoxIp")
|
||||||
|
$lines.Add("Risk count: $($risk.Count)")
|
||||||
|
$lines.Add("")
|
||||||
|
$lines.Add("## Active Network Adapters")
|
||||||
|
$lines.Add("")
|
||||||
|
$lines.Add("| Interface | IPv4 | Gateway | DNS |")
|
||||||
|
$lines.Add("|---|---|---|---|")
|
||||||
|
foreach ($adapter in $adapters) {
|
||||||
|
$lines.Add("| $($adapter.InterfaceAlias) | $($adapter.IPv4) | $($adapter.Gateway) | $($adapter.DnsServer) |")
|
||||||
|
}
|
||||||
|
$lines.Add("")
|
||||||
|
$lines.Add("## Port Tests")
|
||||||
|
$lines.Add("")
|
||||||
|
$lines.Add("| Name | Target | Reachable | Expected from guest Wi-Fi | Risk if reachable from guest |")
|
||||||
|
$lines.Add("|---|---|---:|---|---|")
|
||||||
|
foreach ($result in $results) {
|
||||||
|
$lines.Add("| $($result.Name) | $($result.Target) | $($result.Reachable) | $($result.ExpectedFromGuest) | $($result.RiskIfReachableFromGuest) |")
|
||||||
|
}
|
||||||
|
$lines.Add("")
|
||||||
|
$lines.Add("## Interpretation")
|
||||||
|
$lines.Add("")
|
||||||
|
$lines.Add("- `LanPreflight`: reachable `80/443/222` can be normal; `8082` and `8181` should still be blocked.")
|
||||||
|
$lines.Add("- `Guest`: all listed LAN targets should be blocked. Public domains may still work via the internet path.")
|
||||||
|
$lines.Add("- A non-zero risk count means the selected mode failed.")
|
||||||
|
|
||||||
|
$text = $lines -join [Environment]::NewLine
|
||||||
|
|
||||||
|
if ($ReportPath) {
|
||||||
|
$parent = Split-Path -Parent $ReportPath
|
||||||
|
if ($parent) {
|
||||||
|
New-Item -ItemType Directory -Force -Path $parent | Out-Null
|
||||||
|
}
|
||||||
|
Set-Content -Path $ReportPath -Value $text -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output $text
|
||||||
|
|
||||||
|
if ($risk.Count -gt 0) {
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+90
@@ -0,0 +1,90 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HOST_LAN_IP="${HOST_LAN_IP:-192.168.178.58}"
|
||||||
|
TAILSCALE_IP="${TAILSCALE_IP:-100.80.98.33}"
|
||||||
|
FRITZBOX_TR064_URL="${FRITZBOX_TR064_URL:-http://192.168.178.1:49000/tr64desc.xml}"
|
||||||
|
REPORT_ROOT="${REPORT_ROOT:-/mnt/user/backups/restore-reports}"
|
||||||
|
STAMP="$(date +%F-%H%M%S)"
|
||||||
|
REPORT_FILE="$REPORT_ROOT/guest-iot-preflight-$STAMP.md"
|
||||||
|
|
||||||
|
mkdir -p "$REPORT_ROOT"
|
||||||
|
|
||||||
|
tcp_check() {
|
||||||
|
local host="$1"
|
||||||
|
local port="$2"
|
||||||
|
timeout 2 bash -c "cat < /dev/null > /dev/tcp/$host/$port" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
result_row() {
|
||||||
|
local name="$1"
|
||||||
|
local target="$2"
|
||||||
|
local expectation="$3"
|
||||||
|
local status="$4"
|
||||||
|
printf '| %s | `%s` | %s | %s |\n' "$name" "$target" "$status" "$expectation"
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# Guest/IoT Preflight"
|
||||||
|
echo
|
||||||
|
echo "Timestamp: $(date '+%F %T')"
|
||||||
|
echo "Scope: host-side read-only checks before enabling FRITZ!Box guest/IoT network"
|
||||||
|
echo
|
||||||
|
echo "## FRITZ!Box TR-064"
|
||||||
|
echo
|
||||||
|
if curl -fsS --max-time 5 "$FRITZBOX_TR064_URL" >/tmp/guest-iot-fritzbox-tr064.xml 2>/dev/null; then
|
||||||
|
model="$(grep -o '<friendlyName>[^<]*' /tmp/guest-iot-fritzbox-tr064.xml | head -n1 | sed 's/<friendlyName>//')"
|
||||||
|
echo "- TR-064 descriptor reachable: yes"
|
||||||
|
echo "- Model: ${model:-unknown}"
|
||||||
|
else
|
||||||
|
echo "- TR-064 descriptor reachable: no"
|
||||||
|
fi
|
||||||
|
rm -f /tmp/guest-iot-fritzbox-tr064.xml
|
||||||
|
echo
|
||||||
|
echo "## Host listeners"
|
||||||
|
echo
|
||||||
|
echo '```text'
|
||||||
|
ss -ltnp | sort -k4 | grep -E ':(53|80|443|222|8082|8181)[[:space:]]' || true
|
||||||
|
echo '```'
|
||||||
|
echo
|
||||||
|
echo "## Port reachability from host namespace"
|
||||||
|
echo
|
||||||
|
echo "| Check | Target | Status | Expectation |"
|
||||||
|
echo "|---|---|---|---|"
|
||||||
|
|
||||||
|
for port in 80 443 222 53; do
|
||||||
|
if tcp_check "$HOST_LAN_IP" "$port"; then
|
||||||
|
result_row "LAN service" "$HOST_LAN_IP:$port" "may be reachable from normal LAN; must be blocked from guest Wi-Fi" "reachable"
|
||||||
|
else
|
||||||
|
result_row "LAN service" "$HOST_LAN_IP:$port" "not reachable from host namespace or UDP-only" "blocked"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if tcp_check "$HOST_LAN_IP" 8082; then
|
||||||
|
result_row "AdGuard Admin via LAN IP" "$HOST_LAN_IP:8082" "should be blocked" "reachable"
|
||||||
|
else
|
||||||
|
result_row "AdGuard Admin via LAN IP" "$HOST_LAN_IP:8082" "should be blocked" "blocked"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if tcp_check "$TAILSCALE_IP" 8082; then
|
||||||
|
result_row "AdGuard Admin via Tailscale IP" "$TAILSCALE_IP:8082" "operator path should work" "reachable"
|
||||||
|
else
|
||||||
|
result_row "AdGuard Admin via Tailscale IP" "$TAILSCALE_IP:8082" "operator path should work" "blocked"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if tcp_check "$HOST_LAN_IP" 8181; then
|
||||||
|
result_row "InfluxDB via LAN IP" "$HOST_LAN_IP:8181" "should be blocked unless HA LAN writer is reintroduced" "reachable"
|
||||||
|
else
|
||||||
|
result_row "InfluxDB via LAN IP" "$HOST_LAN_IP:8181" "should be blocked unless HA LAN writer is reintroduced" "blocked"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
echo "## Next operator step"
|
||||||
|
echo
|
||||||
|
echo "Enable FRITZ!Box guest Wi-Fi only after confirming LAN isolation is active. Then connect a phone/laptop to guest Wi-Fi and run:"
|
||||||
|
echo
|
||||||
|
echo '```powershell'
|
||||||
|
echo 'G:\Gitea_Clone\homelab-infra\ops\maintenance\check-guest-iot-isolation.ps1'
|
||||||
|
echo '```'
|
||||||
|
} | tee "$REPORT_FILE"
|
||||||
|
|
||||||
|
echo "Guest/IoT preflight report: $REPORT_FILE"
|
||||||
Reference in New Issue
Block a user