Add OPNsense WireGuard — router-level VPN with per-VLAN access control
Moves WireGuard off the management computer and onto OPNsense so any
device can VPN home without touching the management PC. Each peer is
restricted to only the VLANs you select (e.g. phone gets VLAN 10 only,
laptop gets VLAN 10 + 20). Private keys are generated on the mgmt PC
and never sent to OPNsense — only the public key is registered.
Backend (switch_backend.py):
- /api/opnsense/wireguard/status — check plugin, server, peers
- /api/opnsense/wireguard/setup-server — create wg1 on OPNsense via API
- DELETE /api/opnsense/wireguard/server — tear down server
- /api/opnsense/wireguard/add-peer — generate keypair, register peer,
link to server, return .conf
- DELETE /api/opnsense/wireguard/peer/{uuid} — revoke peer
- /api/opnsense/wireguard/peer-config/{name} — fetch saved .conf
Frontend (ers5952-manager.jsx):
- New OPNsenseWGSection component added to VPN tab below local WireGuard
- Progressive UI: not configured → plugin missing → server setup →
peer management (VLAN checkboxes) → QR/.conf download
- Firewall rules guidance panel auto-generated from active peers showing
exactly which OPNsense rules to add per VLAN
- vlans prop threaded through to WireGuardTab so VLAN names/colors
appear on peer badges and in the VLAN selector
https://claude.ai/code/session_01JR2EMK7rwrZJowpstcaxQ6
This commit is contained in:
+427
-1
@@ -1435,6 +1435,7 @@ export default function App() {
|
||||
session={session}
|
||||
onNeedAuth={() => setShowTotp(true)}
|
||||
backendOk={pollStatus!=="err"}
|
||||
vlans={vlans}
|
||||
/>}
|
||||
|
||||
{showTotp && <TotpModal
|
||||
@@ -1735,7 +1736,7 @@ function QRModal({ config, name, onClose }) {
|
||||
);
|
||||
}
|
||||
|
||||
function WireGuardTab({ session, onNeedAuth, backendOk }) {
|
||||
function WireGuardTab({ session, onNeedAuth, backendOk, vlans = [] }) {
|
||||
const [status, setStatus] = useState(null);
|
||||
const [clients, setClients] = useState([]);
|
||||
const [newName, setNewName] = useState('');
|
||||
@@ -1743,6 +1744,19 @@ function WireGuardTab({ session, onNeedAuth, backendOk }) {
|
||||
const [qrModal, setQrModal] = useState(null); // { config, name }
|
||||
const [error, setError] = useState('');
|
||||
|
||||
// ── OPNsense WireGuard state ────────────────────────────────────
|
||||
const [opnWg, setOpnWg] = useState(null); // status response
|
||||
const [opnLoading, setOpnLoading] = useState(false);
|
||||
const [opnError, setOpnError] = useState('');
|
||||
const [opnSetup, setOpnSetup] = useState({
|
||||
server_name: 'switch-mgmt-vpn', listen_port: 51820,
|
||||
tunnel_subnet: '10.99.2.0/24', public_endpoint: '',
|
||||
});
|
||||
const [opnPeerName, setOpnPeerName] = useState('');
|
||||
const [opnVlans, setOpnVlans] = useState([]); // checked VLAN IDs
|
||||
const [opnAdding, setOpnAdding] = useState(false);
|
||||
const [opnQr, setOpnQr] = useState(null); // { config, name }
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
const [s, c] = await Promise.all([API("/wireguard/status"), API("/wireguard/clients")]);
|
||||
@@ -1785,6 +1799,81 @@ function WireGuardTab({ session, onNeedAuth, backendOk }) {
|
||||
|
||||
const isRunning = status?.running;
|
||||
|
||||
// ── OPNsense WireGuard handlers ──────────────────────────────────
|
||||
const loadOpnWg = async () => {
|
||||
setOpnLoading(true); setOpnError('');
|
||||
try { setOpnWg(await API("/opnsense/wireguard/status")); }
|
||||
catch(e) { setOpnError(e.message); }
|
||||
setOpnLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => { if (backendOk) loadOpnWg(); }, [backendOk]);
|
||||
|
||||
const opnSetupServer = async () => {
|
||||
if (!session) { onNeedAuth(); return; }
|
||||
setOpnLoading(true); setOpnError('');
|
||||
try {
|
||||
await API("/opnsense/wireguard/setup-server", {
|
||||
method:"POST", body:{ token: session.token, ...opnSetup }
|
||||
});
|
||||
await loadOpnWg();
|
||||
} catch(e) { setOpnError(e.message); }
|
||||
setOpnLoading(false);
|
||||
};
|
||||
|
||||
const opnDeleteServer = async () => {
|
||||
if (!session) { onNeedAuth(); return; }
|
||||
if (!confirm("Remove the WireGuard server from OPNsense? All peers will be disconnected.")) return;
|
||||
setOpnLoading(true); setOpnError('');
|
||||
try {
|
||||
await API(`/opnsense/wireguard/server?token=${session.token}`, { method:"DELETE" });
|
||||
await loadOpnWg();
|
||||
} catch(e) { setOpnError(e.message); }
|
||||
setOpnLoading(false);
|
||||
};
|
||||
|
||||
const opnToggleVlan = (vid) => {
|
||||
setOpnVlans(prev => prev.includes(vid) ? prev.filter(v=>v!==vid) : [...prev, vid]);
|
||||
};
|
||||
|
||||
const opnAddPeer = async () => {
|
||||
if (!opnPeerName.trim()) return;
|
||||
if (opnVlans.length === 0) { setOpnError("Select at least one VLAN for this peer."); return; }
|
||||
if (!session) { onNeedAuth(); return; }
|
||||
setOpnAdding(true); setOpnError('');
|
||||
// Build vlan_subnets map from the vlans prop
|
||||
const vlan_subnets = {};
|
||||
vlans.forEach(v => { vlan_subnets[v.id] = `192.168.${v.id}.0/24`; });
|
||||
try {
|
||||
const r = await API("/opnsense/wireguard/add-peer", {
|
||||
method:"POST",
|
||||
body:{ token: session.token, name: opnPeerName.trim(),
|
||||
allowed_vlans: opnVlans, vlan_subnets }
|
||||
});
|
||||
setOpnQr({ config: r.config, name: r.name });
|
||||
setOpnPeerName(''); setOpnVlans([]);
|
||||
await loadOpnWg();
|
||||
} catch(e) { setOpnError(e.message); }
|
||||
setOpnAdding(false);
|
||||
};
|
||||
|
||||
const opnRevokePeer = async (uuid, name) => {
|
||||
if (!session) { onNeedAuth(); return; }
|
||||
if (!confirm(`Revoke VPN access for "${name}"?`)) return;
|
||||
setOpnError('');
|
||||
try {
|
||||
await API(`/opnsense/wireguard/peer/${uuid}?token=${session.token}`, { method:"DELETE" });
|
||||
await loadOpnWg();
|
||||
} catch(e) { setOpnError(e.message); }
|
||||
};
|
||||
|
||||
const opnShowConf = async (name) => {
|
||||
try {
|
||||
const r = await API(`/opnsense/wireguard/peer-config/${name}`);
|
||||
setOpnQr({ config: r.config, name: r.name });
|
||||
} catch(e) { setOpnError(e.message); }
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="main" style={{flexDirection:"column",gap:12}}>
|
||||
<div className="panel">
|
||||
@@ -1898,6 +1987,343 @@ function WireGuardTab({ session, onNeedAuth, backendOk }) {
|
||||
</div>
|
||||
|
||||
{qrModal && <QRModal config={qrModal.config} name={qrModal.name} onClose={()=>setQrModal(null)}/>}
|
||||
|
||||
{/* ── OPNsense WireGuard ─────────────────────────────────────── */}
|
||||
<OPNsenseWGSection
|
||||
opnWg={opnWg}
|
||||
opnLoading={opnLoading}
|
||||
opnError={opnError}
|
||||
opnSetup={opnSetup}
|
||||
setOpnSetup={setOpnSetup}
|
||||
opnPeerName={opnPeerName}
|
||||
setOpnPeerName={setOpnPeerName}
|
||||
opnVlans={opnVlans}
|
||||
opnAdding={opnAdding}
|
||||
session={session}
|
||||
onNeedAuth={onNeedAuth}
|
||||
vlans={vlans}
|
||||
onToggleVlan={opnToggleVlan}
|
||||
onSetupServer={opnSetupServer}
|
||||
onDeleteServer={opnDeleteServer}
|
||||
onAddPeer={opnAddPeer}
|
||||
onRevokePeer={opnRevokePeer}
|
||||
onShowConf={opnShowConf}
|
||||
onRefresh={loadOpnWg}
|
||||
/>
|
||||
|
||||
{opnQr && <QRModal config={opnQr.config} name={opnQr.name} onClose={()=>setOpnQr(null)}/>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── OPNsense WireGuard section (separate component for readability) ───────────
|
||||
function OPNsenseWGSection({
|
||||
opnWg, opnLoading, opnError, opnSetup, setOpnSetup,
|
||||
opnPeerName, setOpnPeerName, opnVlans, opnAdding,
|
||||
session, onNeedAuth, vlans,
|
||||
onToggleVlan, onSetupServer, onDeleteServer,
|
||||
onAddPeer, onRevokePeer, onShowConf, onRefresh,
|
||||
}) {
|
||||
const panelHeader = (
|
||||
<div className="ph" style={{display:"flex",alignItems:"center",gap:8}}>
|
||||
◈ OPNsense WireGuard — Router-Level VPN
|
||||
<span style={{marginLeft:"auto",fontSize:10,fontFamily:"var(--mono)",
|
||||
color:"var(--dm)",cursor:"pointer"}} onClick={onRefresh}>
|
||||
{opnLoading ? "loading…" : "↻ refresh"}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
// ── OPNsense not connected ──────────────────────────────────────
|
||||
if (!opnWg || !opnWg.opnsense_configured) {
|
||||
return (
|
||||
<div className="panel">
|
||||
{panelHeader}
|
||||
<div className="pb" style={{color:"var(--dm)",fontSize:12,lineHeight:1.8}}>
|
||||
<div style={{marginBottom:8}}>
|
||||
Move WireGuard off your management computer and onto OPNsense.
|
||||
Each peer can be restricted to specific VLANs — e.g. a phone
|
||||
gets access to VLAN 10 only, while a laptop gets VLAN 10 + 20.
|
||||
</div>
|
||||
<div style={{background:"rgba(255,234,0,.07)",border:"1px solid rgba(255,234,0,.2)",
|
||||
borderRadius:4,padding:"10px 14px",fontSize:11,color:"var(--warn)"}}>
|
||||
OPNsense is not connected. Go to the <strong>DHCP tab</strong> and
|
||||
add your OPNsense API credentials first.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Plugin not installed ────────────────────────────────────────
|
||||
if (!opnWg.plugin_installed) {
|
||||
return (
|
||||
<div className="panel">
|
||||
{panelHeader}
|
||||
<div className="pb">
|
||||
<div style={{background:"rgba(255,23,68,.07)",border:"1px solid rgba(255,23,68,.2)",
|
||||
borderRadius:4,padding:"10px 14px",fontSize:11,color:"var(--err)",marginBottom:12}}>
|
||||
<strong>WireGuard plugin not installed on OPNsense.</strong>
|
||||
{opnWg.error && <div style={{marginTop:4,opacity:.7}}>{opnWg.error}</div>}
|
||||
</div>
|
||||
<div style={{fontSize:12,color:"var(--dm)",lineHeight:1.9}}>
|
||||
Install it in OPNsense:
|
||||
<ol style={{margin:"6px 0 0 16px",padding:0}}>
|
||||
<li>System → Firmware → Plugins</li>
|
||||
<li>Search <span style={{fontFamily:"var(--mono)",color:"var(--ac)"}}>wireguard</span></li>
|
||||
<li>Install <strong>os-wireguard</strong></li>
|
||||
<li>Reload this page or click refresh above</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const server = opnWg.server;
|
||||
const peers = opnWg.peers || [];
|
||||
|
||||
// ── Server not yet created ──────────────────────────────────────
|
||||
if (!server) {
|
||||
return (
|
||||
<div className="panel">
|
||||
{panelHeader}
|
||||
<div className="pb">
|
||||
<div style={{fontSize:12,color:"var(--dm)",lineHeight:1.7,marginBottom:14}}>
|
||||
Create a WireGuard server on OPNsense. It runs as <code>wg1</code> so it
|
||||
does not conflict with the local <code>wg0</code> on this machine.
|
||||
Peer private keys are generated here and stored only on this management PC —
|
||||
OPNsense only ever receives the public key.
|
||||
</div>
|
||||
|
||||
{opnError && (
|
||||
<div style={{color:"var(--err)",fontSize:11,marginBottom:10}}>{opnError}</div>
|
||||
)}
|
||||
|
||||
<div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:10,marginBottom:14}}>
|
||||
<div className="field" style={{margin:0}}>
|
||||
<label>Server Name</label>
|
||||
<input value={opnSetup.server_name}
|
||||
onChange={e=>setOpnSetup(s=>({...s,server_name:e.target.value}))}
|
||||
placeholder="switch-mgmt-vpn"/>
|
||||
</div>
|
||||
<div className="field" style={{margin:0}}>
|
||||
<label>Listen Port</label>
|
||||
<input type="number" value={opnSetup.listen_port}
|
||||
onChange={e=>setOpnSetup(s=>({...s,listen_port:parseInt(e.target.value)||51820}))}/>
|
||||
</div>
|
||||
<div className="field" style={{margin:0}}>
|
||||
<label>Tunnel Subnet</label>
|
||||
<input value={opnSetup.tunnel_subnet}
|
||||
onChange={e=>setOpnSetup(s=>({...s,tunnel_subnet:e.target.value}))}
|
||||
placeholder="10.99.2.0/24"/>
|
||||
</div>
|
||||
<div className="field" style={{margin:0}}>
|
||||
<label>Your Public IP / DDNS</label>
|
||||
<input value={opnSetup.public_endpoint}
|
||||
onChange={e=>setOpnSetup(s=>({...s,public_endpoint:e.target.value}))}
|
||||
placeholder="home.example.com or 1.2.3.4"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{fontSize:11,color:"var(--dm)",marginBottom:14,lineHeight:1.7}}>
|
||||
The tunnel subnet (default <span style={{fontFamily:"var(--mono)",color:"var(--ac)"}}>10.99.2.0/24</span>)
|
||||
is separate from your LAN VLANs. VPN clients get IPs from this range.
|
||||
Your public IP / DDNS is what clients connect to from the internet.
|
||||
</div>
|
||||
|
||||
<div style={{display:"flex",gap:8,alignItems:"center"}}>
|
||||
{!session && (
|
||||
<button className="btn bg" style={{fontSize:11,padding:"4px 10px"}} onClick={onNeedAuth}>
|
||||
Authenticate
|
||||
</button>
|
||||
)}
|
||||
<button className="btn bp" onClick={onSetupServer}
|
||||
disabled={opnLoading || !session || !opnSetup.server_name}>
|
||||
{opnLoading ? "Creating…" : "Create Server on OPNsense"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Server active — show peers and add-peer form ────────────────
|
||||
const tunnelNet = opnSetup.tunnel_subnet || "10.99.2.0/24";
|
||||
|
||||
// Determine which VLANs have firewall rules to show
|
||||
const allUsedVlans = [...new Set(peers.flatMap(p => p.allowed_vlans || []))];
|
||||
|
||||
return (
|
||||
<div style={{display:"flex",flexDirection:"column",gap:12}}>
|
||||
{/* Server status card */}
|
||||
<div className="panel">
|
||||
{panelHeader}
|
||||
<div className="pb">
|
||||
<div style={{display:"flex",alignItems:"flex-start",gap:16,flexWrap:"wrap"}}>
|
||||
<div style={{flex:1,minWidth:200}}>
|
||||
<div style={{fontWeight:700,marginBottom:6,color:"var(--ok)"}}>
|
||||
● {server.name}
|
||||
</div>
|
||||
<div style={{fontFamily:"var(--mono)",fontSize:11,color:"var(--dm)",lineHeight:2}}>
|
||||
<span style={{color:"var(--tx)"}}>Tunnel:</span> {server.tunnel_ip}<br/>
|
||||
<span style={{color:"var(--tx)"}}>Port:</span> {server.listen_port}<br/>
|
||||
<span style={{color:"var(--tx)"}}>Endpoint:</span> {server.public_endpoint||<em>not set</em>}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{flex:2,minWidth:240}}>
|
||||
<div style={{fontSize:10,color:"var(--dm)",marginBottom:4}}>Server Public Key</div>
|
||||
<div style={{fontFamily:"var(--mono)",fontSize:10,background:"var(--bg)",
|
||||
borderRadius:4,padding:"6px 10px",wordBreak:"break-all",
|
||||
border:"1px solid var(--b1)",color:"var(--ac)"}}>
|
||||
{server.pubkey || "— generating —"}
|
||||
</div>
|
||||
</div>
|
||||
<button className="btn bd" style={{fontSize:10,padding:"3px 10px",alignSelf:"flex-start"}}
|
||||
onClick={onDeleteServer}>
|
||||
Remove Server
|
||||
</button>
|
||||
</div>
|
||||
{opnError && (
|
||||
<div style={{color:"var(--err)",fontSize:11,marginTop:10}}>{opnError}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Peer list */}
|
||||
<div className="panel">
|
||||
<div className="ph">◈ VPN Peers ({peers.length})</div>
|
||||
<div className="pb">
|
||||
{peers.length === 0 && (
|
||||
<div className="empty" style={{padding:"12px 0"}}>No peers yet. Add one below.</div>
|
||||
)}
|
||||
{peers.map(p => (
|
||||
<div key={p.uuid} style={{display:"flex",alignItems:"center",gap:10,
|
||||
padding:"8px 0",borderBottom:"1px solid var(--b1)"}}>
|
||||
<div style={{width:8,height:8,borderRadius:"50%",flexShrink:0,
|
||||
background:p.enabled?"var(--ok)":"var(--dm)"}}/>
|
||||
<div style={{flex:1}}>
|
||||
<div style={{fontWeight:700,marginBottom:3}}>{p.name}</div>
|
||||
<div style={{fontFamily:"var(--mono)",fontSize:10,color:"var(--dm)"}}>
|
||||
{p.tunnel_ip}
|
||||
</div>
|
||||
<div style={{display:"flex",flexWrap:"wrap",gap:4,marginTop:4}}>
|
||||
{(p.allowed_vlans||[]).map(vid => {
|
||||
const v = vlans.find(x=>x.id===vid)||{name:`VLAN ${vid}`,color:"var(--dm)"};
|
||||
return (
|
||||
<span key={vid} style={{
|
||||
background:`${v.color}22`,color:v.color,
|
||||
borderRadius:10,padding:"1px 7px",fontSize:10,
|
||||
fontFamily:"var(--mono)",fontWeight:700,
|
||||
}}>VLAN {vid} · {v.name}</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<button className="btn bg" style={{fontSize:10,padding:"3px 8px"}}
|
||||
onClick={()=>onShowConf(p.name)}>
|
||||
.conf / QR
|
||||
</button>
|
||||
<button className="btn bd" style={{fontSize:10,padding:"3px 8px"}}
|
||||
onClick={()=>onRevokePeer(p.uuid, p.name)}>
|
||||
Revoke
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Add peer form */}
|
||||
<div style={{marginTop:16,paddingTop:14,borderTop:"1px solid var(--b1)"}}>
|
||||
<div style={{fontWeight:700,marginBottom:10,fontSize:12}}>Add Peer</div>
|
||||
<div style={{display:"flex",gap:10,alignItems:"flex-start",flexWrap:"wrap"}}>
|
||||
<div className="field" style={{margin:0,flex:"0 0 180px"}}>
|
||||
<label>Peer Name</label>
|
||||
<input value={opnPeerName}
|
||||
onChange={e=>setOpnPeerName(e.target.value)}
|
||||
onKeyDown={e=>e.key==="Enter"&&onAddPeer()}
|
||||
placeholder="e.g. phone, laptop"/>
|
||||
</div>
|
||||
<div style={{flex:1,minWidth:220}}>
|
||||
<label style={{display:"block",fontSize:11,color:"var(--dm)",
|
||||
marginBottom:6,fontFamily:"var(--mono)"}}>VLAN Access</label>
|
||||
<div style={{display:"flex",flexWrap:"wrap",gap:6}}>
|
||||
{vlans.filter(v=>v.id!==99).map(v => {
|
||||
const on = opnVlans.includes(v.id);
|
||||
return (
|
||||
<div key={v.id} onClick={()=>onToggleVlan(v.id)}
|
||||
style={{
|
||||
cursor:"pointer",userSelect:"none",borderRadius:5,
|
||||
padding:"5px 10px",fontSize:11,fontFamily:"var(--mono)",
|
||||
border:`1px solid ${on?v.color:"var(--b2)"}`,
|
||||
background:on?`${v.color}22`:"transparent",
|
||||
color:on?v.color:"var(--dm)",fontWeight:on?700:400,
|
||||
transition:"all .15s",
|
||||
}}>
|
||||
{on?"✓ ":""}{v.id} · {v.name}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{marginTop:12,display:"flex",gap:8,alignItems:"center"}}>
|
||||
{!session && (
|
||||
<button className="btn bg" style={{fontSize:11,padding:"4px 10px"}}
|
||||
onClick={onNeedAuth}>Authenticate</button>
|
||||
)}
|
||||
<button className="btn bp" onClick={onAddPeer}
|
||||
disabled={opnAdding||!opnPeerName.trim()||opnVlans.length===0||!session}>
|
||||
{opnAdding?"Adding…":"Add Peer"}
|
||||
</button>
|
||||
<span style={{fontSize:11,color:"var(--dm)"}}>
|
||||
Generates keypair here · sends only pubkey to OPNsense
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Firewall rules guidance */}
|
||||
{peers.length > 0 && (
|
||||
<div className="panel">
|
||||
<div className="ph">◈ Required OPNsense Firewall Rules</div>
|
||||
<div className="pb">
|
||||
<div style={{fontSize:12,color:"var(--dm)",lineHeight:1.7,marginBottom:10}}>
|
||||
WireGuard handles the tunnel, but OPNsense still enforces firewall rules
|
||||
between the tunnel and your VLANs. Add these rules under
|
||||
<span style={{fontFamily:"var(--mono)",color:"var(--ac)",margin:"0 4px"}}>
|
||||
Firewall → Rules → WireGuard
|
||||
</span>
|
||||
(the interface is <span style={{fontFamily:"var(--mono)",color:"var(--ac)"}}>wg1</span>
|
||||
if this is instance 1):
|
||||
</div>
|
||||
<div style={{background:"#060809",borderRadius:4,padding:"10px 14px",
|
||||
fontFamily:"var(--mono)",fontSize:11,color:"#a0b0c0",lineHeight:2}}>
|
||||
{/* One rule per unique VLAN across all peers */}
|
||||
{[...new Set(peers.flatMap(p=>p.allowed_vlans||[]))].sort((a,b)=>a-b).map(vid => {
|
||||
const v = vlans.find(x=>x.id===vid)||{name:`VLAN ${vid}`};
|
||||
return (
|
||||
<div key={vid}>
|
||||
<span style={{color:"#4fc"}}># Allow WireGuard → VLAN {vid} ({v.name})</span><br/>
|
||||
<span style={{color:"#ffa"}}>pass </span>
|
||||
<span>in interface wg1</span><br/>
|
||||
<span style={{paddingLeft:16}}>src: {tunnelNet}</span><br/>
|
||||
<span style={{paddingLeft:16}}>dst: 192.168.{vid}.0/24</span><br/>
|
||||
<br/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<span style={{color:"#4fc"}}># Block everything else from the tunnel</span><br/>
|
||||
<span style={{color:"#f88"}}>block </span>
|
||||
<span>in interface wg1 src: {tunnelNet} dst: any</span>
|
||||
</div>
|
||||
<div style={{fontSize:11,color:"var(--dm)",marginTop:8,lineHeight:1.7}}>
|
||||
Also open UDP {server?.listen_port||51820} inbound on your WAN interface
|
||||
so peers can reach OPNsense from the internet.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user