Add PASSPHRASE support in headless mode (#1015)

Add support for a password protected user in headless mode

Fixes #389

---------

Co-authored-by: Siebren Kraak <siebren.kraak@secura.com>
Co-authored-by: Stanislas Lange <git@slange.me>
This commit is contained in:
Siebren Kraak
2025-12-13 15:42:43 +01:00
committed by GitHub
parent 75ea8ef1c1
commit cb2d67be74
4 changed files with 173 additions and 8 deletions

View File

@@ -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
```

View File

@@ -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

View File

@@ -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!"

View File

@@ -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 ==="