diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index fa6a16d..2ac72d6 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -17,7 +17,7 @@ permissions: jobs: docker-test: runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 20 strategy: fail-fast: false matrix: @@ -134,11 +134,12 @@ jobs: --device=/dev/net/tun:/dev/net/tun \ --network vpn-test \ --ip 172.28.0.20 \ - -v shared-config:/shared:ro \ + -v shared-config:/shared \ openvpn-client & # Wait for tests to complete (look for success message) - for i in {1..60}; do + # Extended timeout for revocation e2e tests + for i in {1..180}; do if docker logs openvpn-client 2>&1 | grep -q "ALL TESTS PASSED" then echo "Tests passed!" @@ -149,7 +150,7 @@ jobs: docker logs openvpn-client exit 1 fi - echo "Waiting for tests... ($i/60)" + echo "Waiting for tests... ($i/180)" sleep 2 done diff --git a/Makefile b/Makefile index 7352a6b..c73f669 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # Run the full test suite test: test-build test-up @echo "Waiting for tests to complete..." - @for i in $$(seq 1 60); do \ + @for i in $$(seq 1 180); do \ if docker logs openvpn-client 2>&1 | grep -q "ALL TESTS PASSED"; then \ echo "✓ Tests passed!"; \ $(MAKE) test-down; \ @@ -15,7 +15,7 @@ test: test-build test-up $(MAKE) test-down; \ exit 1; \ fi; \ - echo "Waiting... ($$i/60)"; \ + echo "Waiting... ($$i/180)"; \ sleep 2; \ done; \ echo "Timeout waiting for tests"; \ diff --git a/docker-compose.yml b/docker-compose.yml index 094c96d..1147a4c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,7 @@ services: devices: - /dev/net/tun:/dev/net/tun volumes: - - shared-config:/shared:ro + - shared-config:/shared networks: vpn-test: ipv4_address: 172.28.0.20 diff --git a/test/client-entrypoint.sh b/test/client-entrypoint.sh index 69b779b..4dba5bf 100755 --- a/test/client-entrypoint.sh +++ b/test/client-entrypoint.sh @@ -103,10 +103,265 @@ else exit 1 fi +echo "" +echo "=== Initial connectivity tests PASSED ===" + +# Signal server that initial tests passed +touch /shared/initial-tests-passed + +# ===================================================== +# Certificate Revocation E2E Tests +# ===================================================== +echo "" +echo "=== Starting Certificate Revocation E2E Tests ===" + +REVOKE_CLIENT="revoketest" + +# Wait for revoke test client config +echo "Waiting for revoke test client config..." +MAX_WAIT=120 +WAITED=0 +while [ ! -f /shared/revoke-client-config-ready ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for revoke test config... ($WAITED/$MAX_WAIT seconds)" +done + +if [ ! -f /shared/revoke-client-config-ready ]; then + echo "FAIL: Revoke test client config not ready in time" + exit 1 +fi + +if [ ! -f "/shared/$REVOKE_CLIENT.ovpn" ]; then + echo "FAIL: Revoke test client config file not found" + exit 1 +fi + +echo "Revoke test client config found!" + +# Disconnect current VPN (testclient) before connecting with revoke test client +echo "Disconnecting current VPN connection..." +pkill openvpn || true +sleep 2 + +# Connect with revoke test client +echo "Connecting with '$REVOKE_CLIENT' certificate..." +openvpn --config "/shared/$REVOKE_CLIENT.ovpn" --daemon --log /var/log/openvpn-revoke.log + +# Wait for connection +echo "Waiting for VPN connection with revoke test client..." +MAX_WAIT=60 +WAITED=0 +while ! ip addr show tun0 2>/dev/null | grep -q "inet " && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for tun0... ($WAITED/$MAX_WAIT seconds)" + if [ -f /var/log/openvpn-revoke.log ]; then + tail -3 /var/log/openvpn-revoke.log + fi +done + +if ! ip addr show tun0 2>/dev/null | grep -q "inet "; then + echo "FAIL: VPN connection with revoke test client failed" + cat /var/log/openvpn-revoke.log || true + exit 1 +fi + +echo "PASS: Connected with '$REVOKE_CLIENT' certificate" +ip addr show tun0 + +# Verify connectivity +if ping -c 2 10.8.0.1 >/dev/null 2>&1; then + echo "PASS: Can ping VPN gateway with revoke test client" +else + echo "FAIL: Cannot ping VPN gateway with revoke test client" + exit 1 +fi + +# Signal server that we're connected with revoke test client +touch /shared/revoke-client-connected + +# Wait for server to signal us to disconnect +echo "Waiting for server to signal disconnect..." +MAX_WAIT=60 +WAITED=0 +while [ ! -f /shared/revoke-client-disconnect ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) +done + +if [ ! -f /shared/revoke-client-disconnect ]; then + echo "FAIL: Server did not signal disconnect" + exit 1 +fi + +# Disconnect +echo "Disconnecting revoke test client..." +pkill openvpn || true + +# Wait for openvpn to fully exit and tun0 to be released +WAITED=0 +MAX_WAIT_DISCONNECT=10 +while (pgrep openvpn >/dev/null || ip addr show tun0 2>/dev/null | grep -q "inet ") && [ $WAITED -lt $MAX_WAIT_DISCONNECT ]; do + sleep 1 + WAITED=$((WAITED + 1)) +done + +# Verify disconnected +if ip addr show tun0 2>/dev/null | grep -q "inet "; then + echo "FAIL: tun0 still has IP after disconnect" + exit 1 +fi +echo "PASS: Disconnected successfully" + +# Signal server that we're disconnected +touch /shared/revoke-client-disconnected + +# Wait for server to revoke the certificate and signal us to reconnect +echo "Waiting for server to revoke certificate and signal reconnect..." +MAX_WAIT=60 +WAITED=0 +while [ ! -f /shared/revoke-try-reconnect ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) +done + +if [ ! -f /shared/revoke-try-reconnect ]; then + echo "FAIL: Server did not signal to try reconnect" + exit 1 +fi + +# Try to reconnect with the now-revoked certificate (should fail) +echo "Attempting to reconnect with revoked certificate (should fail)..." +rm -f /var/log/openvpn-revoke-fail.log +openvpn --config "/shared/$REVOKE_CLIENT.ovpn" --daemon --log /var/log/openvpn-revoke-fail.log + +# Wait and check if connection fails +# The connection should fail due to certificate being revoked +echo "Waiting to verify connection is rejected..." +CONNECT_FAILED=false +MAX_WAIT=30 +WAITED=0 +while [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + + # Check if tun0 came up (would mean revocation didn't work) + if ip addr show tun0 2>/dev/null | grep -q "inet "; then + echo "FAIL: Connection succeeded with revoked certificate!" + cat /var/log/openvpn-revoke-fail.log || true + exit 1 + fi + + # Check for certificate verification failure in log + if [ -f /var/log/openvpn-revoke-fail.log ]; then + if grep -qi "certificate verify failed\|TLS Error\|AUTH_FAILED\|certificate revoked" /var/log/openvpn-revoke-fail.log; then + echo "Connection correctly rejected (certificate revoked)" + CONNECT_FAILED=true + break + fi + fi + + echo "Checking connection status... ($WAITED/$MAX_WAIT seconds)" + if [ -f /var/log/openvpn-revoke-fail.log ]; then + tail -3 /var/log/openvpn-revoke-fail.log + fi +done + +# Kill any remaining openvpn process +pkill openvpn 2>/dev/null || true +sleep 1 + +# Even if we didn't see explicit error, verify tun0 is not up +if ip addr show tun0 2>/dev/null | grep -q "inet "; then + echo "FAIL: tun0 interface exists - revoked cert may have connected" + exit 1 +fi + +if [ "$CONNECT_FAILED" = true ]; then + echo "PASS: Connection with revoked certificate was correctly rejected" +else + echo "PASS: Connection with revoked certificate did not succeed (no tun0)" + echo "OpenVPN log:" + cat /var/log/openvpn-revoke-fail.log || true +fi + +# Signal server that reconnect with revoked cert failed +touch /shared/revoke-reconnect-failed + +# ===================================================== +# Test connecting with new certificate (same name) +# ===================================================== +echo "" +echo "=== Testing connection with recreated certificate ===" + +# Wait for server to create new cert and signal us +echo "Waiting for new client config with same name..." +MAX_WAIT=120 +WAITED=0 +while [ ! -f /shared/new-client-config-ready ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for new config... ($WAITED/$MAX_WAIT seconds)" +done + +if [ ! -f /shared/new-client-config-ready ]; then + echo "FAIL: New client config not ready in time" + exit 1 +fi + +if [ ! -f "/shared/$REVOKE_CLIENT-new.ovpn" ]; then + echo "FAIL: New client config file not found" + exit 1 +fi + +echo "New client config found!" + +# Connect with the new certificate +echo "Connecting with new '$REVOKE_CLIENT' certificate..." +rm -f /var/log/openvpn-new.log +openvpn --config "/shared/$REVOKE_CLIENT-new.ovpn" --daemon --log /var/log/openvpn-new.log + +# Wait for connection +echo "Waiting for VPN connection with new certificate..." +MAX_WAIT=60 +WAITED=0 +while ! ip addr show tun0 2>/dev/null | grep -q "inet " && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for tun0... ($WAITED/$MAX_WAIT seconds)" + if [ -f /var/log/openvpn-new.log ]; then + tail -3 /var/log/openvpn-new.log + fi +done + +if ! ip addr show tun0 2>/dev/null | grep -q "inet "; then + echo "FAIL: VPN connection with new certificate failed" + cat /var/log/openvpn-new.log || true + exit 1 +fi + +echo "PASS: Connected with new '$REVOKE_CLIENT' certificate" +ip addr show tun0 + +# Verify connectivity +if ping -c 2 10.8.0.1 >/dev/null 2>&1; then + echo "PASS: Can ping VPN gateway with new certificate" +else + echo "FAIL: Cannot ping VPN gateway with new certificate" + exit 1 +fi + +# Signal server that we connected with new cert +touch /shared/new-client-connected + +echo "" +echo "=== Certificate Revocation E2E Tests PASSED ===" + echo "" echo "==========================================" echo " ALL TESTS PASSED!" echo "==========================================" # Keep container running for debugging if needed -exec tail -f /var/log/openvpn.log +exec tail -f /var/log/openvpn-new.log 2>/dev/null || tail -f /var/log/openvpn.log 2>/dev/null || sleep infinity diff --git a/test/server-entrypoint.sh b/test/server-entrypoint.sh index 527c258..133f3c2 100755 --- a/test/server-entrypoint.sh +++ b/test/server-entrypoint.sh @@ -346,6 +346,228 @@ if [ "$(cat /proc/sys/net/ipv4/ip_forward)" != "1" ]; then } fi -# Start OpenVPN in foreground (run from /etc/openvpn so relative paths work) +# Start OpenVPN in background (run from /etc/openvpn so relative paths work) cd /etc/openvpn -exec openvpn --config /etc/openvpn/server.conf +openvpn --config /etc/openvpn/server.conf --log /var/log/openvpn-server.log & +OPENVPN_PID=$! + +# Wait for OpenVPN to start +echo "Waiting for OpenVPN server to start..." +for _ in $(seq 1 30); do + if pgrep -f "openvpn --config" >/dev/null; then + echo "OpenVPN server started (PID: $OPENVPN_PID)" + break + fi + sleep 1 +done + +if ! pgrep -f "openvpn --config" >/dev/null; then + echo "FAIL: OpenVPN server failed to start" + cat /var/log/openvpn-server.log || true + exit 1 +fi + +# ===================================================== +# Wait for initial client tests to complete +# ===================================================== +echo "" +echo "=== Waiting for initial client connectivity tests ===" +MAX_WAIT=120 +WAITED=0 +while [ ! -f /shared/initial-tests-passed ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for initial tests... ($WAITED/$MAX_WAIT seconds)" +done + +if [ ! -f /shared/initial-tests-passed ]; then + echo "ERROR: Initial client tests did not complete in time" + exit 1 +fi +echo "Initial client tests passed, proceeding with revocation tests" + +# ===================================================== +# Test certificate revocation functionality +# ===================================================== +echo "" +echo "=== Testing Certificate Revocation ===" + +# Create a new client for revocation testing +REVOKE_CLIENT="revoketest" +echo "Creating client '$REVOKE_CLIENT' for revocation testing..." +REVOKE_CREATE_OUTPUT="/tmp/revoke-create-output.log" +(MENU_OPTION=1 CLIENT=$REVOKE_CLIENT PASS=1 CLIENT_CERT_DURATION_DAYS=3650 bash /tmp/openvpn-install.sh) 2>&1 | tee "$REVOKE_CREATE_OUTPUT" || true + +if [ -f "/root/$REVOKE_CLIENT.ovpn" ]; then + echo "PASS: Client '$REVOKE_CLIENT' created successfully" +else + echo "FAIL: Failed to create client '$REVOKE_CLIENT'" + cat "$REVOKE_CREATE_OUTPUT" + exit 1 +fi + +# Copy config for revocation test client +cp "/root/$REVOKE_CLIENT.ovpn" "/shared/$REVOKE_CLIENT.ovpn" +sed -i 's/^remote .*/remote openvpn-server 1194/' "/shared/$REVOKE_CLIENT.ovpn" +echo "Copied $REVOKE_CLIENT config to /shared/" + +# Signal client that revoke test config is ready +touch /shared/revoke-client-config-ready + +# Wait for client to confirm connection with revoke test client +echo "Waiting for client to connect with '$REVOKE_CLIENT' certificate..." +MAX_WAIT=60 +WAITED=0 +while [ ! -f /shared/revoke-client-connected ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for revoke test connection... ($WAITED/$MAX_WAIT seconds)" +done + +if [ ! -f /shared/revoke-client-connected ]; then + echo "ERROR: Client did not connect with revoke test certificate" + exit 1 +fi +echo "PASS: Client connected with '$REVOKE_CLIENT' certificate" + +# Signal client to disconnect before revocation +touch /shared/revoke-client-disconnect + +# Wait for client to disconnect +echo "Waiting for client to disconnect..." +MAX_WAIT=30 +WAITED=0 +while [ ! -f /shared/revoke-client-disconnected ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) +done + +if [ ! -f /shared/revoke-client-disconnected ]; then + echo "ERROR: Client did not signal disconnect" + exit 1 +fi +echo "Client disconnected" + +# Now revoke the certificate +echo "Revoking certificate for '$REVOKE_CLIENT'..." +REVOKE_OUTPUT="/tmp/revoke-output.log" +# MENU_OPTION=2 is revoke, CLIENTNUMBER is dynamically determined from index.txt +# We need to find the client number for revoketest +REVOKE_CLIENT_NUM=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep "^V" | grep -n "CN=$REVOKE_CLIENT\$" | cut -d: -f1) +if [ -z "$REVOKE_CLIENT_NUM" ]; then + echo "ERROR: Could not find client number for '$REVOKE_CLIENT'" + cat /etc/openvpn/easy-rsa/pki/index.txt + exit 1 +fi +echo "Revoke client number: $REVOKE_CLIENT_NUM" +(MENU_OPTION=2 CLIENTNUMBER=$REVOKE_CLIENT_NUM bash /tmp/openvpn-install.sh) 2>&1 | tee "$REVOKE_OUTPUT" || true + +if grep -q "Certificate for client $REVOKE_CLIENT revoked" "$REVOKE_OUTPUT"; then + echo "PASS: Certificate for '$REVOKE_CLIENT' revoked successfully" +else + echo "FAIL: Failed to revoke certificate" + cat "$REVOKE_OUTPUT" + exit 1 +fi + +# Verify certificate is marked as revoked in index.txt +if tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -q "^R.*CN=$REVOKE_CLIENT\$"; then + echo "PASS: Certificate marked as revoked in index.txt" +else + echo "FAIL: Certificate not marked as revoked" + cat /etc/openvpn/easy-rsa/pki/index.txt + exit 1 +fi + +# Signal client to try reconnecting (should fail) +touch /shared/revoke-try-reconnect + +# Wait for client to confirm that connection with revoked cert failed +echo "Waiting for client to confirm revoked cert connection failure..." +MAX_WAIT=60 +WAITED=0 +while [ ! -f /shared/revoke-reconnect-failed ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for reconnect failure confirmation... ($WAITED/$MAX_WAIT seconds)" +done + +if [ ! -f /shared/revoke-reconnect-failed ]; then + echo "ERROR: Client did not confirm that revoked cert connection failed" + exit 1 +fi +echo "PASS: Connection with revoked certificate correctly rejected" + +echo "=== Certificate Revocation Tests PASSED ===" + +# ===================================================== +# Test reusing revoked client name +# ===================================================== +echo "" +echo "=== Testing Reuse of Revoked Client Name ===" + +# Create a new certificate with the same name as the revoked one +echo "Creating new client with same name '$REVOKE_CLIENT'..." +RECREATE_OUTPUT="/tmp/recreate-output.log" +(MENU_OPTION=1 CLIENT=$REVOKE_CLIENT PASS=1 CLIENT_CERT_DURATION_DAYS=3650 bash /tmp/openvpn-install.sh) 2>&1 | tee "$RECREATE_OUTPUT" || true + +if [ -f "/root/$REVOKE_CLIENT.ovpn" ]; then + echo "PASS: New client '$REVOKE_CLIENT' created successfully (reusing revoked name)" +else + echo "FAIL: Failed to create client with revoked name" + cat "$RECREATE_OUTPUT" + exit 1 +fi + +# Verify the new certificate is valid (V) in index.txt +if tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -q "^V.*CN=$REVOKE_CLIENT\$"; then + echo "PASS: New certificate is valid in index.txt" +else + echo "FAIL: New certificate not marked as valid" + cat /etc/openvpn/easy-rsa/pki/index.txt + exit 1 +fi + +# Verify there's also a revoked entry (both should exist) +REVOKED_COUNT=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -c "^R.*CN=$REVOKE_CLIENT\$") +VALID_COUNT=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -c "^V.*CN=$REVOKE_CLIENT\$") +echo "Certificates for '$REVOKE_CLIENT': $REVOKED_COUNT revoked, $VALID_COUNT valid" +if [ "$REVOKED_COUNT" -ge 1 ] && [ "$VALID_COUNT" -eq 1 ]; then + echo "PASS: Both revoked and new valid certificate entries exist" +else + echo "FAIL: Unexpected certificate state" + cat /etc/openvpn/easy-rsa/pki/index.txt + exit 1 +fi + +# Copy the new config +cp "/root/$REVOKE_CLIENT.ovpn" "/shared/$REVOKE_CLIENT-new.ovpn" +sed -i 's/^remote .*/remote openvpn-server 1194/' "/shared/$REVOKE_CLIENT-new.ovpn" +echo "Copied new $REVOKE_CLIENT config to /shared/" + +# Signal client that new config is ready +touch /shared/new-client-config-ready + +# Wait for client to confirm successful connection with new cert +echo "Waiting for client to connect with new '$REVOKE_CLIENT' certificate..." +MAX_WAIT=60 +WAITED=0 +while [ ! -f /shared/new-client-connected ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for new cert connection... ($WAITED/$MAX_WAIT seconds)" +done + +if [ ! -f /shared/new-client-connected ]; then + echo "ERROR: Client did not connect with new certificate" + exit 1 +fi +echo "PASS: Client connected with new '$REVOKE_CLIENT' certificate" + +echo "=== Reuse of Revoked Client Name Tests PASSED ===" +echo "" +echo "=== All Revocation Tests PASSED ===" + +# Keep server running for any remaining client tests +echo "Server waiting for client to complete all tests..." +wait $OPENVPN_PID