diff --git a/README.md b/README.md index fd280c1..bc40145 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ If you want to customise your installation, you can export them or specify them - `COMPRESSION_ENABLED=n` - `CUSTOMIZE_ENC=n` - `CLIENT=clientname` -- `PASS=1` +- `PASS=1` (set to `2` for password-protected clients, requires `PASSPHRASE`) - `MULTI_CLIENT=n` - `CLIENT_CERT_DURATION_DAYS=3650` - `SERVER_CERT_DURATION_DAYS=3650` @@ -104,8 +104,6 @@ If the server is behind NAT, you can specify its endpoint with the `ENDPOINT` va Other variables can be set depending on your choice (encryption, compression). You can search for them in the `installQuestions()` function of the script. -Password-protected clients are not supported by the headless installation method since user input is expected by Easy-RSA. - The headless install is more-or-less idempotent, in that it has been made safe to run multiple times with the same parameters, e.g. by a state provisioner like Ansible/Terraform/Salt/Chef/Puppet. It will only install and regenerate the Easy-RSA PKI if it doesn't already exist, and it will only install OpenVPN and other upstream dependencies if OpenVPN isn't already installed. It will recreate all local config and re-generate the client file on each headless run. ### Headless User Addition @@ -118,7 +116,7 @@ The following Bash script adds a new user `foo` to an existing OpenVPN configura #!/bin/bash export MENU_OPTION="1" export CLIENT="foo" -export PASS="1" +export PASS="1" # set to "2" for a password-protected client, and set PASSPHRASE ./openvpn-install.sh ``` diff --git a/openvpn-install.sh b/openvpn-install.sh index 5fa0270..cc0b43b 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -1688,10 +1688,18 @@ function newClient() { run_cmd_fatal "Building client certificate" ./easyrsa --batch build-client-full "$CLIENT" nopass ;; 2) - log_warn "You will be asked for the client password below" - # Run directly (not via run_cmd) so password prompt is visible to user - if ! ./easyrsa --batch build-client-full "$CLIENT"; then - log_fatal "Building client certificate failed" + if [[ -z "$PASSPHRASE" ]]; then + log_warn "You will be asked for the client password below" + # Run directly (not via run_cmd) so password prompt is visible to user + if ! ./easyrsa --batch build-client-full "$CLIENT"; then + log_fatal "Building client certificate failed" + fi + else + log_info "Using provided passphrase for client certificate" + # Use env var to avoid exposing passphrase in install log + export EASYRSA_PASSPHRASE="$PASSPHRASE" + run_cmd_fatal "Building client certificate" ./easyrsa --batch --passin=env:EASYRSA_PASSPHRASE --passout=env:EASYRSA_PASSPHRASE build-client-full "$CLIENT" + unset EASYRSA_PASSPHRASE fi ;; esac diff --git a/test/client-entrypoint.sh b/test/client-entrypoint.sh index 648c285..9b46fe4 100755 --- a/test/client-entrypoint.sh +++ b/test/client-entrypoint.sh @@ -359,6 +359,86 @@ touch /shared/new-client-connected echo "" echo "=== Certificate Revocation E2E Tests PASSED ===" +# ===================================================== +# Test PASSPHRASE-protected client connection +# ===================================================== +echo "" +echo "=== Testing PASSPHRASE-protected Client Connection ===" + +PASSPHRASE_CLIENT="passphrasetest" + +# Wait for passphrase test client config +echo "Waiting for passphrase test client config..." +MAX_WAIT=120 +WAITED=0 +while [ ! -f /shared/passphrase-client-config-ready ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for passphrase test config... ($WAITED/$MAX_WAIT seconds)" +done + +if [ ! -f /shared/passphrase-client-config-ready ]; then + echo "FAIL: Passphrase test client config not ready in time" + exit 1 +fi + +if [ ! -f "/shared/$PASSPHRASE_CLIENT.ovpn" ]; then + echo "FAIL: Passphrase test client config file not found" + exit 1 +fi + +if [ ! -f "/shared/$PASSPHRASE_CLIENT.pass" ]; then + echo "FAIL: Passphrase file not found" + exit 1 +fi + +echo "Passphrase test client config found!" + +# Disconnect current VPN before connecting with passphrase client +echo "Disconnecting current VPN connection..." +pkill openvpn || true +sleep 2 + +# Connect with passphrase-protected client using --askpass +echo "Connecting with '$PASSPHRASE_CLIENT' certificate (passphrase-protected)..." +openvpn --config "/shared/$PASSPHRASE_CLIENT.ovpn" --askpass "/shared/$PASSPHRASE_CLIENT.pass" --daemon --log /var/log/openvpn-passphrase.log + +# Wait for connection +echo "Waiting for VPN connection with passphrase-protected 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-passphrase.log ]; then + tail -3 /var/log/openvpn-passphrase.log + fi +done + +if ! ip addr show tun0 2>/dev/null | grep -q "inet "; then + echo "FAIL: VPN connection with passphrase-protected client failed" + cat /var/log/openvpn-passphrase.log || true + exit 1 +fi + +echo "PASS: Connected with passphrase-protected '$PASSPHRASE_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 passphrase-protected client" +else + echo "FAIL: Cannot ping VPN gateway with passphrase-protected client" + exit 1 +fi + +# Signal server that we connected with passphrase client +touch /shared/passphrase-client-connected + +echo "" +echo "=== PASSPHRASE-protected Client Tests PASSED ===" + echo "" echo "==========================================" echo " ALL TESTS PASSED!" diff --git a/test/server-entrypoint.sh b/test/server-entrypoint.sh index 83e5328..3b2daa0 100755 --- a/test/server-entrypoint.sh +++ b/test/server-entrypoint.sh @@ -618,6 +618,85 @@ fi echo "PASS: Client connected with new '$REVOKE_CLIENT' certificate" echo "=== Reuse of Revoked Client Name Tests PASSED ===" + +# ===================================================== +# Test PASSPHRASE support for headless client creation +# ===================================================== +echo "" +echo "=== Testing PASSPHRASE Support ===" + +PASSPHRASE_CLIENT="passphrasetest" +TEST_PASSPHRASE="TestP@ssw0rd#123" +echo "Creating client '$PASSPHRASE_CLIENT' with passphrase in headless mode..." +PASSPHRASE_OUTPUT="/tmp/passphrase-output.log" +(MENU_OPTION=1 CLIENT=$PASSPHRASE_CLIENT PASS=2 PASSPHRASE="$TEST_PASSPHRASE" CLIENT_CERT_DURATION_DAYS=3650 bash /opt/openvpn-install.sh) 2>&1 | tee "$PASSPHRASE_OUTPUT" || true + +# Verify client was created +if [ -f "/root/$PASSPHRASE_CLIENT.ovpn" ]; then + echo "PASS: Client '$PASSPHRASE_CLIENT' with passphrase created successfully" +else + echo "FAIL: Failed to create client '$PASSPHRASE_CLIENT' with passphrase" + cat "$PASSPHRASE_OUTPUT" + exit 1 +fi + +# Verify the passphrase is NOT leaked in the output +if grep -q "$TEST_PASSPHRASE" "$PASSPHRASE_OUTPUT"; then + echo "FAIL: Passphrase was leaked in command output!" + exit 1 +else + echo "PASS: Passphrase not leaked in command output" +fi + +# Verify the log file doesn't contain the passphrase +if [ -f /opt/openvpn-install.log ] && grep -q "$TEST_PASSPHRASE" /opt/openvpn-install.log; then + echo "FAIL: Passphrase was leaked in log file!" + exit 1 +else + echo "PASS: Passphrase not leaked in log file" +fi + +# Verify certificate was created with encryption (key should be encrypted) +CLIENT_KEY="/etc/openvpn/server/easy-rsa/pki/private/$PASSPHRASE_CLIENT.key" +if [ -f "$CLIENT_KEY" ]; then + if grep -q "ENCRYPTED" "$CLIENT_KEY"; then + echo "PASS: Client key is encrypted" + else + echo "FAIL: Client key is not encrypted" + exit 1 + fi +else + echo "FAIL: Client key not found at $CLIENT_KEY" + exit 1 +fi + +# Copy config for passphrase client connectivity test +cp "/root/$PASSPHRASE_CLIENT.ovpn" "/shared/$PASSPHRASE_CLIENT.ovpn" +sed -i 's/^remote .*/remote openvpn-server 1194/' "/shared/$PASSPHRASE_CLIENT.ovpn" +# Write passphrase to a file for client to use with --askpass +echo "$TEST_PASSPHRASE" >"/shared/$PASSPHRASE_CLIENT.pass" +echo "Copied $PASSPHRASE_CLIENT config and passphrase to /shared/" + +# Signal client that passphrase test config is ready +touch /shared/passphrase-client-config-ready + +# Wait for client to confirm connection with passphrase client +echo "Waiting for client to connect with '$PASSPHRASE_CLIENT' certificate..." +MAX_WAIT=60 +WAITED=0 +while [ ! -f /shared/passphrase-client-connected ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for passphrase client connection... ($WAITED/$MAX_WAIT seconds)" +done + +if [ ! -f /shared/passphrase-client-connected ]; then + echo "FAIL: Client did not connect with passphrase-protected certificate" + exit 1 +fi +echo "PASS: Client connected with passphrase-protected certificate" + +echo "=== PASSPHRASE Support Tests PASSED ===" echo "" echo "=== All Revocation Tests PASSED ==="