diff --git a/automation/bin/unifi_stage_policy_engine_firewall.py b/automation/bin/unifi_stage_policy_engine_firewall.py index 46182d9..4810fbd 100644 --- a/automation/bin/unifi_stage_policy_engine_firewall.py +++ b/automation/bin/unifi_stage_policy_engine_firewall.py @@ -33,6 +33,7 @@ def load_helper(path: Path): def desired_policies() -> list[dict[str, Any]]: + restricted_subnets = ['10.5.10.0/24', '10.5.20.0/24', '192.168.1.0/24'] return [ { 'name': 'Allow Internal to Untrusted', @@ -76,14 +77,14 @@ def desired_policies() -> list[dict[str, Any]]: 'match_opposite_protocol': False, 'icmp_typename': 'ANY', 'icmp_v6_typename': 'ANY', - 'description': 'Block IoT, Camera, and Old IoT clients from UniFi gateway admin TCP ports while preserving existing zone defaults for DHCP, DNS, mDNS, and internet access.', + 'description': 'Block IoT, Camera, and Old IoT clients from UniFi gateway admin TCP ports while preserving explicit DHCP and mDNS exceptions plus external DNS via 10.5.30.53.', 'source_zone_name': 'Untrusted', 'destination_zone_name': 'Gateway', 'schedule': {'mode': 'ALWAYS'}, 'source': { 'matching_target': 'IP', 'matching_target_type': 'SPECIFIC', - 'ips': ['10.5.10.0/24', '10.5.20.0/24', '192.168.1.0/24'], + 'ips': restricted_subnets, 'match_mac': False, 'match_opposite_ips': False, 'match_opposite_ports': False, @@ -99,6 +100,149 @@ def desired_policies() -> list[dict[str, Any]]: 'port': '22,80,443,8443,9443', }, }, + { + 'name': 'Allow Untrusted to DNS Resolver', + 'enabled': True, + 'action': 'ALLOW', + 'protocol': 'tcp_udp', + 'ip_version': 'IPV4', + 'logging': False, + 'create_allow_respond': False, + 'connection_state_type': 'ALL', + 'connection_states': [], + 'match_ip_sec': False, + 'match_opposite_protocol': False, + 'icmp_typename': 'ANY', + 'icmp_v6_typename': 'ANY', + 'description': 'Allow IoT, Camera, and Old IoT clients to reach the Pi-hole DNS resolver on PD after DHCP DNS cutover.', + 'source_zone_name': 'Untrusted', + 'destination_zone_name': 'Internal', + 'schedule': {'mode': 'ALWAYS'}, + 'source': { + 'matching_target': 'IP', + 'matching_target_type': 'SPECIFIC', + 'ips': restricted_subnets, + 'match_mac': False, + 'match_opposite_ips': False, + 'match_opposite_ports': False, + 'port_matching_type': 'ANY', + }, + 'destination': { + 'matching_target': 'IP', + 'matching_target_type': 'SPECIFIC', + 'ips': ['10.5.30.53'], + 'match_opposite_ips': False, + 'match_opposite_ports': False, + 'port_matching_type': 'SPECIFIC', + 'port': '53', + }, + }, + { + 'name': 'Block Untrusted to Gateway Default Services', + 'enabled': True, + 'action': 'BLOCK', + 'protocol': 'all', + 'ip_version': 'BOTH', + 'logging': False, + 'create_allow_respond': False, + 'connection_state_type': 'ALL', + 'connection_states': [], + 'match_ip_sec': False, + 'match_opposite_protocol': False, + 'icmp_typename': 'ANY', + 'icmp_v6_typename': 'ANY', + 'description': 'Block general access from IoT, Camera, and Old IoT to gateway services after DNS cutover, preserving only explicit DHCP and mDNS exceptions.', + 'source_zone_name': 'Untrusted', + 'destination_zone_name': 'Gateway', + 'schedule': {'mode': 'ALWAYS'}, + 'source': { + 'matching_target': 'IP', + 'matching_target_type': 'SPECIFIC', + 'ips': restricted_subnets, + 'match_mac': False, + 'match_opposite_ips': False, + 'match_opposite_ports': False, + 'port_matching_type': 'ANY', + }, + 'destination': { + 'matching_target': 'ANY', + 'match_opposite_ports': False, + 'port_matching_type': 'ANY', + }, + }, + { + 'name': 'Allow Untrusted to Gateway DHCP', + 'enabled': True, + 'action': 'ALLOW', + 'protocol': 'udp', + 'ip_version': 'BOTH', + 'logging': False, + 'create_allow_respond': False, + 'connection_state_type': 'ALL', + 'connection_states': [], + 'match_ip_sec': False, + 'match_opposite_protocol': False, + 'icmp_typename': 'ANY', + 'icmp_v6_typename': 'ANY', + 'description': 'Preserve DHCP from IoT, Camera, and Old IoT to the gateway while the broader gateway shield is in place.', + 'source_zone_name': 'Untrusted', + 'destination_zone_name': 'Gateway', + 'schedule': {'mode': 'ALWAYS'}, + 'source': { + 'matching_target': 'IP', + 'matching_target_type': 'SPECIFIC', + 'ips': restricted_subnets, + 'match_mac': False, + 'match_opposite_ips': False, + 'match_opposite_ports': False, + 'port_matching_type': 'SPECIFIC', + 'port': '68', + }, + 'destination': { + 'matching_target': 'ANY', + 'match_opposite_ports': False, + 'port_matching_type': 'SPECIFIC', + 'port': '67', + }, + }, + { + 'name': 'Allow Untrusted to Gateway mDNS', + 'enabled': True, + 'action': 'ALLOW', + 'protocol': 'udp', + 'ip_version': 'IPV4', + 'logging': False, + 'create_allow_respond': False, + 'connection_state_type': 'ALL', + 'connection_states': [], + 'match_ip_sec': False, + 'match_opposite_protocol': False, + 'icmp_typename': 'ANY', + 'icmp_v6_typename': 'ANY', + 'description': 'Preserve mDNS multicast from IoT, Camera, and Old IoT while the broader gateway shield is in place.', + 'source_zone_name': 'Untrusted', + 'destination_zone_name': 'Gateway', + 'schedule': {'mode': 'ALWAYS'}, + 'source': { + 'matching_target': 'IP', + 'matching_target_type': 'SPECIFIC', + 'ips': restricted_subnets, + 'match_mac': False, + 'match_opposite_ips': False, + 'match_opposite_ports': False, + 'port_matching_type': 'SPECIFIC', + 'port': '5353', + }, + 'destination': { + 'matching_target': 'IP', + 'matching_target_type': 'SPECIFIC', + 'ips': ['224.0.0.251'], + 'match_opposite_ips': False, + 'match_opposite_ports': False, + 'port_matching_type': 'SPECIFIC', + 'port': '5353', + }, + }, ] diff --git a/home/doris-dashboard/docs/network-migration-remaining-checklist-2026-05-22.md b/home/doris-dashboard/docs/network-migration-remaining-checklist-2026-05-22.md index 49baa20..f2e66d1 100644 --- a/home/doris-dashboard/docs/network-migration-remaining-checklist-2026-05-22.md +++ b/home/doris-dashboard/docs/network-migration-remaining-checklist-2026-05-22.md @@ -35,7 +35,19 @@ Current intent: only safe, low-drama follow-up work remains. The easy-value clie ## Still pending before the migration is truly finished -### A. Google/cast validation batch +### A. DHCP/DNS and gateway-dependency closeout +- Fresh live verification is written up in `unifi-dhcp-dns-verification-2026-05-23.md` +- Recommended design is written up in `unifi-restricted-lane-dns-ntp-recommendation-2026-05-23.md` +- Live DNS cutover result is written up in `unifi-restricted-dns-cutover-result-2026-05-23.md` +- Broader gateway shield result is written up in `unifi-broader-gateway-shield-result-2026-05-23.md` +- Completed live changes: + - `IoT`, `Camera`, and `Old IoT` now DHCP-advertise DNS `10.5.30.53` + - explicit `Untrusted -> Internal` DNS allow now preserves access to `10.5.30.53:53` + - broader `Untrusted -> Gateway` block is now in place with DHCP and mDNS preserved +- NTP remains intentionally unchanged for now +- Remaining follow-up is validation, not additional broad gateway-rule design + +### B. Google/cast validation batch Do later, with user present on laptop: - pilot one Google/cast-class device only - validate Plex playback @@ -49,33 +61,36 @@ Likely candidates still needing that treatment: - `3c:8d:20:f3:92:36` - `90:ca:fa:b6:7f:6e` -### B. Remaining Trusted-lane cleanup +### C. Remaining Trusted-lane cleanup - Completed: `d4:ad:fc:f2:df:d2` was removed from Trusted and re-homed to `Old IoT` - Reference: `intellirocks-triage-result-2026-05-23.md` -### C. Legacy CIA quarantine closeout +### D. Legacy CIA quarantine closeout - Completed as a disposition decision: the remaining unidentified leftovers stay quarantined on `Old IoT` - Reference: `unifi-legacy-cia-closeout-2026-05-23.md` -### D. Real firewall enforcement -Partially completed: -- live custom rule now blocks `IoT` / `Camera` / `Old IoT` access to the UDM Pro admin TCP ports -- reference: `unifi-firewall-enforcement-result-2026-05-23.md` +### E. Real firewall enforcement +Substantially completed: +- live custom rule blocks `IoT` / `Camera` / `Old IoT` access to the UDM Pro admin TCP ports +- live custom rule allows restricted-lane DNS to `10.5.30.53:53` +- live custom rule blocks general restricted-lane `Untrusted -> Gateway` access +- live custom rules preserve DHCP and mDNS for the restricted lanes +- references: + - `unifi-firewall-enforcement-result-2026-05-23.md` + - `unifi-broader-gateway-shield-result-2026-05-23.md` -Still needed if the full design is to be enforced rather than just the first safe slice: -- stateful baseline review against UniFi built-ins -- broader management shield only after DHCP/DNS/gateway dependencies are verified +Still needed if the full design is to be enforced rather than just the current safe slices: +- real client validation from each restricted lane after lease renewal - explicit Trusted admin -> Management allows if broad internal->gateway restrictions are introduced later - Trusted -> Servers allow only if later intra-Internal restrictions are introduced - any camera/Protect helper exceptions after port-level verification - any Google/cast discovery exceptions only after laptop validation Important distinction: -- groups/objects exist -- at least one custom outbound policy exists -- the full inter-VLAN segmentation matrix is not yet fully enforced +- custom rules now cover the main restricted-lane gateway hardening path +- the full inter-VLAN segmentation matrix is still not yet fully enforced -### E. Final cleanup +### F. Final cleanup - SSID simplification once Google/cast behavior is understood - reference SSID retire/keep plan: `unifi-ssid-cleanup-proposal-2026-05-23.md` - final validation sweep @@ -84,6 +99,7 @@ Important distinction: ## Suggested execution order from here 1. Wait for laptop session -2. Human-validate the already-completed Google/cast pilot from the laptop -3. Finish firewall enforcement carefully -4. Do final SSID simplification and closeout using `unifi-ssid-cleanup-proposal-2026-05-23.md` +2. Human-validate Google/cast behavior from the laptop +3. Renew/validate one real client on each restricted lane against the new DNS + gateway shield posture +4. Add only any proven narrow exceptions that real validation actually requires +5. Do final SSID simplification and closeout using `unifi-ssid-cleanup-proposal-2026-05-23.md` diff --git a/home/doris-dashboard/docs/unifi-broader-gateway-shield-result-2026-05-23.md b/home/doris-dashboard/docs/unifi-broader-gateway-shield-result-2026-05-23.md new file mode 100644 index 0000000..8085e20 --- /dev/null +++ b/home/doris-dashboard/docs/unifi-broader-gateway-shield-result-2026-05-23.md @@ -0,0 +1,66 @@ +# UniFi Broader Gateway Shield Result — 2026-05-23 + +Purpose: record the live broader `Untrusted -> Gateway` shield after the restricted-lane DHCP DNS cutover. + +## Live outcome +Applied the broader restricted-lane gateway hardening successfully. + +Restricted source lanes covered: +- `IoT` (`10.5.10.0/24`) +- `Camera` (`10.5.20.0/24`) +- `Old IoT` (`192.168.1.0/24`) + +## Custom policies now present live +1. `Block Untrusted to Gateway Admin Surfaces` + - blocks TCP admin surfaces on `10.5.0.1` + - ports: `22,80,443,8443,9443` + +2. `Allow Untrusted to DNS Resolver` + - preserves restricted-lane DNS to `10.5.30.53:53` + - direction: `Untrusted -> Internal` + - protocol: `tcp_udp` + +3. `Block Untrusted to Gateway Default Services` + - blocks general restricted-lane access to the gateway zone + +4. `Allow Untrusted to Gateway DHCP` + - preserves DHCP while the broader gateway shield is active + - UDP `68 -> 67` + +5. `Allow Untrusted to Gateway mDNS` + - preserves mDNS multicast behavior + - UDP `5353 -> 224.0.0.251:5353` + +## Important implementation note +During staging, one hidden dependency became obvious: +- after moving restricted-lane DHCP DNS to `10.5.30.53`, those clients still needed an explicit `Untrusted -> Internal` allow for the DNS resolver + +So the broader shield was not considered complete until that allow was added live. + +## Verified live after apply +Read back from the UniFi Policy Engine on PD and confirmed the following custom rule order exists live: +- idx 108: `Block Untrusted to Gateway Admin Surfaces` +- idx 109: `Allow Untrusted to DNS Resolver` +- idx 110: `Block Untrusted to Gateway Default Services` +- idx 111: `Allow Untrusted to Gateway DHCP` +- idx 112: `Allow Untrusted to Gateway mDNS` + +## Net effect +For `IoT`, `Camera`, and `Old IoT`: +- gateway admin surfaces are blocked +- general gateway access is blocked +- DNS to `10.5.30.53` is preserved +- DHCP is preserved +- mDNS is preserved + +## What is still not proven by controller reads alone +This does not by itself prove every device behavior is perfect. + +Still validate with real clients: +- renew at least one DHCP lease per restricted lane +- confirm DNS resolution works through `10.5.30.53` +- confirm gateway UI/admin access is blocked from those lanes +- treat any additional exception as earned only by a real failing client + +## Bottom line +The broader restricted-lane `Untrusted -> Gateway` shield is now live, with explicit DNS, DHCP, and mDNS preservation in place. diff --git a/home/doris-dashboard/docs/unifi-dhcp-dns-verification-2026-05-23.md b/home/doris-dashboard/docs/unifi-dhcp-dns-verification-2026-05-23.md new file mode 100644 index 0000000..c512c6f --- /dev/null +++ b/home/doris-dashboard/docs/unifi-dhcp-dns-verification-2026-05-23.md @@ -0,0 +1,90 @@ +# UniFi DHCP/DNS Verification — 2026-05-23 + +Purpose: determine whether the restricted lanes (`IoT`, `Camera`, `Old IoT`) are safe for a broader `Untrusted -> Gateway` management shield, specifically by checking what DHCP/DNS behavior they actually appear to rely on. + +## Live verification performed +Fresh read-only UniFi poll executed from PD against the live controller. + +Verified live from UniFi: +- site: `default` +- controller path: `rest/networkconf` +- supporting reads: `stat/sta`, `v2/api/site/default/firewall-policies` +- current custom policy still present: `Block Untrusted to Gateway Admin Surfaces` + +Observed restricted-lane client counts during the same read: +- `IoT`: 6 +- `Camera`: 3 +- `Old IoT`: 11 + +## Restricted-lane DHCP/DNS findings from the live controller + +### IoT +- subnet: `10.5.10.1/24` +- VLAN: `510` +- DHCP range: `10.5.10.6` - `10.5.10.254` +- `dhcpd_enabled=true` +- `dhcpd_dns_enabled=false` +- `dhcpd_ntp_enabled=false` +- `dhcpd_gateway_enabled=false` +- `mdns_enabled=true` +- `network_isolation_enabled=true` + +### Camera +- subnet: `10.5.20.1/24` +- VLAN: `520` +- DHCP range: `10.5.20.6` - `10.5.20.254` +- `dhcpd_enabled=true` +- `dhcpd_dns_enabled=false` +- `dhcpd_ntp_enabled=false` +- `dhcpd_gateway_enabled=false` +- `mdns_enabled=true` +- `network_isolation_enabled=false` + +### Old IoT +- subnet: `192.168.1.1/24` +- VLAN: `2` +- DHCP range: `192.168.1.100` - `192.168.1.199` +- `dhcpd_enabled=true` +- `dhcpd_dns_enabled=false` +- `dhcpd_ntp_enabled=false` +- `dhcpd_gateway_enabled=false` +- `mdns_enabled=true` +- `network_isolation_enabled=false` + +## Interpretation +The common pattern across all three restricted lanes is unchanged: +- DHCP is on +- custom DHCP DNS is off +- custom DHCP NTP is off +- no explicit per-network DNS servers are configured for those three lanes + +Operationally, that means these lanes still look gateway-dependent for their default DHCP-delivered DNS behavior. + +Safe working assumption from the live config: +- `IoT` clients likely still use `10.5.10.1` for default DNS delivery +- `Camera` clients likely still use `10.5.20.1` for default DNS delivery +- `Old IoT` clients likely still use `192.168.1.1` for default DNS delivery +- NTP is likewise not explicitly overridden per network + +## Decision +Do not apply a broad `Untrusted -> Gateway` deny yet. + +The currently deployed surgical rule remains the right safe stopping point: +- `Block Untrusted to Gateway Admin Surfaces` + +A broader management shield is still unsafe until one of these becomes true and is verified: +1. restricted-lane clients are intentionally moved to explicit non-gateway DNS/NTP targets, or +2. the broader gateway policy explicitly preserves the exact gateway services those lanes still need, or +3. a later live validation proves those clients no longer depend on gateway DNS/NTP despite the current network definitions + +## Practical next step before any broader gateway block +Preferred next live order: +1. decide whether `10.5.30.53` should be the DHCP-advertised DNS target for `IoT`, `Camera`, and `Old IoT` +2. decide whether NTP should stay public, stay gateway-provided, or move to a local service +3. if DNS/NTP stay gateway-dependent, model the exact gateway exceptions first +4. only then convert the current surgical admin-surface block into a broader `Untrusted -> Gateway` shield + +## Bottom line +Fresh live UniFi reads confirm the restricted lanes still look gateway-dependent for default DHCP-delivered DNS behavior. + +So the answer to "can we safely broaden the gateway shield right now?" is: not yet. diff --git a/home/doris-dashboard/docs/unifi-restricted-dns-cutover-result-2026-05-23.md b/home/doris-dashboard/docs/unifi-restricted-dns-cutover-result-2026-05-23.md new file mode 100644 index 0000000..dbd80fd --- /dev/null +++ b/home/doris-dashboard/docs/unifi-restricted-dns-cutover-result-2026-05-23.md @@ -0,0 +1,47 @@ +# UniFi Restricted-Lane DNS Cutover Result — 2026-05-23 + +Purpose: record the live DHCP DNS change for the restricted lanes and the immediate post-write verification. + +## Live change applied +Updated the UniFi network definitions for: +- `IoT` +- `Camera` +- `Old IoT` + +New DHCP DNS target on all three lanes: +- `10.5.30.53` + +NTP was intentionally left unchanged: +- `dhcpd_ntp_enabled=false` + +## Verified live after apply +### IoT +- subnet: `10.5.10.1/24` +- `dhcpd_dns_enabled=true` +- `dhcpd_dns_1=10.5.30.53` + +### Camera +- subnet: `10.5.20.1/24` +- `dhcpd_dns_enabled=true` +- `dhcpd_dns_1=10.5.30.53` + +### Old IoT +- subnet: `192.168.1.1/24` +- `dhcpd_dns_enabled=true` +- `dhcpd_dns_1=10.5.30.53` + +## Why this matters +This removes the prior default-DNS dependency on each restricted lane's gateway address and puts those devices behind John's DNS blacklist policy. + +That makes the next firewall phase materially safer because a broader `Untrusted -> Gateway` shield no longer has to preserve gateway DNS behavior for these three lanes. + +## Still not done yet +This change alone does not prove the broader gateway shield is safe to apply immediately. + +Before the next firewall wave, still verify: +- whether any restricted devices need gateway NTP behavior +- whether any restricted devices still need other specific gateway services besides DHCP/mDNS +- Google/cast behavior separately from a laptop session + +## Recommended next live step +Stage the broader `Untrusted -> Gateway` shield carefully with only exact remaining exceptions preserved, instead of leaving the current broad gateway dependency in place. diff --git a/home/doris-dashboard/docs/unifi-restricted-lane-dns-ntp-recommendation-2026-05-23.md b/home/doris-dashboard/docs/unifi-restricted-lane-dns-ntp-recommendation-2026-05-23.md new file mode 100644 index 0000000..00ea3a1 --- /dev/null +++ b/home/doris-dashboard/docs/unifi-restricted-lane-dns-ntp-recommendation-2026-05-23.md @@ -0,0 +1,60 @@ +# UniFi Restricted-Lane DNS/NTP Recommendation — 2026-05-23 + +Purpose: turn the DHCP/DNS verification result into a concrete next-step design decision for `IoT`, `Camera`, and `Old IoT`. + +## Inputs +- John wants restricted lanes to use `10.5.30.53` so those devices inherit the DNS blacklist policy. +- Fresh live verification showed `IoT`, `Camera`, and `Old IoT` still rely on default gateway-delivered DNS behavior. +- Current live firewall hardening stops at `Block Untrusted to Gateway Admin Surfaces`. + +## Recommended decision + +### DNS +Use explicit DHCP-advertised DNS for all restricted lanes: +- `IoT` -> `10.5.30.53` +- `Camera` -> `10.5.30.53` +- `Old IoT` -> `10.5.30.53` + +Why: +- puts those devices behind John's DNS blacklist policy +- removes default dependency on each lane's gateway IP for DNS +- makes later `Untrusted -> Gateway` tightening much safer +- centralizes DNS behavior instead of hiding it in per-subnet gateway defaults + +### NTP +Do not force restricted lanes onto a new local NTP dependency yet. + +Recommended day-one posture: +- keep NTP non-gateway-dependent if UniFi's policy model can express it cleanly as outbound/public time sync +- if that cannot be expressed cleanly in the first pass, explicitly preserve only the exact gateway NTP behavior still needed until a later cleanup window + +Why: +- DNS filtering value is clear and immediate +- local NTP adds another service dependency without the same immediate user-visible benefit +- many embedded devices are tolerant as long as they can reach some valid time source +- this avoids pretending PD or another host is the canonical house NTP service before that has been deliberately verified + +## Practical rollout shape +1. Update the UniFi network definitions for `IoT`, `Camera`, and `Old IoT` so DHCP hands out `10.5.30.53` explicitly. +2. Re-read `networkconf` to confirm the custom DNS setting stuck. +3. Only after that, stage the broader `Untrusted -> Gateway` shield. +4. In that broader shield, preserve only: + - DHCP + - mDNS if still intentionally needed + - NTP path as explicitly chosen +5. Do not guess Google/cast discovery carve-outs into the same wave. + +## What this means for the firewall plan +After the DNS cutover, the broader management shield can stop assuming the gateway must remain reachable for restricted-lane DNS. + +That makes the next firewall phase much cleaner: +- block general `Untrusted -> Gateway` +- preserve only narrow exceptions actually proven necessary +- keep admin surfaces blocked regardless + +## Bottom line +Recommended design: +- DNS for `IoT` / `Camera` / `Old IoT`: `10.5.30.53` +- NTP for those lanes: leave as minimal non-gateway/public behavior for now rather than inventing a new local dependency + +This is the safest next step that gives immediate value and reduces hidden gateway dependency before broader firewall tightening.