Add configurable VPN access policies

This commit is contained in:
Stanislas Lange
2026-08-02 12:43:31 +02:00
parent 25476a7143
commit 7357079bb9
8 changed files with 953 additions and 279 deletions
+14
View File
@@ -129,6 +129,17 @@ jobs:
name: tls-crypt-v2
sig: crypt-v2
key_file: tls-crypt-v2.key
# Test split tunnel with peer and home-LAN access enabled
- os:
name: ubuntu-24.04-access-policy
image: ubuntu:24.04
route_internet: n
client_to_client: y
local_networks: 10.55.0.0/24
tls:
name: tls-crypt-v2
sig: crypt-v2
key_file: tls-crypt-v2.key
name: ${{ matrix.os.name }}
steps:
@@ -178,6 +189,9 @@ jobs:
-e TLS_KEY_FILE=${{ matrix.tls.key_file }} \
-e CLIENT_IPV6=${{ matrix.os.client_ipv6 && 'y' || 'n' }} \
-e AUTH_MODE=${{ matrix.os.auth_mode || 'pki' }} \
-e ROUTE_INTERNET=${{ matrix.os.route_internet || 'y' }} \
-e CLIENT_TO_CLIENT=${{ matrix.os.client_to_client || 'n' }} \
-e LOCAL_NETWORKS=${{ matrix.os.local_networks || '' }} \
openvpn-server
- name: Wait for server installation and startup
+31 -68
View File
@@ -86,7 +86,7 @@ down /usr/share/openvpn/contrib/pull-resolv-conf/client.down
**Q:** What sysctl and firewall changes are made by the script?
**A:** If firewalld is active, the script uses `firewall-cmd --permanent` to configure port, masquerade, and rich rules. Otherwise, iptables rules are saved at `/etc/iptables/add-openvpn-rules.sh` and `/etc/iptables/rm-openvpn-rules.sh`, managed by `/etc/systemd/system/iptables-openvpn.service`.
**A:** If firewalld is active, the script uses `firewall-cmd --permanent` to configure scoped rich rules. If nftables is active, it creates `/etc/nftables/openvpn.nft`. Otherwise, iptables rules are saved at `/etc/iptables/add-openvpn-rules.sh` and `/etc/iptables/rm-openvpn-rules.sh`, managed by `/etc/systemd/system/iptables-openvpn.service`.
Sysctl options are at `/etc/sysctl.d/99-openvpn.conf`
@@ -94,7 +94,13 @@ Sysctl options are at `/etc/sysctl.d/99-openvpn.conf`
**Q:** How can I access other clients connected to the same OpenVPN server?
**A:** Add `client-to-client` to your `server.conf`
**A:** Enable client-to-client access during installation:
```bash
./openvpn-install.sh install --client-to-client
```
It is disabled by default. The installer configures both OpenVPN and the firewall so the policy also applies when Data Channel Offload (DCO) is active.
---
@@ -110,36 +116,17 @@ Sysctl options are at `/etc/sysctl.d/99-openvpn.conf`
**Q:** How can I access computers on the OpenVPN server's LAN?
**A:** Two steps are required:
**A:** Specify the LAN during installation:
1. **Push a route to clients** - Add the LAN subnet to `/etc/openvpn/server/server.conf`:
```bash
./openvpn-install.sh install --local-network 192.168.1.0/24
```
```
push "route 192.168.1.0 255.255.255.0"
```
Repeat `--local-network` to expose more than one server-side network. This feature is mainly for OpenVPN servers installed at home. It is disabled by default so cloud VPC, container, and management networks are not exposed automatically.
Replace `192.168.1.0/24` with your actual LAN subnet.
The installer pushes the route, permits only the selected destination, and adds destination-scoped NAT. LAN computers therefore see the connection as coming from the OpenVPN server and do not need a return route to the VPN subnet.
2. **Enable routing back to VPN clients** - Choose one of these options:
- **Option A: Add a static route on your router** (recommended when you can configure your router)
On your LAN router, add a route for the VPN subnet (default `10.8.0.0/24`) pointing to the OpenVPN server's LAN IP. This allows LAN devices to reply to VPN clients without NAT.
- **Option B: Masquerade VPN traffic to LAN**
If you can't modify your router, add a masquerade rule so VPN traffic appears to come from the server:
```bash
# iptables
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -d 192.168.1.0/24 -j MASQUERADE
# or nftables
nft add rule ip nat postrouting ip saddr 10.8.0.0/24 ip daddr 192.168.1.0/24 masquerade
```
Make this persistent by adding it to your firewall scripts.
Restart OpenVPN after making changes: `systemctl restart openvpn-server@server`
Do not use a LAN CIDR that overlaps the VPN subnet. An overlap with the client's current LAN can also prevent the route from working.
---
@@ -180,56 +167,32 @@ To add password-protected clients:
---
**Q:** For my clients - I want to set my internal network to pass through the VPN and the rest to go through my internet?
**Q:** For my clients, how can I route only an internal network through the VPN?
**A:** You would need to edit the `.ovpn` file. You can edit the template out of which those files are created by editing `/etc/openvpn/server/client-template.txt` file and adding
**A:** Disable internet routing and specify the server-side network during installation:
```sh
route-nopull
route 10.0.0.0 255.0.0.0
```bash
./openvpn-install.sh install \
--no-route-internet \
--local-network 10.0.0.0/8
```
So for example - here it would route all traffic of `10.0.0.0/8` to the VPN. And the rest through the internet.
The client's normal internet route and DNS remain unchanged.
---
**Q:** How do I configure split-tunnel mode on the server (route only specific networks through VPN for all clients)?
**Q:** How do I configure split-tunnel mode on the server?
**A:** By default, the script configures full-tunnel mode where all client traffic goes through the VPN. To configure split-tunnel (only specific networks routed through VPN), edit `/etc/openvpn/server/server.conf`:
**A:** Use `--no-route-internet`. Add each server-side network that should use the tunnel:
1. Remove or comment out the redirect-gateway line:
```bash
./openvpn-install.sh install \
--no-route-internet \
--local-network 10.0.0.0/8 \
--local-network 192.168.1.0/24
```
```
#push "redirect-gateway def1 bypass-dhcp"
```
2. Add routes for the networks you want to tunnel:
```
push "route 10.0.0.0 255.0.0.0"
push "route 192.168.1.0 255.255.255.0"
```
3. Optionally remove DNS push directives if you don't want VPN DNS:
```
#push "dhcp-option DNS 1.1.1.1"
```
4. For IPv6, remove or comment out:
```
#push "route-ipv6 2000::/3"
#push "redirect-gateway ipv6"
```
Or add specific IPv6 routes:
```
push "route-ipv6 2001:db8::/32"
```
5. Restart OpenVPN: `systemctl restart openvpn-server@server`
The installer does not push internet routes, leak-blocking directives, or VPN DNS in this mode.
---
+17 -1
View File
@@ -12,7 +12,7 @@ This script is meant to be run on your own server, whether it's a VPS or a dedic
Once set up, you will be able to generate client configuration files for every device you want to connect.
Each client will be able to route its internet traffic through the server, fully encrypted.
By default, each client routes its internet traffic through the server, fully encrypted. You can instead keep internet traffic outside the VPN and allow only selected server-side networks.
```mermaid
graph LR
@@ -44,6 +44,7 @@ That said, OpenVPN still makes sense when you need:
- Immediate client disconnect on certificate revocation (via management interface)
- Uses [official OpenVPN repositories](https://community.openvpn.net/openvpn/wiki/OpenvpnSoftwareRepos) when possible for the latest stable releases
- Firewall rules and forwarding managed seamlessly (native firewalld and nftables support, iptables fallback)
- Independent access policies for internet routing, communication between VPN clients, and selected server-side networks
- Configurable VPN subnets (IPv4: default `10.8.0.0/24`, IPv6: default `fd42:42:42:42::/112`)
- Configurable tunnel MTU (default: `1500`)
- If needed, the script can cleanly remove OpenVPN, including configuration and firewall rules
@@ -263,6 +264,12 @@ The `install` command supports many options for customization:
# Custom VPN subnet
./openvpn-install.sh install --subnet-ipv4 10.9.0.0
# Home VPN: access the home LAN without routing internet through the VPN
./openvpn-install.sh install --no-route-internet --local-network 192.168.1.0/24
# Allow VPN clients to access each other
./openvpn-install.sh install --client-to-client
# Enable dual-stack (IPv4 + IPv6) for clients
./openvpn-install.sh install --client-ipv4 --client-ipv6
@@ -299,13 +306,22 @@ The `install` command supports many options for customization:
- `--no-client-ipv6` - Disable IPv6 for VPN clients
- `--subnet-ipv4 <x.x.x.0>` - IPv4 VPN subnet (default: `10.8.0.0`)
- `--subnet-ipv6 <prefix>` - IPv6 VPN subnet (default: `fd42:42:42:42::`)
- `--route-internet` / `--no-route-internet` - Enable or disable routing client internet traffic through the VPN (default: enabled)
- `--client-to-client` / `--no-client-to-client` - Allow or isolate communication between VPN clients (default: isolated)
- `--local-network <CIDR>` - Allow access to a server-side network and add destination-scoped NAT. Repeat for multiple networks (default: none)
- `--port <num>` - OpenVPN port (default: `1194`)
- `--port-random` - Use random port (49152-65535)
- `--protocol <udp|tcp>` - Protocol (default: `udp`)
- `--mtu <size>` - Tunnel MTU (default: `1500`)
Server-side network access is mainly intended for VPN servers installed at home. Specify each LAN explicitly. The installer does not automatically expose connected cloud, container, or management networks. LAN devices see connections as coming from the VPN server because destination-scoped NAT is enabled.
Local networks must use network-aligned IPv4 or IPv6 CIDRs and must not overlap the VPN pools. Client-side and server-side LANs that overlap can still cause routing conflicts.
**DNS Options:**
DNS settings are pushed only when internet routing through the VPN is enabled.
- `--dns <provider>` - DNS provider (default: `cloudflare`). Options: `system`, `unbound`, `cloudflare`, `quad9`, `quad9-uncensored`, `fdn`, `dnswatch`, `opendns`, `google`, `yandex`, `adguard`, `nextdns`, `custom`
- `--dns-primary <ip>` - Custom primary DNS (requires `--dns custom`)
- `--dns-secondary <ip>` - Custom secondary DNS (requires `--dns custom`)
+4
View File
@@ -14,6 +14,10 @@ services:
cgroupns: host
devices:
- /dev/net/tun:/dev/net/tun
environment:
ROUTE_INTERNET: ${ROUTE_INTERNET:-y}
CLIENT_TO_CLIENT: ${CLIENT_TO_CLIENT:-n}
LOCAL_NETWORKS: ${LOCAL_NETWORKS:-}
sysctls:
- net.ipv4.ip_forward=1
volumes:
+677 -154
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -80,7 +80,7 @@ RUN printf '%s\n' \
'[Service]' \
'Type=oneshot' \
'Environment=HOME=/root' \
'PassEnvironment=AUTH_MODE TLS_SIG TLS_KEY_FILE TLS_VERSION_MIN TLS13_CIPHERSUITES CLIENT_IPV6 VPN_SUBNET_IPV6 WAIT_TIMEOUT_SIGNAL WAIT_TIMEOUT_CONNECT WAIT_TIMEOUT_REVOKE' \
'PassEnvironment=AUTH_MODE TLS_SIG TLS_KEY_FILE TLS_VERSION_MIN TLS13_CIPHERSUITES CLIENT_IPV6 VPN_SUBNET_IPV6 ROUTE_INTERNET CLIENT_TO_CLIENT LOCAL_NETWORKS WAIT_TIMEOUT_SIGNAL WAIT_TIMEOUT_CONNECT WAIT_TIMEOUT_REVOKE' \
'WorkingDirectory=/root' \
'ExecStart=/entrypoint.sh' \
'RemainAfterExit=yes' \
+42 -7
View File
@@ -220,13 +220,42 @@ if [ "${CLIENT_IPV6:-n}" = "y" ]; then
fi
fi
# Test 2: Ping VPN gateway (IPv4)
echo "Test 2: Pinging VPN gateway (IPv4) ($VPN_GATEWAY)..."
# Test 2: Verify pushed routes match the access policy.
echo "Test 2: Checking access policy routes..."
if [ "${ROUTE_INTERNET:-y}" = "y" ]; then
if ip route show | grep -q '^0.0.0.0/1 .* tun0' && ip route show | grep -q '^128.0.0.0/1 .* tun0'; then
echo "PASS: Internet routes use the VPN"
else
echo "FAIL: VPN internet routes are missing"
ip route show
exit 1
fi
else
if ip route show | grep -qE '^(0\.0\.0\.0/1|128\.0\.0\.0/1) .* tun0'; then
echo "FAIL: Internet route uses the VPN in split-tunnel mode"
ip route show
exit 1
fi
echo "PASS: Internet routes remain outside the VPN"
fi
if [ -n "${LOCAL_NETWORKS:-}" ]; then
while IFS= read -r local_network; do
if [[ $local_network == *.* ]] && ! ip route show "$local_network" | grep -q 'tun0'; then
echo "FAIL: Local network route is missing for $local_network"
ip route show
exit 1
fi
done < <(tr ',' '\n' <<<"$LOCAL_NETWORKS")
fi
# Test 3: Ping VPN gateway (IPv4)
echo "Test 3: Pinging VPN gateway (IPv4) ($VPN_GATEWAY)..."
wait_for_gateway_ping "VPN gateway (IPv4)"
# Test 2b: Ping VPN gateway (IPv6, if enabled)
# Test 3b: Ping VPN gateway (IPv6, if enabled)
if [ "${CLIENT_IPV6:-n}" = "y" ]; then
echo "Test 2b: Pinging VPN gateway (IPv6) ($VPN_GATEWAY_IPV6)..."
echo "Test 3b: Pinging VPN gateway (IPv6) ($VPN_GATEWAY_IPV6)..."
if ping6 -c 5 "$VPN_GATEWAY_IPV6"; then
echo "PASS: Can ping VPN gateway (IPv6)"
else
@@ -235,8 +264,12 @@ if [ "${CLIENT_IPV6:-n}" = "y" ]; then
fi
fi
# Test 3: DNS resolution through Unbound
test_dns_resolution "Test 3"
# Test 4: DNS resolution through Unbound in full-tunnel mode.
if [ "${ROUTE_INTERNET:-y}" = "y" ]; then
test_dns_resolution "Test 4"
else
echo "Test 4: SKIP: VPN DNS is disabled in split-tunnel mode"
fi
echo ""
echo "=== Initial connectivity tests PASSED ==="
@@ -269,7 +302,9 @@ sleep 5
echo "Test: Pinging VPN gateway after renewal ($VPN_GATEWAY)..."
wait_for_gateway_ping "VPN gateway after renewal"
test_dns_resolution "Test: Post-renewal DNS"
if [ "${ROUTE_INTERNET:-y}" = "y" ]; then
test_dns_resolution "Test: Post-renewal DNS"
fi
echo ""
echo "=== Post-renewal connectivity tests PASSED ==="
+167 -48
View File
@@ -57,10 +57,27 @@ if grep -q $'\033' "$NO_COLOR_OUTPUT"; then
fi
echo "PASS: --no-color help output has no ANSI escape sequences"
INVALID_NETWORK_OUTPUT="/tmp/invalid-local-network.log"
if /opt/openvpn-install.sh install --local-network 192.168.1.1/24 >"$INVALID_NETWORK_OUTPUT" 2>&1; then
echo "FAIL: Host-address CIDR was accepted as a local network"
exit 1
elif grep -q "Invalid local network" "$INVALID_NETWORK_OUTPUT"; then
echo "PASS: Invalid local network CIDR is rejected"
else
echo "FAIL: Expected local network validation error"
cat "$INVALID_NETWORK_OUTPUT"
exit 1
fi
# Calculate VPN gateway from subnet (first usable IP)
VPN_GATEWAY="${VPN_SUBNET_IPV4%.*}.1"
export VPN_GATEWAY
# Access policy configuration
ROUTE_INTERNET="${ROUTE_INTERNET:-y}"
CLIENT_TO_CLIENT="${CLIENT_TO_CLIENT:-n}"
LOCAL_NETWORKS="${LOCAL_NETWORKS:-}"
# IPv6 configuration (optional)
# CLIENT_IPV6: y/n to enable IPv6 for VPN clients
# VPN_SUBNET_IPV6: IPv6 subnet (ULA prefix, e.g., fd42:42:42:42::)
@@ -95,6 +112,18 @@ INSTALL_CMD+=(--subnet-ipv4 "$VPN_SUBNET_IPV4")
INSTALL_CMD+=(--mtu 1400)
INSTALL_CMD+=(--client testclient)
if [ "$ROUTE_INTERNET" = "n" ]; then
INSTALL_CMD+=(--no-route-internet)
fi
if [ "$CLIENT_TO_CLIENT" = "y" ]; then
INSTALL_CMD+=(--client-to-client)
fi
if [ -n "$LOCAL_NETWORKS" ]; then
while IFS= read -r local_network; do
INSTALL_CMD+=(--local-network "$local_network")
done < <(tr ',' '\n' <<<"$LOCAL_NETWORKS")
fi
# Add IPv6 client support if enabled
if [ "$CLIENT_IPV6" = "y" ]; then
INSTALL_CMD+=(--client-ipv6)
@@ -197,6 +226,65 @@ fi
echo "All required files present"
# =====================================================
# Verify access policy configuration
# =====================================================
echo ""
echo "=== Verifying Access Policy Configuration ==="
if [ "$ROUTE_INTERNET" = "y" ]; then
if grep -q '^push "redirect-gateway def1 bypass-dhcp"' /etc/openvpn/server/server.conf; then
echo "PASS: Internet default route is pushed"
else
echo "FAIL: Internet default route is missing"
exit 1
fi
else
if grep -q 'redirect-gateway\|block-ipv6' /etc/openvpn/server/server.conf; then
echo "FAIL: Internet or leak-blocking routes exist in split-tunnel mode"
exit 1
fi
echo "PASS: Client internet routes remain outside the VPN"
fi
if [ "$CLIENT_TO_CLIENT" = "y" ]; then
grep -q '^client-to-client$' /etc/openvpn/server/server.conf || {
echo "FAIL: client-to-client directive is missing"
exit 1
}
else
if grep -q '^client-to-client$' /etc/openvpn/server/server.conf; then
echo "FAIL: client-to-client is enabled by default"
exit 1
fi
fi
if [ -n "$LOCAL_NETWORKS" ]; then
while IFS= read -r local_network; do
if [[ $local_network == *.* ]]; then
local_address="${local_network%/*}"
grep -q "^push \"route $local_address " /etc/openvpn/server/server.conf || {
echo "FAIL: Local IPv4 route for $local_network is missing"
exit 1
}
else
grep -q "^push \"route-ipv6 $local_network\"" /etc/openvpn/server/server.conf || {
echo "FAIL: Local IPv6 route for $local_network is missing"
exit 1
}
fi
done < <(tr ',' '\n' <<<"$LOCAL_NETWORKS")
fi
for setting in "ROUTE_INTERNET=$ROUTE_INTERNET" "CLIENT_TO_CLIENT=$CLIENT_TO_CLIENT" "LOCAL_NETWORKS=$LOCAL_NETWORKS"; do
grep -Fxq "$setting" /etc/openvpn/server/openvpn-install.conf || {
echo "FAIL: Policy manifest is missing $setting"
exit 1
}
done
echo "PASS: Access policy configuration is correct"
# =====================================================
# Verify management interface configuration
# =====================================================
@@ -264,6 +352,9 @@ echo "Client config copied to /shared/client.ovpn"
echo "VPN_GATEWAY=$VPN_GATEWAY"
echo "CLIENT_IPV6=$CLIENT_IPV6"
echo "AUTH_MODE=$AUTH_MODE"
echo "ROUTE_INTERNET=$ROUTE_INTERNET"
echo "CLIENT_TO_CLIENT=$CLIENT_TO_CLIENT"
echo "LOCAL_NETWORKS=$LOCAL_NETWORKS"
if [ "$CLIENT_IPV6" = "y" ]; then
echo "VPN_SUBNET_IPV6=$VPN_SUBNET_IPV6"
echo "VPN_GATEWAY_IPV6=$VPN_GATEWAY_IPV6"
@@ -599,7 +690,8 @@ echo "Post-renewal client tests passed"
# =====================================================
# Verify Unbound DNS resolver (started by systemd via install script)
# =====================================================
echo "=== Verifying Unbound DNS Resolver ==="
if [ "$ROUTE_INTERNET" = "y" ]; then
echo "=== Verifying Unbound DNS Resolver ==="
if [ -f /etc/unbound/unbound.conf ]; then
# Verify Unbound is running (started by systemctl in install script)
@@ -655,8 +747,15 @@ else
exit 1
fi
echo "=== Unbound Installation Verified ==="
echo ""
echo "=== Unbound Installation Verified ==="
echo ""
else
if grep -q '^push "dhcp-option DNS ' /etc/openvpn/server/server.conf; then
echo "FAIL: DNS is pushed while internet routing is disabled"
exit 1
fi
echo "PASS: VPN DNS setup is skipped in split-tunnel mode"
fi
# Verify OpenVPN server (started by systemd via install script)
echo "Verifying OpenVPN server..."
@@ -664,19 +763,18 @@ echo "Verifying OpenVPN server..."
# Verify firewall rules exist
echo "Verifying firewall rules..."
if systemctl is-active --quiet firewalld; then
# firewalld is active - verify masquerade is enabled
echo "firewalld detected, checking masquerade..."
for _ in $(seq 1 10); do
if firewall-cmd --query-masquerade 2>/dev/null; then
echo "PASS: firewalld masquerade is enabled"
break
echo "firewalld detected, checking scoped policy rules..."
if [ "$ROUTE_INTERNET" = "y" ]; then
if firewall-cmd --list-rich-rules | grep -q "source address=\"$VPN_SUBNET_IPV4/24\" masquerade"; then
echo "PASS: firewalld has source-scoped internet NAT"
else
echo "FAIL: firewalld source-scoped internet NAT is missing"
firewall-cmd --list-rich-rules
exit 1
fi
sleep 1
done
if ! firewall-cmd --query-masquerade 2>/dev/null; then
echo "FAIL: firewalld masquerade is not enabled"
echo "Current firewalld config:"
firewall-cmd --list-all 2>&1 || true
fi
if firewall-cmd --query-masquerade 2>/dev/null; then
echo "FAIL: firewalld zone-wide masquerade should not be enabled"
exit 1
fi
# Verify port is open
@@ -687,13 +785,17 @@ if systemctl is-active --quiet firewalld; then
firewall-cmd --list-ports
exit 1
fi
# Verify VPN subnet rich rule exists (source-based rules work reliably across firewalld backends)
if firewall-cmd --list-rich-rules | grep -q "source address=\"$VPN_SUBNET_IPV4/24\""; then
echo "PASS: VPN subnet rich rule is configured"
else
echo "FAIL: VPN subnet rich rule not found in firewalld"
echo "Current rich rules:"
firewall-cmd --list-rich-rules
if [ "$ROUTE_INTERNET" = "y" ]; then
# Private destinations, including the VPN pool, stay isolated unless explicitly allowed.
if firewall-cmd --list-rich-rules | grep -q "destination address=\"10.0.0.0/8\" reject"; then
echo "PASS: firewalld private-network isolation is configured"
else
echo "FAIL: firewalld private-network isolation is missing"
firewall-cmd --list-rich-rules
exit 1
fi
elif ! firewall-cmd --list-rich-rules | grep -q "source address=\"$VPN_SUBNET_IPV4/24\" reject"; then
echo "FAIL: firewalld split-tunnel default reject is missing"
exit 1
fi
elif systemctl is-active --quiet nftables; then
@@ -712,20 +814,25 @@ elif systemctl is-active --quiet nftables; then
nft list ruleset 2>&1 || true
exit 1
fi
# Verify NAT table exists
if nft list table ip openvpn-nat >/dev/null 2>&1; then
echo "PASS: nftables 'ip openvpn-nat' table exists"
else
echo "FAIL: nftables 'ip openvpn-nat' table not found"
nft list ruleset 2>&1 || true
exit 1
if [ "$ROUTE_INTERNET" = "y" ] || [ -n "$LOCAL_NETWORKS" ]; then
if nft list table ip openvpn-nat >/dev/null 2>&1 && nft list table ip openvpn-nat | grep -q "masquerade"; then
echo "PASS: nftables scoped NAT is configured"
else
echo "FAIL: nftables scoped NAT is missing"
nft list ruleset 2>&1 || true
exit 1
fi
fi
# Verify masquerade rule exists
if nft list table ip openvpn-nat | grep -q "masquerade"; then
echo "PASS: nftables masquerade rule exists"
else
echo "FAIL: nftables masquerade rule not found"
nft list table ip openvpn-nat 2>&1 || true
if [ "$ROUTE_INTERNET" = "y" ]; then
if nft list table inet openvpn | grep -q "ip daddr 10.0.0.0/8 drop"; then
echo "PASS: nftables private-network isolation is configured"
else
echo "FAIL: nftables private-network isolation is missing"
nft list table inet openvpn
exit 1
fi
elif ! nft list table inet openvpn | grep -q "ip saddr $VPN_SUBNET_IPV4/24 drop"; then
echo "FAIL: nftables split-tunnel default drop is missing"
exit 1
fi
# Verify include in nftables.conf
@@ -737,20 +844,32 @@ elif systemctl is-active --quiet nftables; then
exit 1
fi
else
# iptables mode - verify NAT rules
echo "iptables mode, checking NAT rules..."
for _ in $(seq 1 10); do
if iptables -t nat -L POSTROUTING -n | grep -q "$VPN_SUBNET_IPV4"; then
echo "PASS: NAT POSTROUTING rule for $VPN_SUBNET_IPV4/24 exists"
break
echo "iptables mode, checking policy rules..."
if [ "$ROUTE_INTERNET" = "y" ] || [ -n "$LOCAL_NETWORKS" ]; then
for _ in $(seq 1 10); do
iptables -t nat -L POSTROUTING -n | grep -q "$VPN_SUBNET_IPV4" && break
sleep 1
done
if ! iptables -t nat -L POSTROUTING -n | grep -q "$VPN_SUBNET_IPV4"; then
echo "FAIL: Expected scoped NAT rule was not found"
iptables -t nat -L POSTROUTING -n -v
exit 1
fi
sleep 1
done
if ! iptables -t nat -L POSTROUTING -n | grep -q "$VPN_SUBNET_IPV4"; then
echo "FAIL: NAT POSTROUTING rule for $VPN_SUBNET_IPV4/24 not found"
echo "Current NAT rules:"
iptables -t nat -L POSTROUTING -n -v
systemctl status iptables-openvpn 2>&1 || true
fi
if [ "$ROUTE_INTERNET" = "y" ]; then
if iptables -S OPENVPN_INSTALL_FORWARD | grep -q -- "-d 10.0.0.0/8 -j REJECT"; then
echo "PASS: iptables private-network isolation is configured"
else
echo "FAIL: iptables private-network isolation is missing"
iptables -S OPENVPN_INSTALL_FORWARD
exit 1
fi
elif ! iptables -S OPENVPN_INSTALL_FORWARD | grep -q -- "-A OPENVPN_INSTALL_FORWARD -j REJECT"; then
echo "FAIL: iptables split-tunnel default reject is missing"
exit 1
fi
if [ "$CLIENT_TO_CLIENT" = "y" ] && ! iptables -S OPENVPN_INSTALL_FORWARD | grep -q -- "-d $VPN_SUBNET_IPV4/24 -j ACCEPT"; then
echo "FAIL: iptables client-to-client allow rule is missing"
exit 1
fi
fi