docker part 1
This commit is contained in:
24
Docker/Containerfile
Normal file
24
Docker/Containerfile
Normal file
@@ -0,0 +1,24 @@
|
||||
FROM alpine:3.19
|
||||
|
||||
RUN apk add --no-cache \
|
||||
wireguard-tools \
|
||||
iptables \
|
||||
ip6tables \
|
||||
bash \
|
||||
curl \
|
||||
iputils-ping \
|
||||
iproute2 \
|
||||
openresolv
|
||||
|
||||
# Create wireguard config directory (config is mounted at runtime)
|
||||
RUN mkdir -p /etc/wireguard
|
||||
|
||||
# Copy entrypoint
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# Health check: can we reach the internet through the VPN?
|
||||
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
|
||||
CMD ping -c 1 -W 5 1.1.1.1 || exit 1
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
91
Docker/dispatcher.d-99-wg-routes.sh
Normal file
91
Docker/dispatcher.d-99-wg-routes.sh
Normal file
@@ -0,0 +1,91 @@
|
||||
#!/bin/bash
|
||||
|
||||
# === Configuration ===
|
||||
LOGFILE="/tmp/dispatcher.log"
|
||||
BACKUP="/tmp/dispatcher.log.1"
|
||||
MAXSIZE=$((1024 * 1024)) # 1 MB
|
||||
VPN_IFACE="nl"
|
||||
GATEWAY="192.168.178.1"
|
||||
LOCAL_IFACE="wlp4s0f0"
|
||||
ROUTE1="185.183.34.149"
|
||||
ROUTE2="192.168.178.0/24"
|
||||
|
||||
# === Log Rotation ===
|
||||
if [ -f "$LOGFILE" ] && [ "$(stat -c%s "$LOGFILE")" -ge "$MAXSIZE" ]; then
|
||||
echo "[$(date)] Log file exceeded 1MB, rotating..." >> "$LOGFILE"
|
||||
mv "$LOGFILE" "$BACKUP"
|
||||
touch "$LOGFILE"
|
||||
fi
|
||||
|
||||
# === Logging Setup ===
|
||||
exec >> "$LOGFILE" 2>&1
|
||||
echo "[$(date)] Running dispatcher for $1 with status $2"
|
||||
|
||||
IFACE="$1"
|
||||
STATUS="$2"
|
||||
|
||||
log_and_run() {
|
||||
echo "[$(date)] Executing: $*"
|
||||
if ! output=$("$@" 2>&1); then
|
||||
echo "[$(date)] ERROR: Command failed: $*"
|
||||
echo "[$(date)] Output: $output"
|
||||
else
|
||||
echo "[$(date)] Success: $*"
|
||||
fi
|
||||
}
|
||||
|
||||
# === VPN Routing Logic ===
|
||||
if [ "$IFACE" = "$VPN_IFACE" ]; then
|
||||
case "$STATUS" in
|
||||
up)
|
||||
echo "[$(date)] VPN interface is up. Preparing routes..."
|
||||
|
||||
# === Wait for local interface and gateway ===
|
||||
echo "[$(date)] Waiting for $LOCAL_IFACE (state UP) and gateway $GATEWAY (reachable)..."
|
||||
until ip link show "$LOCAL_IFACE" | grep -q "state UP" && ip route get "$GATEWAY" &>/dev/null; do
|
||||
echo "[$(date)] Waiting for $LOCAL_IFACE and $GATEWAY..."
|
||||
sleep 1
|
||||
done
|
||||
echo "[$(date)] Local interface and gateway are ready."
|
||||
# === End Wait ===
|
||||
|
||||
# === APPLY ROUTES (Corrected Order) ===
|
||||
|
||||
# 1. Add the route for the local network FIRST
|
||||
log_and_run /sbin/ip route replace "$ROUTE2" dev "$LOCAL_IFACE"
|
||||
|
||||
# 2. Add the route to the VPN endpoint via the gateway SECOND
|
||||
log_and_run /sbin/ip route replace "$ROUTE1" via "$GATEWAY" dev "$LOCAL_IFACE"
|
||||
|
||||
# === END APPLY ROUTES ===
|
||||
|
||||
# Log interface and WireGuard status
|
||||
echo "[$(date)] --- ip addr show $VPN_IFACE ---"
|
||||
ip addr show "$VPN_IFACE"
|
||||
echo "[$(date)] --- wg show $VPN_IFACE ---"
|
||||
wg show "$VPN_IFACE"
|
||||
|
||||
;;
|
||||
|
||||
down)
|
||||
echo "[$(date)] VPN interface is down. Verifying before removing routes..."
|
||||
|
||||
# Log interface and WireGuard status
|
||||
echo "[$(date)] --- ip addr show $VPN_IFACE ---"
|
||||
ip addr show "$VPN_IFACE"
|
||||
echo "[$(date)] --- wg show $VPN_IFACE ---"
|
||||
wg show "$VPN_IFACE"
|
||||
|
||||
# Delay and confirm interface is still down
|
||||
sleep 5
|
||||
if ip link show "$VPN_IFACE" | grep -q "state UP"; then
|
||||
echo "[$(date)] VPN interface is still up. Skipping route removal."
|
||||
else
|
||||
echo "[$(date)] Confirmed VPN is down. Removing routes..."
|
||||
# It's good practice to remove them in reverse order, too.
|
||||
log_and_run /sbin/ip route del "$ROUTE1" via "$GATEWAY" dev "$LOCAL_IFACE"
|
||||
log_and_run /sbin/ip route del "$ROUTE2" dev "$LOCAL_IFACE"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
212
Docker/entrypoint.sh
Normal file
212
Docker/entrypoint.sh
Normal file
@@ -0,0 +1,212 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
INTERFACE="wg0"
|
||||
MOUNT_CONFIG="/etc/wireguard/${INTERFACE}.conf"
|
||||
CONFIG_DIR="/run/wireguard"
|
||||
CONFIG_FILE="${CONFIG_DIR}/${INTERFACE}.conf"
|
||||
CHECK_INTERVAL="${HEALTH_CHECK_INTERVAL:-10}"
|
||||
CHECK_HOST="${HEALTH_CHECK_HOST:-1.1.1.1}"
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Validate config exists, copy to writable location
|
||||
# ──────────────────────────────────────────────
|
||||
if [ ! -f "$MOUNT_CONFIG" ]; then
|
||||
echo "[error] WireGuard config not found at ${MOUNT_CONFIG}"
|
||||
echo "[error] Mount your config file: -v /path/to/your.conf:/etc/wireguard/wg0.conf:ro"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
cp "$MOUNT_CONFIG" "$CONFIG_FILE"
|
||||
chmod 600 "$CONFIG_FILE"
|
||||
|
||||
# Extract endpoint IP and port from the config
|
||||
VPN_ENDPOINT=$(grep -i '^Endpoint' "$CONFIG_FILE" | head -1 | sed 's/.*= *//;s/:.*//;s/ //g')
|
||||
VPN_PORT=$(grep -i '^Endpoint' "$CONFIG_FILE" | head -1 | sed 's/.*://;s/ //g')
|
||||
# Extract address
|
||||
VPN_ADDRESS=$(grep -i '^Address' "$CONFIG_FILE" | head -1 | sed 's/.*= *//;s/ //g')
|
||||
|
||||
if [ -z "$VPN_ENDPOINT" ] || [ -z "$VPN_PORT" ]; then
|
||||
echo "[error] Could not parse Endpoint from ${CONFIG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[init] Config: ${CONFIG_FILE}"
|
||||
echo "[init] Endpoint: ${VPN_ENDPOINT}:${VPN_PORT}"
|
||||
echo "[init] Address: ${VPN_ADDRESS}"
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Kill switch: only allow traffic through wg0
|
||||
# ──────────────────────────────────────────────
|
||||
setup_killswitch() {
|
||||
echo "[killswitch] Setting up iptables kill switch..."
|
||||
|
||||
# Flush existing rules
|
||||
iptables -F
|
||||
iptables -X
|
||||
iptables -t nat -F
|
||||
|
||||
# Default policy: DROP everything
|
||||
iptables -P INPUT DROP
|
||||
iptables -P FORWARD DROP
|
||||
iptables -P OUTPUT DROP
|
||||
|
||||
# Allow loopback
|
||||
iptables -A INPUT -i lo -j ACCEPT
|
||||
iptables -A OUTPUT -o lo -j ACCEPT
|
||||
|
||||
# Allow traffic to/from VPN endpoint (needed to establish tunnel)
|
||||
iptables -A OUTPUT -d "$VPN_ENDPOINT" -p udp --dport "$VPN_PORT" -j ACCEPT
|
||||
iptables -A INPUT -s "$VPN_ENDPOINT" -p udp --sport "$VPN_PORT" -j ACCEPT
|
||||
|
||||
# Allow all traffic through the WireGuard interface
|
||||
iptables -A INPUT -i "$INTERFACE" -j ACCEPT
|
||||
iptables -A OUTPUT -o "$INTERFACE" -j ACCEPT
|
||||
|
||||
# Allow DNS to the VPN DNS server (through wg0)
|
||||
iptables -A OUTPUT -o "$INTERFACE" -p udp --dport 53 -j ACCEPT
|
||||
iptables -A OUTPUT -o "$INTERFACE" -p tcp --dport 53 -j ACCEPT
|
||||
|
||||
# Allow DHCP (for container networking)
|
||||
iptables -A OUTPUT -p udp --dport 67:68 -j ACCEPT
|
||||
iptables -A INPUT -p udp --sport 67:68 -j ACCEPT
|
||||
|
||||
# Allow established/related connections
|
||||
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
# ── Allow incoming connections to exposed service ports (e.g. app on 8000) ──
|
||||
# LOCAL_PORTS can be set as env var, e.g. "8000,8080,3000"
|
||||
if [ -n "${LOCAL_PORTS:-}" ]; then
|
||||
for port in $(echo "$LOCAL_PORTS" | tr ',' ' '); do
|
||||
echo "[killswitch] Allowing incoming traffic on port ${port}"
|
||||
iptables -A INPUT -p tcp --dport "$port" -j ACCEPT
|
||||
iptables -A OUTPUT -p tcp --sport "$port" -j ACCEPT
|
||||
done
|
||||
fi
|
||||
|
||||
# ── FORWARDING (so other containers can use this VPN) ──
|
||||
iptables -A FORWARD -i eth0 -o "$INTERFACE" -j ACCEPT
|
||||
iptables -A FORWARD -i "$INTERFACE" -o eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
# NAT: masquerade traffic from other containers going out through wg0
|
||||
iptables -t nat -A POSTROUTING -o "$INTERFACE" -j MASQUERADE
|
||||
|
||||
echo "[killswitch] Kill switch active. Traffic blocked if VPN drops."
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Enable IP forwarding so other containers can route through us
|
||||
# ──────────────────────────────────────────────
|
||||
enable_forwarding() {
|
||||
echo "[init] Enabling IP forwarding..."
|
||||
if echo 1 > /proc/sys/net/ipv4/ip_forward 2>/dev/null; then
|
||||
echo "[init] IP forwarding enabled via /proc."
|
||||
else
|
||||
echo "[init] /proc read-only — relying on --sysctl net.ipv4.ip_forward=1"
|
||||
fi
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Start WireGuard manually (no wg-quick, avoids sysctl issues)
|
||||
# ──────────────────────────────────────────────
|
||||
start_vpn() {
|
||||
echo "[vpn] Starting WireGuard interface ${INTERFACE}..."
|
||||
|
||||
# Create the interface
|
||||
ip link add "$INTERFACE" type wireguard
|
||||
|
||||
# Apply the WireGuard config (keys, peer, endpoint)
|
||||
wg setconf "$INTERFACE" <(grep -v -i '^\(Address\|DNS\|MTU\|Table\|PreUp\|PostUp\|PreDown\|PostDown\|SaveConfig\)' "$CONFIG_FILE")
|
||||
|
||||
# Assign the address
|
||||
ip -4 address add "$VPN_ADDRESS" dev "$INTERFACE"
|
||||
|
||||
# Set MTU
|
||||
ip link set mtu 1420 up dev "$INTERFACE"
|
||||
|
||||
# Find default gateway/interface for the endpoint route
|
||||
DEFAULT_GW=$(ip route | grep '^default' | head -1 | awk '{print $3}')
|
||||
DEFAULT_IF=$(ip route | grep '^default' | head -1 | awk '{print $5}')
|
||||
|
||||
# Route VPN endpoint through the container's default gateway
|
||||
if [ -n "$DEFAULT_GW" ] && [ -n "$DEFAULT_IF" ]; then
|
||||
ip route add "$VPN_ENDPOINT/32" via "$DEFAULT_GW" dev "$DEFAULT_IF" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Route all traffic through the WireGuard tunnel
|
||||
ip route add 0.0.0.0/1 dev "$INTERFACE"
|
||||
ip route add 128.0.0.0/1 dev "$INTERFACE"
|
||||
|
||||
# Set up DNS
|
||||
VPN_DNS=$(grep -i '^DNS' "$CONFIG_FILE" | head -1 | sed 's/.*= *//;s/ //g')
|
||||
if [ -n "$VPN_DNS" ]; then
|
||||
echo "nameserver $VPN_DNS" > /etc/resolv.conf
|
||||
echo "[vpn] DNS set to ${VPN_DNS}"
|
||||
fi
|
||||
|
||||
echo "[vpn] WireGuard interface ${INTERFACE} is up."
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Stop WireGuard manually
|
||||
# ──────────────────────────────────────────────
|
||||
stop_vpn() {
|
||||
echo "[vpn] Stopping WireGuard interface ${INTERFACE}..."
|
||||
ip link del "$INTERFACE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Health check loop — restarts VPN if tunnel dies
|
||||
# ──────────────────────────────────────────────
|
||||
health_loop() {
|
||||
local failures=0
|
||||
local max_failures=3
|
||||
|
||||
echo "[health] Starting health check (every ${CHECK_INTERVAL}s, target ${CHECK_HOST})..."
|
||||
|
||||
while true; do
|
||||
sleep "$CHECK_INTERVAL"
|
||||
|
||||
if ping -c 1 -W 5 "$CHECK_HOST" > /dev/null 2>&1; then
|
||||
if [ "$failures" -gt 0 ]; then
|
||||
echo "[health] VPN recovered."
|
||||
failures=0
|
||||
fi
|
||||
else
|
||||
failures=$((failures + 1))
|
||||
echo "[health] Ping failed ($failures/$max_failures)"
|
||||
|
||||
if [ "$failures" -ge "$max_failures" ]; then
|
||||
echo "[health] VPN appears down. Restarting WireGuard..."
|
||||
stop_vpn
|
||||
sleep 2
|
||||
start_vpn
|
||||
failures=0
|
||||
echo "[health] WireGuard restarted."
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Graceful shutdown
|
||||
# ──────────────────────────────────────────────
|
||||
cleanup() {
|
||||
echo "[shutdown] Stopping WireGuard..."
|
||||
stop_vpn
|
||||
echo "[shutdown] Flushing iptables..."
|
||||
iptables -F
|
||||
iptables -t nat -F
|
||||
echo "[shutdown] Done."
|
||||
exit 0
|
||||
}
|
||||
|
||||
trap cleanup SIGTERM SIGINT
|
||||
|
||||
# ── Main ──
|
||||
enable_forwarding
|
||||
setup_killswitch
|
||||
start_vpn
|
||||
health_loop
|
||||
17
Docker/nl.conf
Normal file
17
Docker/nl.conf
Normal file
@@ -0,0 +1,17 @@
|
||||
[Interface]
|
||||
PrivateKey = iO5spIue/6ciwUoR95hYtuxdtQxV/Q9EOoQ/jHe18kM=
|
||||
Address = 10.2.0.2/32
|
||||
DNS = 10.2.0.1
|
||||
|
||||
# Route zum VPN-Server direkt über dein lokales Netz
|
||||
PostUp = ip route add 185.183.34.149 via 192.168.178.1 dev wlp4s0f0
|
||||
PostUp = ip route add 192.168.178.0/24 via 192.168.178.1 dev wlp4s0f0
|
||||
PostDown = ip route del 185.183.34.149 via 192.168.178.1 dev wlp4s0f0
|
||||
PostDown = ip route del 192.168.178.0/24 via 192.168.178.1 dev wlp4s0f0
|
||||
|
||||
[Peer]
|
||||
PublicKey = J4XVdtoBVc/EoI2Yk673Oes97WMnQSH5KfamZNjtM2s=
|
||||
AllowedIPs = 0.0.0.0/1, 128.0.0.0/1
|
||||
Endpoint = 185.183.34.149:51820
|
||||
|
||||
|
||||
37
Docker/podman-compose.yml
Normal file
37
Docker/podman-compose.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
services:
|
||||
vpn:
|
||||
build: .
|
||||
container_name: vpn-wireguard
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
sysctls:
|
||||
- net.ipv4.ip_forward=1
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
volumes:
|
||||
- ./wg0.conf:/etc/wireguard/wg0.conf:ro
|
||||
- /lib/modules:/lib/modules:ro
|
||||
ports:
|
||||
# Expose app's port 8000 to the local network through the VPN container
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- HEALTH_CHECK_INTERVAL=10
|
||||
- HEALTH_CHECK_HOST=1.1.1.1
|
||||
- LOCAL_PORTS=8000
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "ping", "-c", "1", "-W", "5", "1.1.1.1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
app:
|
||||
image: python:3.12-alpine
|
||||
container_name: vpn-app
|
||||
# Share the VPN container's network — all outgoing traffic goes through WireGuard
|
||||
network_mode: "service:vpn"
|
||||
depends_on:
|
||||
vpn:
|
||||
condition: service_healthy
|
||||
# Example: simple HTTP server on port 8000. Replace with your actual app.
|
||||
command: ["python3", "-m", "http.server", "8000"]
|
||||
185
Docker/test_vpn.py
Normal file
185
Docker/test_vpn.py
Normal file
@@ -0,0 +1,185 @@
|
||||
"""
|
||||
Integration test for the WireGuard VPN Podman image.
|
||||
|
||||
Verifies:
|
||||
1. The image builds successfully.
|
||||
2. The container starts and becomes healthy.
|
||||
3. The public IP inside the VPN differs from the host IP.
|
||||
4. Kill switch blocks traffic when WireGuard is down.
|
||||
|
||||
Requirements:
|
||||
- podman installed
|
||||
- Root/sudo (NET_ADMIN capability)
|
||||
- A valid WireGuard config at ./wg0.conf (or ./nl.conf)
|
||||
|
||||
Usage:
|
||||
sudo python3 -m pytest test_vpn.py -v
|
||||
# or
|
||||
sudo python3 test_vpn.py
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import unittest
|
||||
import os
|
||||
|
||||
IMAGE_NAME = "vpn-wireguard-test"
|
||||
CONTAINER_NAME = "vpn-test-container"
|
||||
CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "wg0.conf")
|
||||
BUILD_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
IP_CHECK_URL = "https://ifconfig.me"
|
||||
STARTUP_TIMEOUT = 30 # seconds to wait for VPN to come up
|
||||
HEALTH_POLL_INTERVAL = 2 # seconds between health checks
|
||||
|
||||
|
||||
def run(cmd: list[str], timeout: int = 30, check: bool = True) -> subprocess.CompletedProcess:
|
||||
"""Run a command and return the result."""
|
||||
return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, check=check)
|
||||
|
||||
|
||||
def get_host_ip() -> str:
|
||||
"""Get the public IP of the host machine."""
|
||||
result = run(["curl", "-s", "--max-time", "10", IP_CHECK_URL])
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def podman_exec(container: str, cmd: list[str], timeout: int = 15) -> subprocess.CompletedProcess:
|
||||
"""Execute a command inside a running container."""
|
||||
return run(["podman", "exec", container] + cmd, timeout=timeout, check=False)
|
||||
|
||||
|
||||
class TestVPNImage(unittest.TestCase):
|
||||
"""Test suite for the WireGuard VPN container."""
|
||||
|
||||
host_ip: str = ""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Build image, get host IP, start container, wait for VPN."""
|
||||
# Clean up any leftover container from a previous run
|
||||
subprocess.run(
|
||||
["podman", "rm", "-f", CONTAINER_NAME],
|
||||
capture_output=True, check=False,
|
||||
)
|
||||
|
||||
# ── 1. Get host public IP before VPN ──
|
||||
print("\n[setup] Fetching host public IP...")
|
||||
cls.host_ip = get_host_ip()
|
||||
print(f"[setup] Host public IP: {cls.host_ip}")
|
||||
assert cls.host_ip, "Could not determine host public IP"
|
||||
|
||||
# ── 2. Build the image ──
|
||||
print(f"[setup] Building image '{IMAGE_NAME}'...")
|
||||
result = run(
|
||||
["podman", "build", "-t", IMAGE_NAME, BUILD_DIR],
|
||||
timeout=180,
|
||||
)
|
||||
print(result.stdout[-500:] if len(result.stdout) > 500 else result.stdout)
|
||||
assert result.returncode == 0, f"Build failed:\n{result.stderr}"
|
||||
print("[setup] Image built successfully.")
|
||||
|
||||
# ── 3. Start the container ──
|
||||
print(f"[setup] Starting container '{CONTAINER_NAME}'...")
|
||||
result = run(
|
||||
[
|
||||
"podman", "run", "-d",
|
||||
"--name", CONTAINER_NAME,
|
||||
"--cap-add=NET_ADMIN",
|
||||
"--cap-add=SYS_MODULE",
|
||||
"--sysctl", "net.ipv4.ip_forward=1",
|
||||
"-v", f"{CONFIG_FILE}:/etc/wireguard/wg0.conf:ro",
|
||||
"-v", "/lib/modules:/lib/modules:ro",
|
||||
IMAGE_NAME,
|
||||
],
|
||||
timeout=30,
|
||||
check=False,
|
||||
)
|
||||
assert result.returncode == 0, f"Container failed to start:\n{result.stderr}"
|
||||
cls.container_id = result.stdout.strip()
|
||||
print(f"[setup] Container started: {cls.container_id[:12]}")
|
||||
|
||||
# Verify it's running
|
||||
inspect = run(
|
||||
["podman", "inspect", "-f", "{{.State.Running}}", CONTAINER_NAME],
|
||||
check=False,
|
||||
)
|
||||
assert inspect.stdout.strip() == "true", "Container is not running"
|
||||
|
||||
# ── 4. Wait for VPN to come up ──
|
||||
print(f"[setup] Waiting up to {STARTUP_TIMEOUT}s for VPN tunnel...")
|
||||
vpn_up = cls._wait_for_vpn_cls(STARTUP_TIMEOUT)
|
||||
assert vpn_up, f"VPN did not come up within {STARTUP_TIMEOUT}s"
|
||||
print("[setup] VPN tunnel is up. Running tests.\n")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""Stop and remove the container."""
|
||||
print("\n[teardown] Cleaning up...")
|
||||
subprocess.run(["podman", "rm", "-f", CONTAINER_NAME], capture_output=True, check=False)
|
||||
print("[teardown] Done.")
|
||||
|
||||
@classmethod
|
||||
def _wait_for_vpn_cls(cls, timeout: int = STARTUP_TIMEOUT) -> bool:
|
||||
"""Wait until the VPN tunnel is up (can reach the internet)."""
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
result = podman_exec(CONTAINER_NAME, ["ping", "-c", "1", "-W", "3", "1.1.1.1"])
|
||||
if result.returncode == 0:
|
||||
return True
|
||||
time.sleep(HEALTH_POLL_INTERVAL)
|
||||
return False
|
||||
|
||||
def _get_vpn_ip(self) -> str:
|
||||
"""Get the public IP as seen from inside the container."""
|
||||
result = podman_exec(
|
||||
CONTAINER_NAME,
|
||||
["curl", "-s", "--max-time", "10", IP_CHECK_URL],
|
||||
timeout=20,
|
||||
)
|
||||
return result.stdout.strip()
|
||||
|
||||
# ── Tests ────────────────────────────────────────────────
|
||||
|
||||
def test_01_ip_differs_from_host(self):
|
||||
"""Public IP inside VPN is different from host IP."""
|
||||
vpn_ip = self._get_vpn_ip()
|
||||
print(f"\n[test] VPN public IP: {vpn_ip}")
|
||||
print(f"[test] Host public IP: {self.host_ip}")
|
||||
|
||||
self.assertTrue(vpn_ip, "Could not fetch IP from inside the container")
|
||||
self.assertNotEqual(
|
||||
vpn_ip,
|
||||
self.host_ip,
|
||||
f"VPN IP ({vpn_ip}) is the same as host IP — VPN is not working!",
|
||||
)
|
||||
|
||||
def test_02_wireguard_interface_exists(self):
|
||||
"""The wg0 interface is present in the container."""
|
||||
result = podman_exec(CONTAINER_NAME, ["wg", "show", "wg0"])
|
||||
self.assertEqual(result.returncode, 0, f"wg show failed:\n{result.stderr}")
|
||||
self.assertIn("peer", result.stdout.lower(), "No peer information in wg show output")
|
||||
|
||||
def test_03_kill_switch_blocks_traffic(self):
|
||||
"""When WireGuard is down, traffic is blocked (kill switch)."""
|
||||
# Bring down the WireGuard interface by deleting it
|
||||
down_result = podman_exec(CONTAINER_NAME, ["ip", "link", "del", "wg0"], timeout=10)
|
||||
self.assertEqual(down_result.returncode, 0, f"ip link del wg0 failed:\n{down_result.stderr}")
|
||||
|
||||
# Give iptables a moment
|
||||
time.sleep(2)
|
||||
|
||||
# Try to reach the internet — should fail due to kill switch
|
||||
result = podman_exec(
|
||||
CONTAINER_NAME,
|
||||
["curl", "-s", "--max-time", "5", IP_CHECK_URL],
|
||||
timeout=10,
|
||||
)
|
||||
self.assertNotEqual(
|
||||
result.returncode, 0,
|
||||
"Traffic went through even with WireGuard down — kill switch is NOT working!",
|
||||
)
|
||||
print("\n[test] Kill switch confirmed: traffic blocked with VPN down")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
10
Docker/wg0.conf
Normal file
10
Docker/wg0.conf
Normal file
@@ -0,0 +1,10 @@
|
||||
[Interface]
|
||||
PrivateKey = iO5spIue/6ciwUoR95hYtuxdtQxV/Q9EOoQ/jHe18kM=
|
||||
Address = 10.2.0.2/32
|
||||
DNS = 10.2.0.1
|
||||
|
||||
[Peer]
|
||||
PublicKey = J4XVdtoBVc/EoI2Yk673Oes97WMnQSH5KfamZNjtM2s=
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
Endpoint = 185.183.34.149:51820
|
||||
PersistentKeepalive = 25
|
||||
Reference in New Issue
Block a user