Harden UniFi restricted-lane gateway access
Some checks failed
secret-guardrails / artifact-secret-scan (push) Has been cancelled
secret-guardrails / gitleaks (push) Has been cancelled

This commit is contained in:
Fizzlepoof
2026-05-23 02:48:49 +00:00
parent 462b39e572
commit 080ba83989
6 changed files with 442 additions and 19 deletions

View File

@@ -33,6 +33,7 @@ def load_helper(path: Path):
def desired_policies() -> list[dict[str, Any]]: 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 [ return [
{ {
'name': 'Allow Internal to Untrusted', 'name': 'Allow Internal to Untrusted',
@@ -76,14 +77,14 @@ def desired_policies() -> list[dict[str, Any]]:
'match_opposite_protocol': False, 'match_opposite_protocol': False,
'icmp_typename': 'ANY', 'icmp_typename': 'ANY',
'icmp_v6_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', 'source_zone_name': 'Untrusted',
'destination_zone_name': 'Gateway', 'destination_zone_name': 'Gateway',
'schedule': {'mode': 'ALWAYS'}, 'schedule': {'mode': 'ALWAYS'},
'source': { 'source': {
'matching_target': 'IP', 'matching_target': 'IP',
'matching_target_type': 'SPECIFIC', '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_mac': False,
'match_opposite_ips': False, 'match_opposite_ips': False,
'match_opposite_ports': False, 'match_opposite_ports': False,
@@ -99,6 +100,149 @@ def desired_policies() -> list[dict[str, Any]]:
'port': '22,80,443,8443,9443', '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',
},
},
] ]

View File

@@ -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 ## 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: Do later, with user present on laptop:
- pilot one Google/cast-class device only - pilot one Google/cast-class device only
- validate Plex playback - validate Plex playback
@@ -49,33 +61,36 @@ Likely candidates still needing that treatment:
- `3c:8d:20:f3:92:36` - `3c:8d:20:f3:92:36`
- `90:ca:fa:b6:7f:6e` - `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` - 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` - 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` - Completed as a disposition decision: the remaining unidentified leftovers stay quarantined on `Old IoT`
- Reference: `unifi-legacy-cia-closeout-2026-05-23.md` - Reference: `unifi-legacy-cia-closeout-2026-05-23.md`
### D. Real firewall enforcement ### E. Real firewall enforcement
Partially completed: Substantially completed:
- live custom rule now blocks `IoT` / `Camera` / `Old IoT` access to the UDM Pro admin TCP ports - live custom rule blocks `IoT` / `Camera` / `Old IoT` access to the UDM Pro admin TCP ports
- reference: `unifi-firewall-enforcement-result-2026-05-23.md` - 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: Still needed if the full design is to be enforced rather than just the current safe slices:
- stateful baseline review against UniFi built-ins - real client validation from each restricted lane after lease renewal
- broader management shield only after DHCP/DNS/gateway dependencies are verified
- explicit Trusted admin -> Management allows if broad internal->gateway restrictions are introduced later - explicit Trusted admin -> Management allows if broad internal->gateway restrictions are introduced later
- Trusted -> Servers allow only if later intra-Internal restrictions are introduced - Trusted -> Servers allow only if later intra-Internal restrictions are introduced
- any camera/Protect helper exceptions after port-level verification - any camera/Protect helper exceptions after port-level verification
- any Google/cast discovery exceptions only after laptop validation - any Google/cast discovery exceptions only after laptop validation
Important distinction: Important distinction:
- groups/objects exist - custom rules now cover the main restricted-lane gateway hardening path
- at least one custom outbound policy exists - the full inter-VLAN segmentation matrix is still not yet fully enforced
- the full inter-VLAN segmentation matrix is not yet fully enforced
### E. Final cleanup ### F. Final cleanup
- SSID simplification once Google/cast behavior is understood - SSID simplification once Google/cast behavior is understood
- reference SSID retire/keep plan: `unifi-ssid-cleanup-proposal-2026-05-23.md` - reference SSID retire/keep plan: `unifi-ssid-cleanup-proposal-2026-05-23.md`
- final validation sweep - final validation sweep
@@ -84,6 +99,7 @@ Important distinction:
## Suggested execution order from here ## Suggested execution order from here
1. Wait for laptop session 1. Wait for laptop session
2. Human-validate the already-completed Google/cast pilot from the laptop 2. Human-validate Google/cast behavior from the laptop
3. Finish firewall enforcement carefully 3. Renew/validate one real client on each restricted lane against the new DNS + gateway shield posture
4. Do final SSID simplification and closeout using `unifi-ssid-cleanup-proposal-2026-05-23.md` 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`

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.