mirror of
https://gitlab.com/openconnect/ocserv.git
synced 2026-08-08 09:21:48 +08:00
Added ocserv-fw for nftables
This also introduces a basic functional test for ocserv-fw. Resolves: #397 Signed-off-by: Nikos Mavrogiannopoulos <n.mavrogiannopoulos@gmail.com>
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
[codespell]
|
||||
skip = ./.git,aclocal.m4,./autom4te.cache,./build-aux,config.log,ccan,pcl,*.pem
|
||||
skip = ./.git,aclocal.m4,./autom4te.cache,./build-aux,config.log,ccan,pcl,*.pem,./src/ocserv-fw-nftables,./tests/test-fw-script
|
||||
ignore-words-list = alloced,ans,anull,bu,gir,siz,te
|
||||
uri-ignore-words-list = gord,ist
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
commands that disconnect users and invalidate their session cookies,
|
||||
preventing automatic reconnection (#689)
|
||||
- Replaced autoconf/automake build system with meson (#699)
|
||||
- Added nftables-based ocserv-fw; requires ipcalc-ng/ipcalc (#397)
|
||||
|
||||
|
||||
* Version 1.4.1 (released 2026-02-28)
|
||||
|
||||
@@ -562,9 +562,6 @@ route = 192.168.0.0/255.255.0.0
|
||||
|
||||
no-route = 192.168.5.0/255.255.255.0
|
||||
|
||||
# Note the that following two firewalling options currently are available
|
||||
# in Linux systems with iptables software.
|
||||
|
||||
# If set, the script /usr/libexec/ocserv-fw will be called to restrict
|
||||
# the user to its allowed routes and prevent him from accessing
|
||||
# any other routes. In case of defaultroute, the no-routes are restricted.
|
||||
|
||||
+10
@@ -41,6 +41,16 @@ endif
|
||||
gperf = find_program('gperf', required: false)
|
||||
protoc_c = find_program('protoc-c')
|
||||
ipcalc = find_program(['ipcalc', 'ipcalc-ng'], required: true)
|
||||
nft_prog = find_program('nft', required: false)
|
||||
|
||||
# Firewall script selection
|
||||
_fw_opt = get_option('firewall-script')
|
||||
if _fw_opt == 'auto'
|
||||
fw_script_type = nft_prog.found() ? 'nftables' : 'iptables'
|
||||
else
|
||||
fw_script_type = _fw_opt
|
||||
endif
|
||||
message('firewall script: ' + fw_script_type)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Required dependencies
|
||||
|
||||
@@ -25,3 +25,4 @@ option('tun-tests', type: 'boolean', value: true, description: 'En
|
||||
option('kerberos-tests', type: 'boolean', value: false, description: 'Enable tests requiring a Kerberos setup')
|
||||
option('with-werror', type: 'boolean', value: false, description: 'Treat compiler warnings as errors')
|
||||
option('pager', type: 'string', value: 'less', description: 'Default pager for occtl (empty string for none)')
|
||||
option('firewall-script', type: 'combo', choices: ['auto', 'nftables', 'iptables'], value: 'auto', description: 'Firewall script backend (auto prefers nftables if nft is found)')
|
||||
|
||||
+7
-1
@@ -290,7 +290,13 @@ executable('ocserv-worker', worker_sources,
|
||||
# ocserv-fw script
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
install_data('ocserv-fw',
|
||||
if fw_script_type == 'nftables'
|
||||
_fw_src = 'ocserv-fw-nftables'
|
||||
else
|
||||
_fw_src = 'ocserv-fw-iptables'
|
||||
endif
|
||||
install_data(_fw_src,
|
||||
rename: 'ocserv-fw',
|
||||
install_dir: get_option('libexecdir'),
|
||||
install_mode: 'rwxr-xr-x',
|
||||
)
|
||||
|
||||
Executable
+236
@@ -0,0 +1,236 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2026 Nikos Mavrogiannopoulos
|
||||
#
|
||||
# This file is part of ocserv.
|
||||
#
|
||||
# This file is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This file is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this file; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
# Input is from environment:
|
||||
#
|
||||
# OCSERV_RESTRICT_TO_ROUTES: If set to '1' the user should be restricted
|
||||
# to accessing the OCSERV_ROUTES and prevented from accessing
|
||||
# OCSERV_NO_ROUTES.
|
||||
#
|
||||
# OCSERV_ROUTES: A space separated list of IPv4 and IPv6 routes to
|
||||
# which the user has access. If empty or not set the
|
||||
# user has default route.
|
||||
#
|
||||
# OCSERV_ROUTES4: A version of OCSERV_ROUTES with IPv4 addresses only.
|
||||
# OCSERV_ROUTES6: A version of OCSERV_ROUTES with IPv6 addresses only.
|
||||
#
|
||||
# OCSERV_NO_ROUTES: A space separated list of IPv4 and IPv6 routes to
|
||||
# which the user has NO access.
|
||||
#
|
||||
# OCSERV_NO_ROUTES4: A version of OCSERV_NO_ROUTES with IPv4 addresses only.
|
||||
# OCSERV_NO_ROUTES6: A version of OCSERV_NO_ROUTES with IPv6 addresses only.
|
||||
#
|
||||
# OCSERV_DNS: A space-separated list of DNS servers the user has access to.
|
||||
# OCSERV_DNS4: A version of OCSERV_DNS with IPv4 addresses only.
|
||||
# OCSERV_DNS6: A version of OCSERV_DNS with IPv6 addresses only.
|
||||
#
|
||||
# OCSERV_DENY_PORTS: A space-separated list of port types and ports that the user
|
||||
# should be denied access to. An example of the format is:
|
||||
# "tcp 443 udp 312 sctp 999 icmp all esp all icmpv6 all"
|
||||
#
|
||||
# OCSERV_ALLOW_PORTS: A space-separated list of port types and ports that the user
|
||||
# should be granted access to. If set the user must be denied access
|
||||
# to any other ports. An example of the format is:
|
||||
# "tcp 443 udp 312 sctp 999 icmp all esp all icmpv6 all"
|
||||
|
||||
PATH=/sbin:/usr/sbin:$PATH
|
||||
|
||||
IPCALC=$(which ipcalc-ng 2>/dev/null)
|
||||
if test -z "${IPCALC}"; then
|
||||
IPCALC=$(which ipcalc 2>/dev/null)
|
||||
fi
|
||||
|
||||
if test -z "${IPCALC}"; then
|
||||
logger -t ocserv-fw "ipcalc or ipcalc-ng is required but not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# nft table names cannot contain hyphens, dots, or other non-identifier chars
|
||||
TABLE="ocserv_$(echo "${DEVICE}" | sed 's/[^a-zA-Z0-9_]/_/g')"
|
||||
|
||||
if test "$1" = "--removeall"; then
|
||||
_tables=$(nft list tables inet 2>/dev/null | sed -n 's/^table inet \(ocserv_[^ ]*\).*/\1/p')
|
||||
for t in $_tables; do
|
||||
nft delete table inet "$t" 2>/dev/null || true
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
execute_next_script() {
|
||||
if test -n "${OCSERV_NEXT_SCRIPT}"; then
|
||||
TMP_SCRIPT="${OCSERV_NEXT_SCRIPT}"
|
||||
unset OCSERV_NEXT_SCRIPT
|
||||
/bin/sh "${TMP_SCRIPT}"
|
||||
fi
|
||||
}
|
||||
|
||||
if test "${REASON}" = "disconnect"; then
|
||||
nft delete table inet "${TABLE}" 2>/dev/null || true
|
||||
execute_next_script
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if test "${REASON}" != "connect"; then
|
||||
logger -t ocserv-fw "unknown reason ${REASON}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -e
|
||||
|
||||
# Emit route restriction rules into the current nft chain context.
|
||||
# When OCSERV_RESTRICT_TO_ROUTES=1: denied routes are rejected, allowed routes
|
||||
# are accepted, and traffic not matching any route is rejected or accepted
|
||||
# depending on whether an explicit allow-list (OCSERV_ROUTES) was given.
|
||||
# Without route restriction, all traffic is accepted.
|
||||
emit_route_rules() {
|
||||
if test "${OCSERV_RESTRICT_TO_ROUTES}" = "1"; then
|
||||
if test -n "${OCSERV_NO_ROUTES4}"; then
|
||||
no4=$(routes_to_nft $OCSERV_NO_ROUTES4)
|
||||
printf ' iif "%s" ip daddr { %s } reject\n' "${DEVICE}" "${no4}"
|
||||
fi
|
||||
if test -n "${OCSERV_NO_ROUTES6}"; then
|
||||
no6=$(routes_to_nft $OCSERV_NO_ROUTES6)
|
||||
printf ' iif "%s" ip6 daddr { %s } reject\n' "${DEVICE}" "${no6}"
|
||||
fi
|
||||
if test -n "$OCSERV_ROUTES"; then
|
||||
if test -n "$OCSERV_ROUTES4"; then
|
||||
r4=$(routes_to_nft $OCSERV_ROUTES4)
|
||||
printf ' iif "%s" ip daddr { %s } accept\n' "${DEVICE}" "${r4}"
|
||||
fi
|
||||
if test -n "$OCSERV_ROUTES6"; then
|
||||
r6=$(routes_to_nft $OCSERV_ROUTES6)
|
||||
printf ' iif "%s" ip6 daddr { %s } accept\n' "${DEVICE}" "${r6}"
|
||||
fi
|
||||
printf ' iif "%s" reject\n' "${DEVICE}"
|
||||
else
|
||||
printf ' iif "%s" accept\n' "${DEVICE}"
|
||||
fi
|
||||
else
|
||||
printf ' iif "%s" accept\n' "${DEVICE}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Convert a route that may use a dotted-decimal subnet mask
|
||||
# (e.g. 10.0.0.0/255.0.0.0, as ocserv normalises IPv4 routes) to
|
||||
# CIDR prefix-length notation (10.0.0.0/8) required by nftables.
|
||||
# IPv6 routes and routes already in CIDR notation are passed through.
|
||||
normalize_route() {
|
||||
case "$1" in
|
||||
*/*.*.*.*) eval $(${IPCALC} -p "$1"); echo "${1%%/*}/${PREFIX}" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Convert a space-separated list of routes to a comma-separated list
|
||||
# in CIDR notation for use in nftables inline sets.
|
||||
routes_to_nft() {
|
||||
_sep=""
|
||||
for _r in $@; do
|
||||
printf '%s%s' "${_sep}" "$(normalize_route "${_r}")"
|
||||
_sep=","
|
||||
done
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Remove any leftover table for this device (must be outside the atomic block)
|
||||
nft delete table inet "${TABLE}" 2>/dev/null || true
|
||||
|
||||
# Build and apply the complete table definition atomically
|
||||
{
|
||||
printf 'table inet %s {\n chain ocserv_fwd {\n' "${TABLE}"
|
||||
# priority filter-10 ensures ocserv rules evaluate before firewalld (priority 0)
|
||||
printf ' type filter hook forward priority filter - 10; policy accept;\n'
|
||||
printf ' oif "%s" ct state established,related accept\n' "${DEVICE}"
|
||||
|
||||
# DNS — collect addresses into a single inline set per address family
|
||||
if test -n "${OCSERV_DNS4}"; then
|
||||
dns4=$(echo $OCSERV_DNS4 | sed 's/ /,/g')
|
||||
printf ' iif "%s" ip daddr { %s } udp dport 53 ct state new accept\n' \
|
||||
"${DEVICE}" "${dns4}"
|
||||
printf ' iif "%s" ip daddr { %s } tcp dport 53 ct state new,established accept\n' \
|
||||
"${DEVICE}" "${dns4}"
|
||||
fi
|
||||
if test -n "${OCSERV_DNS6}"; then
|
||||
dns6=$(echo $OCSERV_DNS6 | sed 's/ /,/g')
|
||||
printf ' iif "%s" ip6 daddr { %s } udp dport 53 ct state new accept\n' \
|
||||
"${DEVICE}" "${dns6}"
|
||||
printf ' iif "%s" ip6 daddr { %s } tcp dport 53 ct state new,established accept\n' \
|
||||
"${DEVICE}" "${dns6}"
|
||||
fi
|
||||
|
||||
# Port restrictions
|
||||
if test -n "${OCSERV_DENY_PORTS}"; then
|
||||
# Denied ports are rejected directly; route restriction follows for
|
||||
# the remaining traffic.
|
||||
set -- ${OCSERV_DENY_PORTS}
|
||||
while test $# -gt 1; do
|
||||
proto=$1
|
||||
port=$2
|
||||
case "$proto" in
|
||||
icmp) printf ' iif "%s" ip protocol icmp reject\n' "${DEVICE}" ;;
|
||||
icmpv6) printf ' iif "%s" ip6 nexthdr icmpv6 reject\n' "${DEVICE}" ;;
|
||||
esp) printf ' iif "%s" meta l4proto esp reject\n' "${DEVICE}" ;;
|
||||
*) printf ' iif "%s" meta l4proto %s th dport %s reject\n' \
|
||||
"${DEVICE}" "$proto" "$port" ;;
|
||||
esac
|
||||
shift 2
|
||||
done
|
||||
elif test -n "${OCSERV_ALLOW_PORTS}"; then
|
||||
# Allowed ports jump to the route-restriction chain; everything else
|
||||
# is rejected. This ensures route restriction (which restrict-user-to-ports
|
||||
# implies) is evaluated for each allowed port.
|
||||
set -- ${OCSERV_ALLOW_PORTS}
|
||||
while test $# -gt 1; do
|
||||
proto=$1
|
||||
port=$2
|
||||
case "$proto" in
|
||||
icmp) printf ' iif "%s" ip protocol icmp jump ocserv_rt_%s\n' "${DEVICE}" "${TABLE}" ;;
|
||||
icmpv6) printf ' iif "%s" ip6 nexthdr icmpv6 jump ocserv_rt_%s\n' "${DEVICE}" "${TABLE}" ;;
|
||||
esp) printf ' iif "%s" meta l4proto esp jump ocserv_rt_%s\n' "${DEVICE}" "${TABLE}" ;;
|
||||
*) printf ' iif "%s" meta l4proto %s th dport %s jump ocserv_rt_%s\n' \
|
||||
"${DEVICE}" "$proto" "$port" "${TABLE}" ;;
|
||||
esac
|
||||
shift 2
|
||||
done
|
||||
printf ' iif "%s" reject\n' "${DEVICE}"
|
||||
fi
|
||||
|
||||
# Route restrictions for DENY_PORTS and no-port-restriction cases.
|
||||
# For ALLOW_PORTS the route restrictions live in the ocserv_rt_${TABLE} chain below.
|
||||
if test -z "${OCSERV_ALLOW_PORTS}"; then
|
||||
emit_route_rules
|
||||
fi
|
||||
|
||||
printf ' }\n'
|
||||
|
||||
# Named chain jumped to by ALLOW_PORTS rules; applies route restriction
|
||||
# so that both port and route policies are enforced simultaneously.
|
||||
if test -n "${OCSERV_ALLOW_PORTS}"; then
|
||||
printf ' chain ocserv_rt_%s {\n' "${TABLE}"
|
||||
emit_route_rules
|
||||
printf ' }\n'
|
||||
fi
|
||||
|
||||
printf '}\n'
|
||||
} | nft -f -
|
||||
|
||||
execute_next_script
|
||||
|
||||
exit 0
|
||||
@@ -213,6 +213,7 @@ endif
|
||||
|
||||
if get_option('root-tests')
|
||||
root_scripts = [
|
||||
'test-fw-script',
|
||||
'haproxy-connect', 'test-iroute', 'test-multi-cookie',
|
||||
'test-pass-script', 'idle-timeout',
|
||||
'test-cookie-timeout', 'test-cookie-timeout-2',
|
||||
|
||||
Executable
+349
@@ -0,0 +1,349 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (C) 2026 Nikos Mavrogiannopoulos
|
||||
#
|
||||
# This file is part of ocserv.
|
||||
#
|
||||
# This file is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This file is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this file; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
# Test that ocserv-fw-iptables and/or ocserv-fw-nftables install and remove
|
||||
# firewall rules correctly for each supported feature combination.
|
||||
#
|
||||
# When FW_SCRIPT is set, only that script is tested.
|
||||
# Otherwise, all scripts for which the required tool is present are tested.
|
||||
#
|
||||
# Exits 77 (skip) only when run without root.
|
||||
|
||||
srcdir=${srcdir:-.}
|
||||
DEVICE="octest0"
|
||||
|
||||
if test "$(id -u)" != "0"; then
|
||||
echo "Skipping: requires root"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
cleanup_script() {
|
||||
local script="$1"
|
||||
REASON=disconnect DEVICE=$DEVICE sh "$script" 2>/dev/null || true
|
||||
REASON=disconnect DEVICE="${DEVICE}a" sh "$script" 2>/dev/null || true
|
||||
REASON=disconnect DEVICE="${DEVICE}b" sh "$script" 2>/dev/null || true
|
||||
sh "$script" --removeall 2>/dev/null || true
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
for script in $SCRIPTS_TO_TEST; do
|
||||
cleanup_script "$script"
|
||||
done
|
||||
ip link del "$DEVICE" 2>/dev/null || true
|
||||
ip link del "${DEVICE}a" 2>/dev/null || true
|
||||
ip link del "${DEVICE}b" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Build the list of scripts to test
|
||||
if test -n "$FW_SCRIPT"; then
|
||||
SCRIPTS_TO_TEST="$FW_SCRIPT"
|
||||
else
|
||||
SCRIPTS_TO_TEST=""
|
||||
for s in ../src/ocserv-fw-iptables ../src/ocserv-fw-nftables; do
|
||||
case "$s" in
|
||||
*nftables) tool=nft ;;
|
||||
*) tool=iptables ;;
|
||||
esac
|
||||
if command -v "$tool" >/dev/null 2>&1; then
|
||||
SCRIPTS_TO_TEST="$SCRIPTS_TO_TEST $s"
|
||||
fi
|
||||
done
|
||||
if test -z "$SCRIPTS_TO_TEST"; then
|
||||
echo "FAIL: neither iptables nor nft found"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
dump_rules() {
|
||||
case "$TOOL" in
|
||||
nft) nft list ruleset 2>/dev/null ;;
|
||||
iptables) iptables-save 2>/dev/null; ip6tables-save 2>/dev/null ;;
|
||||
esac
|
||||
}
|
||||
|
||||
check_rules_exist() {
|
||||
local desc="$1"
|
||||
local pattern="$2"
|
||||
dump_rules | grep -qE -- "$pattern" || fail "expected rule not found: $desc (pattern: $pattern)"
|
||||
}
|
||||
|
||||
check_rules_absent() {
|
||||
local desc="$1"
|
||||
local pattern="$2"
|
||||
dump_rules | grep -qE -- "$pattern" && fail "unexpected rule still present: $desc (pattern: $pattern)" || true
|
||||
}
|
||||
|
||||
run_tests() {
|
||||
# FW_SCRIPT and TOOL must be set by the caller
|
||||
|
||||
ip link add dev "$DEVICE" type dummy 2>/dev/null || true
|
||||
ip link set "$DEVICE" up
|
||||
|
||||
echo "Testing firewall script: $FW_SCRIPT (tool: $TOOL)"
|
||||
|
||||
# --- Test 1: basic connect / disconnect ---
|
||||
echo -n " Test 1: basic connect/disconnect ... "
|
||||
|
||||
REASON=connect DEVICE=$DEVICE sh "$FW_SCRIPT"
|
||||
|
||||
case "$TOOL" in
|
||||
nft)
|
||||
check_rules_exist "ESTABLISHED/RELATED rule" "ct state (established,related|related,established) accept"
|
||||
;;
|
||||
iptables)
|
||||
check_rules_exist "ESTABLISHED/RELATED rule" "RELATED,ESTABLISHED"
|
||||
;;
|
||||
esac
|
||||
|
||||
REASON=disconnect DEVICE=$DEVICE sh "$FW_SCRIPT"
|
||||
|
||||
case "$TOOL" in
|
||||
nft)
|
||||
check_rules_absent "nft table for device" "table inet ocserv_${DEVICE}"
|
||||
;;
|
||||
iptables)
|
||||
check_rules_absent "iptables rules for device" "${DEVICE}.*ocserv-fw|ocserv-fw.*${DEVICE}"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "ok"
|
||||
|
||||
# --- Test 2: DNS allow rules ---
|
||||
echo -n " Test 2: DNS rules ... "
|
||||
|
||||
REASON=connect DEVICE=$DEVICE OCSERV_DNS4="192.0.2.1" OCSERV_DNS6="2001:db8::1" sh "$FW_SCRIPT"
|
||||
|
||||
case "$TOOL" in
|
||||
nft)
|
||||
check_rules_exist "IPv4 DNS accept rule" "192\.0\.2\.1.*dport 53.*accept"
|
||||
check_rules_exist "IPv6 DNS accept rule" "2001:db8::1.*dport 53.*accept"
|
||||
;;
|
||||
iptables)
|
||||
check_rules_exist "IPv4 DNS accept rule" "192\.0\.2\.1.*--dport 53"
|
||||
check_rules_exist "IPv6 DNS accept rule" "2001:db8::1.*--dport 53"
|
||||
;;
|
||||
esac
|
||||
|
||||
REASON=disconnect DEVICE=$DEVICE sh "$FW_SCRIPT"
|
||||
echo "ok"
|
||||
|
||||
# --- Test 3: RESTRICT_TO_ROUTES=1 with explicit routes ---
|
||||
echo -n " Test 3: route restriction with explicit routes ... "
|
||||
|
||||
REASON=connect DEVICE=$DEVICE \
|
||||
OCSERV_RESTRICT_TO_ROUTES=1 \
|
||||
OCSERV_ROUTES="10.0.0.0/8 fd00::/8" \
|
||||
OCSERV_ROUTES4="10.0.0.0/8" \
|
||||
OCSERV_ROUTES6="fd00::/8" \
|
||||
sh "$FW_SCRIPT"
|
||||
|
||||
case "$TOOL" in
|
||||
nft)
|
||||
check_rules_exist "IPv4 route accept rule" "10\.0\.0\.0/8.*accept"
|
||||
check_rules_exist "IPv6 route accept rule" "fd00::/8.*accept"
|
||||
check_rules_exist "default reject rule" "iif \"${DEVICE}\" reject"
|
||||
;;
|
||||
iptables)
|
||||
check_rules_exist "IPv4 route accept rule" "10\.0\.0\.0/8.*ACCEPT"
|
||||
check_rules_exist "IPv6 route accept rule" "fd00::/8.*ACCEPT"
|
||||
check_rules_exist "default REJECT rule" "${DEVICE}.*REJECT"
|
||||
;;
|
||||
esac
|
||||
|
||||
REASON=disconnect DEVICE=$DEVICE sh "$FW_SCRIPT"
|
||||
echo "ok"
|
||||
|
||||
# --- Test 4: RESTRICT_TO_ROUTES=1 with NO_ROUTES (deny-list) ---
|
||||
echo -n " Test 4: route restriction with no-routes deny-list ... "
|
||||
|
||||
REASON=connect DEVICE=$DEVICE \
|
||||
OCSERV_RESTRICT_TO_ROUTES=1 \
|
||||
OCSERV_NO_ROUTES="10.1.0.0/16" \
|
||||
OCSERV_NO_ROUTES4="10.1.0.0/16" \
|
||||
sh "$FW_SCRIPT"
|
||||
|
||||
case "$TOOL" in
|
||||
nft)
|
||||
check_rules_exist "IPv4 no-route reject rule" "10\.1\.0\.0/16.*reject"
|
||||
check_rules_exist "allow-all fall-through" "iif \"${DEVICE}\" accept"
|
||||
;;
|
||||
iptables)
|
||||
check_rules_exist "IPv4 no-route REJECT rule" "10\.1\.0\.0/16.*REJECT"
|
||||
check_rules_exist "allow-all fall-through" "${DEVICE}.*ACCEPT"
|
||||
;;
|
||||
esac
|
||||
|
||||
REASON=disconnect DEVICE=$DEVICE sh "$FW_SCRIPT"
|
||||
echo "ok"
|
||||
|
||||
# --- Test 5: RESTRICT_TO_ROUTES=1 with no routes at all ---
|
||||
echo -n " Test 5: route restriction with no routes (allow-all) ... "
|
||||
|
||||
REASON=connect DEVICE=$DEVICE \
|
||||
OCSERV_RESTRICT_TO_ROUTES=1 \
|
||||
sh "$FW_SCRIPT"
|
||||
|
||||
case "$TOOL" in
|
||||
nft)
|
||||
check_rules_exist "allow-all fall-through" "iif \"${DEVICE}\" accept"
|
||||
;;
|
||||
iptables)
|
||||
check_rules_exist "allow-all fall-through" "${DEVICE}.*ACCEPT"
|
||||
;;
|
||||
esac
|
||||
|
||||
REASON=disconnect DEVICE=$DEVICE sh "$FW_SCRIPT"
|
||||
echo "ok"
|
||||
|
||||
# --- Test 6: RESTRICT_TO_ROUTES=1 with dotted-decimal masks (allow-list) ---
|
||||
# ocserv always normalises IPv4 routes to dotted-decimal notation
|
||||
# (e.g. 10.0.0.0/255.0.0.0) before passing them to the fw script.
|
||||
# The nftables script must convert these to CIDR before use.
|
||||
echo -n " Test 6: route restriction with dotted-decimal subnet masks (allow-list) ... "
|
||||
|
||||
REASON=connect DEVICE=$DEVICE \
|
||||
OCSERV_RESTRICT_TO_ROUTES=1 \
|
||||
OCSERV_ROUTES="10.0.0.0/255.0.0.0" \
|
||||
OCSERV_ROUTES4="10.0.0.0/255.0.0.0" \
|
||||
sh "$FW_SCRIPT"
|
||||
|
||||
case "$TOOL" in
|
||||
nft)
|
||||
check_rules_exist "IPv4 route accept rule in CIDR" "10\.0\.0\.0/8.*accept"
|
||||
check_rules_exist "default reject rule" "iif \"${DEVICE}\" reject"
|
||||
;;
|
||||
iptables)
|
||||
check_rules_exist "IPv4 route accept rule" "10\.0\.0\.0.*ACCEPT"
|
||||
check_rules_exist "default REJECT rule" "${DEVICE}.*REJECT"
|
||||
;;
|
||||
esac
|
||||
|
||||
REASON=disconnect DEVICE=$DEVICE sh "$FW_SCRIPT"
|
||||
echo "ok"
|
||||
|
||||
# --- Test 7: RESTRICT_TO_ROUTES=1 with dotted-decimal masks (deny-list) ---
|
||||
echo -n " Test 7: route restriction with dotted-decimal subnet masks (deny-list) ... "
|
||||
|
||||
REASON=connect DEVICE=$DEVICE \
|
||||
OCSERV_RESTRICT_TO_ROUTES=1 \
|
||||
OCSERV_NO_ROUTES="10.1.0.0/255.255.0.0" \
|
||||
OCSERV_NO_ROUTES4="10.1.0.0/255.255.0.0" \
|
||||
sh "$FW_SCRIPT"
|
||||
|
||||
case "$TOOL" in
|
||||
nft)
|
||||
check_rules_exist "IPv4 no-route reject rule in CIDR" "10\.1\.0\.0/16.*reject"
|
||||
check_rules_exist "allow-all fall-through" "iif \"${DEVICE}\" accept"
|
||||
;;
|
||||
iptables)
|
||||
check_rules_exist "IPv4 no-route REJECT rule" "10\.1\.0\.0.*REJECT"
|
||||
check_rules_exist "allow-all fall-through" "${DEVICE}.*ACCEPT"
|
||||
;;
|
||||
esac
|
||||
|
||||
REASON=disconnect DEVICE=$DEVICE sh "$FW_SCRIPT"
|
||||
echo "ok"
|
||||
|
||||
# --- Test 8: DENY_PORTS ---
|
||||
echo -n " Test 8: deny ports ... "
|
||||
|
||||
REASON=connect DEVICE=$DEVICE \
|
||||
OCSERV_DENY_PORTS="tcp 443" \
|
||||
sh "$FW_SCRIPT"
|
||||
|
||||
case "$TOOL" in
|
||||
nft)
|
||||
check_rules_exist "tcp port 443 reject" "dport 443 reject"
|
||||
;;
|
||||
iptables)
|
||||
check_rules_exist "tcp port 443 REJECT" "--dport 443.*REJECT"
|
||||
;;
|
||||
esac
|
||||
|
||||
REASON=disconnect DEVICE=$DEVICE sh "$FW_SCRIPT"
|
||||
echo "ok"
|
||||
|
||||
# --- Test 9: ALLOW_PORTS ---
|
||||
echo -n " Test 9: allow ports ... "
|
||||
|
||||
REASON=connect DEVICE=$DEVICE \
|
||||
OCSERV_ALLOW_PORTS="tcp 443" \
|
||||
sh "$FW_SCRIPT"
|
||||
|
||||
case "$TOOL" in
|
||||
nft)
|
||||
check_rules_exist "tcp port 443 jump to route chain" "dport 443 jump"
|
||||
check_rules_exist "default reject after allow-ports" "iif \"${DEVICE}\" reject"
|
||||
;;
|
||||
iptables)
|
||||
check_rules_exist "tcp port 443 jump to per-device chain" "--dport 443.*FORWARD-ocserv-fw-${DEVICE}"
|
||||
check_rules_exist "default REJECT after allow-ports" "${DEVICE}.*REJECT"
|
||||
;;
|
||||
esac
|
||||
|
||||
REASON=disconnect DEVICE=$DEVICE sh "$FW_SCRIPT"
|
||||
echo "ok"
|
||||
|
||||
# --- Test 10: --removeall clears all rules ---
|
||||
echo -n " Test 10: --removeall ... "
|
||||
|
||||
ip link add dev "${DEVICE}a" type dummy 2>/dev/null || true
|
||||
ip link set "${DEVICE}a" up
|
||||
ip link add dev "${DEVICE}b" type dummy 2>/dev/null || true
|
||||
ip link set "${DEVICE}b" up
|
||||
|
||||
REASON=connect DEVICE="${DEVICE}a" sh "$FW_SCRIPT"
|
||||
REASON=connect DEVICE="${DEVICE}b" sh "$FW_SCRIPT"
|
||||
|
||||
sh "$FW_SCRIPT" --removeall
|
||||
|
||||
case "$TOOL" in
|
||||
nft)
|
||||
check_rules_absent "any ocserv nft tables" "table inet ocserv_"
|
||||
;;
|
||||
iptables)
|
||||
check_rules_absent "any ocserv iptables rules" "comment ocserv-fw"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "ok"
|
||||
|
||||
# final cleanup for this script
|
||||
cleanup_script "$FW_SCRIPT"
|
||||
ip link del "${DEVICE}a" 2>/dev/null || true
|
||||
ip link del "${DEVICE}b" 2>/dev/null || true
|
||||
}
|
||||
|
||||
for FW_SCRIPT in $SCRIPTS_TO_TEST; do
|
||||
case "$FW_SCRIPT" in
|
||||
*nftables) TOOL=nft ;;
|
||||
*) TOOL=iptables ;;
|
||||
esac
|
||||
run_tests
|
||||
done
|
||||
|
||||
echo "All tests passed."
|
||||
exit 0
|
||||
Reference in New Issue
Block a user