Rewrite services to use Caddy on LAN + NAT reflection for isolated VLANs
Previous approach tried to put a reverse proxy on OPNsense or poke firewall holes — both wrong. Correct architecture: - Caddy stays on LAN management computer (where it already is) - WAN: port 443 forwarded to Caddy. Only port exposed externally. - LAN devices reach services directly via Caddy - Isolated VLANs (IoT, Guest) use public FQDNs (plex.mydomain.com) - OPNsense NAT reflection handles this internally — traffic never leaves the network, but IoT is treated exactly like an external user - Zero cross-VLAN access. No pinholes. Full isolation preserved. IoT = untrusted = same access as someone on the internet. This is the correct security model — no exceptions for "just one port." Deploy endpoint now: writes Caddyfile entries, checks NAT reflection status, provides setup checklist for port forward + reflection toggle. https://claude.ai/code/session_01Do9bsN39MTuy2GVv7yzSrE
This commit is contained in:
+17
-13
@@ -5082,27 +5082,31 @@ function ServicesTab({ vlans, session, onNeedAuth, backendOk }) {
|
||||
<div className="main">
|
||||
<div style={{flex:1}}>
|
||||
<div className="panel">
|
||||
<div className="ph">Service Proxy — FQDN Access Without Breaking VLAN Isolation</div>
|
||||
<div className="ph">Services — Caddy Reverse Proxy + NAT Reflection</div>
|
||||
<div className="pb" style={{fontSize:12,color:"var(--dm)",lineHeight:1.8}}>
|
||||
<div style={{marginBottom:8}}>
|
||||
Make LAN services reachable by FQDN from any VLAN <b style={{color:"var(--tx)"}}>without
|
||||
any inter-VLAN access</b>. Devices never touch the service's VLAN directly.
|
||||
Caddy on the LAN management computer is your reverse proxy for all services.
|
||||
Only port 443 is forwarded from WAN. Service ports are never exposed externally.
|
||||
</div>
|
||||
<div style={{
|
||||
padding:12,background:"var(--bg)",borderRadius:6,border:"1px solid var(--b2)",
|
||||
fontFamily:"monospace",fontSize:11,lineHeight:2,
|
||||
}}>
|
||||
<div style={{color:"var(--ac)",fontWeight:700,marginBottom:4,fontFamily:"inherit",fontSize:12}}>
|
||||
How it works:
|
||||
How isolated VLANs reach services:
|
||||
</div>
|
||||
<div>1. IoT device (VLAN 30) asks DNS for <span style={{color:"var(--ac)"}}>plex.home.lan</span></div>
|
||||
<div>2. Unbound returns <span style={{color:"#00e676"}}>192.168.30.1</span> (OPNsense gateway — device can already reach this)</div>
|
||||
<div>3. OPNsense reverse proxy (Caddy/HAProxy) forwards to actual server <span style={{color:"#ff6d00"}}>192.168.1.100:32400</span></div>
|
||||
<div>4. Response returns the same path. <span style={{color:"#00e676"}}>IoT device never sees or touches LAN.</span></div>
|
||||
<div>1. IoT TV (VLAN 30) asks DNS for <span style={{color:"var(--ac)"}}>plex.mydomain.com</span></div>
|
||||
<div>2. DNS returns your <span style={{color:"#00e676"}}>public IP</span></div>
|
||||
<div>3. OPNsense sees "that's my WAN IP" → <span style={{color:"#ff6d00"}}>NAT reflection</span> routes internally</div>
|
||||
<div>4. Port forward sends to Caddy → Caddy proxies to Plex</div>
|
||||
<div>5. <span style={{color:"#00e676"}}>Traffic never leaves your network. Full VLAN isolation.</span></div>
|
||||
</div>
|
||||
<div style={{marginTop:8,color:"var(--ac)",fontWeight:600}}>
|
||||
No firewall rules needed. No VLAN-to-VLAN access opened. All VLANs can already reach
|
||||
their own gateway — that's how they get internet. The gateway does the proxying.
|
||||
<div style={{marginTop:8,color:"var(--tx)",fontWeight:600}}>
|
||||
IoT = untrusted = treated exactly like an external user. No pinholes, no cross-VLAN access.
|
||||
</div>
|
||||
<div style={{marginTop:6,fontSize:11,color:"var(--dm)"}}>
|
||||
Requires: OPNsense NAT reflection enabled (Firewall > Settings > Advanced > Reflection for port forwards)
|
||||
+ WAN port forward TCP 443 → management computer (Caddy).
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -5116,7 +5120,7 @@ function ServicesTab({ vlans, session, onNeedAuth, backendOk }) {
|
||||
<input value={form.fqdn} onChange={e => setForm(f => ({...f, fqdn: e.target.value}))}
|
||||
placeholder="plex.home.lan"/>
|
||||
</div>
|
||||
<div className="field"><label>Backend URL (actual server on LAN)</label>
|
||||
<div className="field"><label>Backend (LAN server IP:port)</label>
|
||||
<input value={form.backend_url} onChange={e => setForm(f => ({...f, backend_url: e.target.value}))}
|
||||
placeholder="http://192.168.1.100:32400"/>
|
||||
</div>
|
||||
@@ -5163,7 +5167,7 @@ function ServicesTab({ vlans, session, onNeedAuth, backendOk }) {
|
||||
{deploying ? "Deploying..." : "Deploy All Services"}
|
||||
</button>
|
||||
<span style={{fontSize:11,color:"var(--dm)"}}>
|
||||
Pushes DNS overrides to Unbound + configures reverse proxy on OPNsense
|
||||
Updates Caddyfile + checks NAT reflection on OPNsense
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user