feat: add peer-fingerprint authentication mode (OpenVPN 2.6+) (#1437)

## Summary

Implements support for OpenVPN's `--peer-fingerprint` option, enabling
PKI-less authentication using SHA256 certificate fingerprints instead of
a CA chain.

Closes #1361

## Changes

- Add `--auth-mode` option (`pki` or `fingerprint`) for install command
- Use Easy-RSA's `self-sign-server` and `self-sign-client` commands for
fingerprint mode
- Server stores client fingerprints in `<peer-fingerprint>` block in
`server.conf`
- Clients verify server using `peer-fingerprint` directive instead of CA
- Revocation removes fingerprint from config and reloads OpenVPN
(instant effect)
- Version check ensures OpenVPN 2.6+ when fingerprint mode is selected

## Usage

```bash
# Interactive mode prompts for auth mode choice

# CLI mode
./openvpn-install.sh install --auth-mode fingerprint
```

## Comparison

| Aspect | PKI Mode | Fingerprint Mode |
|--------|----------|------------------|
| Server cert | CA-signed | Self-signed |
| Client cert | CA-signed | Self-signed |
| Revocation | CRL-based | Remove fingerprint |
| OpenVPN | Any version | 2.6.0+ required |
| Best for | Large deployments | Small/home setups |
This commit is contained in:
Stanislas
2025-12-18 17:20:28 +01:00
committed by GitHub
parent 13008ef45c
commit df242ee069
4 changed files with 465 additions and 114 deletions

View File

@@ -118,6 +118,15 @@ jobs:
name: tls-crypt-v2 name: tls-crypt-v2
sig: crypt-v2 sig: crypt-v2
key_file: tls-crypt-v2.key key_file: tls-crypt-v2.key
# Test peer-fingerprint authentication mode (OpenVPN 2.6+)
- os:
name: ubuntu-24.04-fingerprint
image: ubuntu:24.04
auth_mode: fingerprint
tls:
name: tls-crypt-v2
sig: crypt-v2
key_file: tls-crypt-v2.key
name: ${{ matrix.os.name }} name: ${{ matrix.os.name }}
steps: steps:
@@ -166,6 +175,7 @@ jobs:
-e TLS_SIG=${{ matrix.tls.sig }} \ -e TLS_SIG=${{ matrix.tls.sig }} \
-e TLS_KEY_FILE=${{ matrix.tls.key_file }} \ -e TLS_KEY_FILE=${{ matrix.tls.key_file }} \
-e CLIENT_IPV6=${{ matrix.os.client_ipv6 && 'y' || 'n' }} \ -e CLIENT_IPV6=${{ matrix.os.client_ipv6 && 'y' || 'n' }} \
-e AUTH_MODE=${{ matrix.os.auth_mode || 'pki' }} \
openvpn-server openvpn-server
- name: Wait for server installation and startup - name: Wait for server installation and startup

View File

@@ -62,6 +62,7 @@ That said, OpenVPN still makes sense when you need:
- Randomised server certificate name - Randomised server certificate name
- Choice to protect clients with a password (private key encryption) - Choice to protect clients with a password (private key encryption)
- Option to allow multiple devices to use the same client profile simultaneously (disables persistent IP addresses) - Option to allow multiple devices to use the same client profile simultaneously (disables persistent IP addresses)
- **Peer fingerprint authentication** (OpenVPN 2.6+): Simplified WireGuard-like authentication without a CA
- Many other little things! - Many other little things!
## Compatibility ## Compatibility
@@ -317,6 +318,7 @@ The `install` command supports many options for customization:
- `--rsa-bits <2048|3072|4096>` - RSA key size (default: `2048`) - `--rsa-bits <2048|3072|4096>` - RSA key size (default: `2048`)
- `--hmac <alg>` - HMAC algorithm (default: `SHA256`). Options: `SHA256`, `SHA384`, `SHA512` - `--hmac <alg>` - HMAC algorithm (default: `SHA256`). Options: `SHA256`, `SHA384`, `SHA512`
- `--tls-sig <mode>` - TLS mode (default: `crypt-v2`). Options: `crypt-v2`, `crypt`, `auth` - `--tls-sig <mode>` - TLS mode (default: `crypt-v2`). Options: `crypt-v2`, `crypt`, `auth`
- `--auth-mode <mode>` - Authentication mode (default: `pki`). Options: `pki` (CA-based), `fingerprint` (peer-fingerprint, requires OpenVPN 2.6+)
- `--tls-version-min <1.2|1.3>` - Minimum TLS version (default: `1.2`) - `--tls-version-min <1.2|1.3>` - Minimum TLS version (default: `1.2`)
- `--tls-ciphersuites <list>` - TLS 1.3 cipher suites, colon-separated (default: `TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256`) - `--tls-ciphersuites <list>` - TLS 1.3 cipher suites, colon-separated (default: `TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256`)
- `--tls-groups <list>` - Key exchange groups, colon-separated (default: `X25519:prime256v1:secp384r1:secp521r1`) - `--tls-groups <list>` - Key exchange groups, colon-separated (default: `X25519:prime256v1:secp384r1:secp521r1`)
@@ -470,6 +472,45 @@ It defaults to ECDSA with `prime256v1`.
OpenVPN uses `SHA-256` as the signature hash by default, and so does the script. It provides no other choice as of now. OpenVPN uses `SHA-256` as the signature hash by default, and so does the script. It provides no other choice as of now.
### Authentication Mode
The script supports two authentication modes:
#### PKI Mode (default)
Traditional Certificate Authority (CA) based authentication. The server and all clients have certificates signed by the same CA. Client revocation is handled via Certificate Revocation Lists (CRL).
This is the recommended mode for larger deployments where you need:
- Centralized certificate management
- Standard CRL-based revocation
- Compatibility with all OpenVPN versions
#### Peer Fingerprint Mode (OpenVPN 2.6+)
A simplified WireGuard-like authentication model using SHA256 certificate fingerprints instead of a CA chain. Each peer (server and clients) has a self-signed certificate, and peers authenticate each other by verifying fingerprints.
```bash
# Install with fingerprint mode
./openvpn-install.sh install --auth-mode fingerprint
```
Benefits:
- Simpler setup: No CA infrastructure needed
- Easier to understand: Similar to SSH's `known_hosts` model
- Ideal for small setups: Home networks, labs, small teams
How it works:
1. Server generates a self-signed certificate and stores its fingerprint
2. Each client generates a self-signed certificate
3. Client fingerprints are added to the server's `<peer-fingerprint>` block
4. Clients verify the server using the server's fingerprint
5. Revocation removes the fingerprint from the server config (no CRL needed)
Trade-off: Revoking a client requires reloading OpenVPN (fingerprints are in server.conf). In PKI mode, the CRL file is re-read automatically on new connections.
### Data channel ### Data channel
> [!NOTE] > [!NOTE]

View File

@@ -239,6 +239,8 @@ show_install_help() {
(default: X25519:prime256v1:secp384r1:secp521r1) (default: X25519:prime256v1:secp384r1:secp521r1)
--hmac <alg> HMAC algorithm: SHA256, SHA384, SHA512 (default: SHA256) --hmac <alg> HMAC algorithm: SHA256, SHA384, SHA512 (default: SHA256)
--tls-sig <mode> TLS mode: crypt-v2, crypt, auth (default: crypt-v2) --tls-sig <mode> TLS mode: crypt-v2, crypt, auth (default: crypt-v2)
--auth-mode <mode> Auth mode: pki, fingerprint (default: pki)
fingerprint requires OpenVPN 2.6+
--server-cert-days <n> Server cert validity in days (default: 3650) --server-cert-days <n> Server cert validity in days (default: 3650)
Other Options: Other Options:
@@ -477,6 +479,9 @@ readonly TLS_VERSIONS=("1.2" "1.3")
# TLS signature modes (use strings) # TLS signature modes (use strings)
readonly TLS_SIG_MODES=("crypt-v2" "crypt" "auth") readonly TLS_SIG_MODES=("crypt-v2" "crypt" "auth")
# Authentication modes: pki (CA-based) or fingerprint (peer-fingerprint, OpenVPN 2.6+)
readonly AUTH_MODES=("pki" "fingerprint")
# HMAC algorithms # HMAC algorithms
readonly HMAC_ALGS=("SHA256" "SHA384" "SHA512") readonly HMAC_ALGS=("SHA256" "SHA384" "SHA512")
@@ -516,6 +521,7 @@ set_installation_defaults() {
TLS_GROUPS="${TLS_GROUPS:-X25519:prime256v1:secp384r1:secp521r1}" TLS_GROUPS="${TLS_GROUPS:-X25519:prime256v1:secp384r1:secp521r1}"
HMAC_ALG="${HMAC_ALG:-SHA256}" HMAC_ALG="${HMAC_ALG:-SHA256}"
TLS_SIG="${TLS_SIG:-crypt-v2}" TLS_SIG="${TLS_SIG:-crypt-v2}"
AUTH_MODE="${AUTH_MODE:-pki}"
# Derive CC_CIPHER from CERT_TYPE if not set # Derive CC_CIPHER from CERT_TYPE if not set
if [[ -z $CC_CIPHER ]]; then if [[ -z $CC_CIPHER ]]; then
@@ -536,6 +542,18 @@ set_installation_defaults() {
# are computed in prepare_network_config() which is called after validation # are computed in prepare_network_config() which is called after validation
} }
# Version comparison: returns 0 if version1 >= version2
version_ge() {
local ver1="$1" ver2="$2"
# Use sort -V for version comparison
[[ "$(printf '%s\n%s' "$ver1" "$ver2" | sort -V | head -n1)" == "$ver2" ]]
}
# Get installed OpenVPN version (e.g., "2.6.12")
get_openvpn_version() {
openvpn --version 2>/dev/null | head -1 | awk '{print $2}'
}
# Validation functions # Validation functions
validate_port() { validate_port() {
local port="$1" local port="$1"
@@ -642,6 +660,21 @@ validate_configuration() {
*) log_fatal "Invalid TLS signature mode: $TLS_SIG. Must be 'crypt-v2', 'crypt', or 'auth'." ;; *) log_fatal "Invalid TLS signature mode: $TLS_SIG. Must be 'crypt-v2', 'crypt', or 'auth'." ;;
esac esac
# Validate AUTH_MODE
case "$AUTH_MODE" in
pki | fingerprint) ;;
*) log_fatal "Invalid auth mode: $AUTH_MODE. Must be 'pki' or 'fingerprint'." ;;
esac
# Fingerprint mode requires OpenVPN 2.6+
if [[ $AUTH_MODE == "fingerprint" ]]; then
local openvpn_ver
openvpn_ver=$(get_openvpn_version)
if [[ -n "$openvpn_ver" ]] && ! version_ge "$openvpn_ver" "2.6.0"; then
log_fatal "Fingerprint mode requires OpenVPN 2.6.0 or later. Installed version: $openvpn_ver"
fi
fi
# Validate PORT # Validate PORT
if ! [[ "$PORT" =~ ^[0-9]+$ ]] || [[ "$PORT" -lt 1 ]] || [[ "$PORT" -gt 65535 ]]; then if ! [[ "$PORT" =~ ^[0-9]+$ ]] || [[ "$PORT" -lt 1 ]] || [[ "$PORT" -gt 65535 ]]; then
log_fatal "Invalid port: $PORT. Must be a number between 1 and 65535." log_fatal "Invalid port: $PORT. Must be a number between 1 and 65535."
@@ -1034,6 +1067,14 @@ cmd_install() {
esac esac
shift 2 shift 2
;; ;;
--auth-mode)
[[ -z "${2:-}" ]] && log_fatal "--auth-mode requires an argument"
case "$2" in
pki | fingerprint) AUTH_MODE="$2" ;;
*) log_fatal "Invalid auth mode: $2. Use 'pki' or 'fingerprint'." ;;
esac
shift 2
;;
--server-cert-days) --server-cert-days)
[[ -z "${2:-}" ]] && log_fatal "--server-cert-days requires an argument" [[ -z "${2:-}" ]] && log_fatal "--server-cert-days requires an argument"
validate_positive_int "$2" "server-cert-days" validate_positive_int "$2" "server-cert-days"
@@ -1251,6 +1292,7 @@ cmd_client_add() {
fi fi
newClient newClient
exit 0
} }
# Handle client list command # Handle client list command
@@ -2336,6 +2378,30 @@ function installQuestions() {
done done
fi fi
log_menu "" log_menu ""
log_prompt "Choose the authentication mode:"
log_menu " 1) PKI (Certificate Authority) - Traditional CA-based authentication (recommended for larger setups)"
log_menu " 2) Peer Fingerprint - Simplified WireGuard-like authentication using certificate fingerprints"
log_menu " Note: Fingerprint mode requires OpenVPN 2.6+ and is ideal for small/home setups"
local auth_mode_choice
until [[ $auth_mode_choice =~ ^[1-2]$ ]]; do
read -rp "Authentication mode [1-2]: " -e -i 1 auth_mode_choice
done
case $auth_mode_choice in
1)
AUTH_MODE="pki"
;;
2)
AUTH_MODE="fingerprint"
# Verify OpenVPN 2.6+ is available for fingerprint mode
local openvpn_ver
openvpn_ver=$(get_openvpn_version)
if [[ -n "$openvpn_ver" ]] && ! version_ge "$openvpn_ver" "2.6.0"; then
log_warn "OpenVPN $openvpn_ver detected. Fingerprint mode requires 2.6.0+."
log_warn "OpenVPN 2.6+ will be installed during setup."
fi
;;
esac
log_menu ""
log_prompt "Do you want to customize encryption settings?" log_prompt "Do you want to customize encryption settings?"
log_prompt "Unless you know what you're doing, you should stick with the default parameters provided by the script." log_prompt "Unless you know what you're doing, you should stick with the default parameters provided by the script."
log_prompt "Note that whatever you choose, all the choices presented in the script are safe (unlike OpenVPN's defaults)." log_prompt "Note that whatever you choose, all the choices presented in the script are safe (unlike OpenVPN's defaults)."
@@ -2541,6 +2607,7 @@ function installOpenVPN() {
log_info " DNS=$DNS" log_info " DNS=$DNS"
[[ -n $MTU ]] && log_info " MTU=$MTU" [[ -n $MTU ]] && log_info " MTU=$MTU"
log_info " MULTI_CLIENT=$MULTI_CLIENT" log_info " MULTI_CLIENT=$MULTI_CLIENT"
log_info " AUTH_MODE=$AUTH_MODE"
log_info " CLIENT=$CLIENT" log_info " CLIENT=$CLIENT"
log_info " CLIENT_CERT_DURATION_DAYS=$CLIENT_CERT_DURATION_DAYS" log_info " CLIENT_CERT_DURATION_DAYS=$CLIENT_CERT_DURATION_DAYS"
log_info " SERVER_CERT_DURATION_DAYS=$SERVER_CERT_DURATION_DAYS" log_info " SERVER_CERT_DURATION_DAYS=$SERVER_CERT_DURATION_DAYS"
@@ -2680,6 +2747,9 @@ function installOpenVPN() {
# Create the PKI, set up the CA, the DH params and the server certificate # Create the PKI, set up the CA, the DH params and the server certificate
log_info "Initializing PKI..." log_info "Initializing PKI..."
run_cmd_fatal "Initializing PKI" ./easyrsa init-pki run_cmd_fatal "Initializing PKI" ./easyrsa init-pki
if [[ $AUTH_MODE == "pki" ]]; then
# Traditional PKI mode with CA
export EASYRSA_CA_EXPIRE=$DEFAULT_CERT_VALIDITY_DURATION_DAYS export EASYRSA_CA_EXPIRE=$DEFAULT_CERT_VALIDITY_DURATION_DAYS
log_info "Building CA..." log_info "Building CA..."
run_cmd_fatal "Building CA" ./easyrsa --batch --req-cn="$SERVER_CN" build-ca nopass run_cmd_fatal "Building CA" ./easyrsa --batch --req-cn="$SERVER_CN" build-ca nopass
@@ -2689,6 +2759,22 @@ function installOpenVPN() {
run_cmd_fatal "Building server certificate" ./easyrsa --batch build-server-full "$SERVER_NAME" nopass run_cmd_fatal "Building server certificate" ./easyrsa --batch build-server-full "$SERVER_NAME" nopass
export EASYRSA_CRL_DAYS=$DEFAULT_CRL_VALIDITY_DURATION_DAYS export EASYRSA_CRL_DAYS=$DEFAULT_CRL_VALIDITY_DURATION_DAYS
run_cmd_fatal "Generating CRL" ./easyrsa gen-crl run_cmd_fatal "Generating CRL" ./easyrsa gen-crl
else
# Fingerprint mode with self-signed certificates (OpenVPN 2.6+)
log_info "Building self-signed server certificate for fingerprint mode..."
export EASYRSA_CERT_EXPIRE=${SERVER_CERT_DURATION_DAYS:-$DEFAULT_CERT_VALIDITY_DURATION_DAYS}
run_cmd_fatal "Building self-signed server certificate" ./easyrsa --batch self-sign-server "$SERVER_NAME" nopass
# Extract and store server fingerprint
SERVER_FINGERPRINT=$(openssl x509 -in "pki/issued/$SERVER_NAME.crt" -fingerprint -sha256 -noout | cut -d'=' -f2)
if [[ -z $SERVER_FINGERPRINT ]]; then
log_error "Failed to extract server certificate fingerprint"
exit 1
fi
mkdir -p /etc/openvpn/server
echo "$SERVER_FINGERPRINT" >/etc/openvpn/server/server-fingerprint
log_info "Server fingerprint: $SERVER_FINGERPRINT"
fi
log_info "Generating TLS key..." log_info "Generating TLS key..."
case $TLS_SIG in case $TLS_SIG in
@@ -2705,19 +2791,32 @@ function installOpenVPN() {
run_cmd_fatal "Generating tls-auth key" openvpn --genkey secret /etc/openvpn/server/tls-auth.key run_cmd_fatal "Generating tls-auth key" openvpn --genkey secret /etc/openvpn/server/tls-auth.key
;; ;;
esac esac
# Store auth mode for later use
echo "$AUTH_MODE" >AUTH_MODE_GENERATED
else else
# If easy-rsa is already installed, grab the generated SERVER_NAME # If easy-rsa is already installed, grab the generated SERVER_NAME
# for client configs # for client configs
cd /etc/openvpn/server/easy-rsa/ || return cd /etc/openvpn/server/easy-rsa/ || return
SERVER_NAME=$(cat SERVER_NAME_GENERATED) SERVER_NAME=$(cat SERVER_NAME_GENERATED)
# Read stored auth mode
if [[ -f AUTH_MODE_GENERATED ]]; then
AUTH_MODE=$(cat AUTH_MODE_GENERATED)
else
# Default to pki for existing installations
AUTH_MODE="pki"
fi
fi fi
# Move all the generated files # Move all the generated files
log_info "Copying certificates..." log_info "Copying certificates..."
if [[ $AUTH_MODE == "pki" ]]; then
run_cmd_fatal "Copying certificates to /etc/openvpn/server" cp pki/ca.crt pki/private/ca.key "pki/issued/$SERVER_NAME.crt" "pki/private/$SERVER_NAME.key" /etc/openvpn/server/easy-rsa/pki/crl.pem /etc/openvpn/server run_cmd_fatal "Copying certificates to /etc/openvpn/server" cp pki/ca.crt pki/private/ca.key "pki/issued/$SERVER_NAME.crt" "pki/private/$SERVER_NAME.key" /etc/openvpn/server/easy-rsa/pki/crl.pem /etc/openvpn/server
# Make cert revocation list readable for non-root # Make cert revocation list readable for non-root
run_cmd "Setting CRL permissions" chmod 644 /etc/openvpn/server/crl.pem run_cmd "Setting CRL permissions" chmod 644 /etc/openvpn/server/crl.pem
else
# Fingerprint mode: only copy server cert and key (no CA or CRL)
run_cmd_fatal "Copying certificates to /etc/openvpn/server" cp "pki/issued/$SERVER_NAME.crt" "pki/private/$SERVER_NAME.key" /etc/openvpn/server
fi
# Generate server.conf # Generate server.conf
log_info "Generating server configuration..." log_info "Generating server configuration..."
@@ -2931,9 +3030,13 @@ topology subnet" >>/etc/openvpn/server/server.conf
;; ;;
esac esac
echo "crl-verify crl.pem # Common server config options
ca ca.crt # PKI mode adds crl-verify, ca, and remote-cert-tls
cert $SERVER_NAME.crt # Fingerprint mode: <peer-fingerprint> block is added when first client is created
{
[[ $AUTH_MODE == "pki" ]] && echo "crl-verify crl.pem
ca ca.crt"
echo "cert $SERVER_NAME.crt
key $SERVER_NAME.key key $SERVER_NAME.key
auth $HMAC_ALG auth $HMAC_ALG
cipher $CIPHER cipher $CIPHER
@@ -2941,14 +3044,15 @@ ignore-unknown-option data-ciphers
data-ciphers $CIPHER data-ciphers $CIPHER
ncp-ciphers $CIPHER ncp-ciphers $CIPHER
tls-server tls-server
tls-version-min $TLS_VERSION_MIN tls-version-min $TLS_VERSION_MIN"
remote-cert-tls client [[ $AUTH_MODE == "pki" ]] && echo "remote-cert-tls client"
tls-cipher $CC_CIPHER echo "tls-cipher $CC_CIPHER
tls-ciphersuites $TLS13_CIPHERSUITES tls-ciphersuites $TLS13_CIPHERSUITES
client-config-dir ccd client-config-dir ccd
status /var/log/openvpn/status.log status /var/log/openvpn/status.log
management /var/run/openvpn/server.sock unix management /var/run/openvpn/server.sock unix
verb 3" >>/etc/openvpn/server/server.conf verb 3"
} >>/etc/openvpn/server/server.conf
# Create management socket directory # Create management socket directory
run_cmd_fatal "Creating management socket directory" mkdir -p /var/run/openvpn run_cmd_fatal "Creating management socket directory" mkdir -p /var/run/openvpn
@@ -3028,7 +3132,11 @@ verb 3" >>/etc/openvpn/server/server.conf
run_cmd "Reloading systemd" systemctl daemon-reload run_cmd "Reloading systemd" systemctl daemon-reload
run_cmd "Enabling OpenVPN service" systemctl enable openvpn-server@server run_cmd "Enabling OpenVPN service" systemctl enable openvpn-server@server
# In fingerprint mode, delay service start until first client is created
# (OpenVPN requires at least one fingerprint or a CA to start)
if [[ $AUTH_MODE == "pki" ]]; then
run_cmd "Starting OpenVPN service" systemctl restart openvpn-server@server run_cmd "Starting OpenVPN service" systemctl restart openvpn-server@server
fi
if [[ $DNS == "unbound" ]]; then if [[ $DNS == "unbound" ]]; then
installUnbound installUnbound
@@ -3207,15 +3315,19 @@ WantedBy=multi-user.target" >/etc/systemd/system/iptables-openvpn.service
elif [[ $PROTOCOL == 'tcp6' ]]; then elif [[ $PROTOCOL == 'tcp6' ]]; then
echo "proto tcp6-client" >>/etc/openvpn/server/client-template.txt echo "proto tcp6-client" >>/etc/openvpn/server/client-template.txt
fi fi
# Common client template options
# PKI mode adds remote-cert-tls and verify-x509-name
# Fingerprint mode adds peer-fingerprint when generating client config
{
echo "remote $IP $PORT echo "remote $IP $PORT
dev tun dev tun
resolv-retry infinite resolv-retry infinite
nobind nobind
persist-key persist-key
persist-tun persist-tun"
remote-cert-tls server [[ $AUTH_MODE == "pki" ]] && echo "remote-cert-tls server
verify-x509-name $SERVER_NAME name verify-x509-name $SERVER_NAME name"
auth $HMAC_ALG echo "auth $HMAC_ALG
auth-nocache auth-nocache
cipher $CIPHER cipher $CIPHER
ignore-unknown-option data-ciphers ignore-unknown-option data-ciphers
@@ -3227,7 +3339,8 @@ tls-cipher $CC_CIPHER
tls-ciphersuites $TLS13_CIPHERSUITES tls-ciphersuites $TLS13_CIPHERSUITES
ignore-unknown-option block-outside-dns ignore-unknown-option block-outside-dns
setenv opt block-outside-dns # Prevent Windows 10 DNS leak setenv opt block-outside-dns # Prevent Windows 10 DNS leak
verb 3" >>/etc/openvpn/server/client-template.txt verb 3"
} >>/etc/openvpn/server/client-template.txt
if [[ -n $MTU ]]; then if [[ -n $MTU ]]; then
echo "tun-mtu $MTU" >>/etc/openvpn/server/client-template.txt echo "tun-mtu $MTU" >>/etc/openvpn/server/client-template.txt
@@ -3235,10 +3348,18 @@ verb 3" >>/etc/openvpn/server/client-template.txt
# Generate the custom client.ovpn # Generate the custom client.ovpn
if [[ $NEW_CLIENT == "n" ]]; then if [[ $NEW_CLIENT == "n" ]]; then
if [[ $AUTH_MODE == "fingerprint" ]]; then
log_info "No clients added. OpenVPN will not start until you add at least one client."
else
log_info "No clients added. To add clients, simply run the script again." log_info "No clients added. To add clients, simply run the script again."
fi
else else
log_info "Generating first client certificate..." log_info "Generating first client certificate..."
newClient newClient
# In fingerprint mode, start service now that we have at least one fingerprint
if [[ $AUTH_MODE == "fingerprint" ]]; then
run_cmd "Starting OpenVPN service" systemctl restart openvpn-server@server
fi
log_success "If you want to add more clients, you simply need to run this script another time!" log_success "If you want to add more clients, you simply need to run this script another time!"
fi fi
} }
@@ -3333,6 +3454,12 @@ function generateClientConfig() {
local client="$1" local client="$1"
local filepath="$2" local filepath="$2"
# Read auth mode
local auth_mode="pki"
if [[ -f /etc/openvpn/server/easy-rsa/AUTH_MODE_GENERATED ]]; then
auth_mode=$(cat /etc/openvpn/server/easy-rsa/AUTH_MODE_GENERATED)
fi
# Determine if we use tls-crypt-v2, tls-crypt, or tls-auth # Determine if we use tls-crypt-v2, tls-crypt, or tls-auth
local tls_sig="" local tls_sig=""
if grep -qs "^tls-crypt-v2" /etc/openvpn/server/server.conf; then if grep -qs "^tls-crypt-v2" /etc/openvpn/server/server.conf; then
@@ -3346,9 +3473,25 @@ function generateClientConfig() {
# Generate the custom client.ovpn # Generate the custom client.ovpn
run_cmd "Creating client config" cp /etc/openvpn/server/client-template.txt "$filepath" run_cmd "Creating client config" cp /etc/openvpn/server/client-template.txt "$filepath"
{ {
if [[ $auth_mode == "pki" ]]; then
# PKI mode: include CA certificate
echo "<ca>" echo "<ca>"
cat "/etc/openvpn/server/easy-rsa/pki/ca.crt" cat "/etc/openvpn/server/easy-rsa/pki/ca.crt"
echo "</ca>" echo "</ca>"
else
# Fingerprint mode: use server fingerprint instead of CA
local server_fingerprint
if [[ ! -f /etc/openvpn/server/server-fingerprint ]]; then
log_error "Server fingerprint file not found"
exit 1
fi
server_fingerprint=$(cat /etc/openvpn/server/server-fingerprint)
if [[ -z $server_fingerprint ]]; then
log_error "Server fingerprint is empty"
exit 1
fi
echo "peer-fingerprint $server_fingerprint"
fi
echo "<cert>" echo "<cert>"
awk '/BEGIN/,/END CERTIFICATE/' "/etc/openvpn/server/easy-rsa/pki/issued/$client.crt" awk '/BEGIN/,/END CERTIFICATE/' "/etc/openvpn/server/easy-rsa/pki/issued/$client.crt"
@@ -3677,36 +3820,89 @@ function newClient() {
done done
fi fi
cd /etc/openvpn/server/easy-rsa/ || return
# Read auth mode
if [[ -f AUTH_MODE_GENERATED ]]; then
AUTH_MODE=$(cat AUTH_MODE_GENERATED)
else
AUTH_MODE="pki"
fi
# Check if client already exists
if [[ -f pki/index.txt ]]; then
CLIENTEXISTS=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -E "^V" | grep -c -E "/CN=$CLIENT\$") CLIENTEXISTS=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -E "^V" | grep -c -E "/CN=$CLIENT\$")
else
CLIENTEXISTS=0
fi
if [[ $CLIENTEXISTS != '0' ]]; then if [[ $CLIENTEXISTS != '0' ]]; then
log_error "The specified client CN was already found in easy-rsa, please choose another name." log_error "The specified client CN was already found in easy-rsa, please choose another name."
exit 1 exit 1
else fi
cd /etc/openvpn/server/easy-rsa/ || return
log_info "Generating client certificate..." log_info "Generating client certificate..."
export EASYRSA_CERT_EXPIRE=$CLIENT_CERT_DURATION_DAYS export EASYRSA_CERT_EXPIRE=$CLIENT_CERT_DURATION_DAYS
# Determine easyrsa command based on auth mode
local easyrsa_cmd cert_desc
if [[ $AUTH_MODE == "pki" ]]; then
easyrsa_cmd="build-client-full"
cert_desc="client certificate"
else
easyrsa_cmd="self-sign-client"
cert_desc="self-signed client certificate"
fi
case $PASS in case $PASS in
1) 1)
run_cmd_fatal "Building client certificate" ./easyrsa --batch build-client-full "$CLIENT" nopass run_cmd_fatal "Building $cert_desc" ./easyrsa --batch "$easyrsa_cmd" "$CLIENT" nopass
;; ;;
2) 2)
if [[ -z "$PASSPHRASE" ]]; then if [[ -z "$PASSPHRASE" ]]; then
log_warn "You will be asked for the client password below" 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 "$easyrsa_cmd" "$CLIENT"; then
if ! ./easyrsa --batch build-client-full "$CLIENT"; then log_fatal "Building $cert_desc failed"
log_fatal "Building client certificate failed"
fi fi
else else
log_info "Using provided passphrase for client certificate" log_info "Using provided passphrase for client certificate"
# Use env var to avoid exposing passphrase in install log
export EASYRSA_PASSPHRASE="$PASSPHRASE" export EASYRSA_PASSPHRASE="$PASSPHRASE"
run_cmd_fatal "Building client certificate" ./easyrsa --batch --passin=env:EASYRSA_PASSPHRASE --passout=env:EASYRSA_PASSPHRASE build-client-full "$CLIENT" run_cmd_fatal "Building $cert_desc" ./easyrsa --batch --passin=env:EASYRSA_PASSPHRASE --passout=env:EASYRSA_PASSPHRASE "$easyrsa_cmd" "$CLIENT"
unset EASYRSA_PASSPHRASE unset EASYRSA_PASSPHRASE
fi fi
;; ;;
esac esac
log_success "Client $CLIENT added and is valid for $CLIENT_CERT_DURATION_DAYS days."
# Fingerprint mode: register client fingerprint with server
if [[ $AUTH_MODE == "fingerprint" ]]; then
CLIENT_FINGERPRINT=$(openssl x509 -in "pki/issued/$CLIENT.crt" -fingerprint -sha256 -noout | cut -d'=' -f2)
if [[ -z $CLIENT_FINGERPRINT ]]; then
log_error "Failed to extract client certificate fingerprint"
exit 1
fi fi
log_info "Client fingerprint: $CLIENT_FINGERPRINT"
# Add fingerprint to server.conf's <peer-fingerprint> block
# Create the block if this is the first client
if ! grep -q '<peer-fingerprint>' /etc/openvpn/server/server.conf; then
echo "# Client fingerprints are listed below
<peer-fingerprint>
# $CLIENT
$CLIENT_FINGERPRINT
</peer-fingerprint>" >>/etc/openvpn/server/server.conf
else
# Insert comment and fingerprint before closing tag
sed -i "/<\/peer-fingerprint>/i # $CLIENT\n$CLIENT_FINGERPRINT" /etc/openvpn/server/server.conf
fi
# Reload OpenVPN to pick up new fingerprint
log_info "Reloading OpenVPN to apply new fingerprint..."
if systemctl is-active --quiet openvpn-server@server; then
systemctl reload openvpn-server@server 2>/dev/null || systemctl restart openvpn-server@server
fi
fi
log_success "Client $CLIENT added and is valid for $CLIENT_CERT_DURATION_DAYS days."
# Write the .ovpn config file with proper path and permissions # Write the .ovpn config file with proper path and permissions
writeClientConfig "$CLIENT" writeClientConfig "$CLIENT"
@@ -3714,8 +3910,6 @@ function newClient() {
log_menu "" log_menu ""
log_success "The configuration file has been written to $GENERATED_CONFIG_PATH." log_success "The configuration file has been written to $GENERATED_CONFIG_PATH."
log_info "Download the .ovpn file and import it in your OpenVPN client." log_info "Download the .ovpn file and import it in your OpenVPN client."
exit 0
} }
function revokeClient() { function revokeClient() {
@@ -3724,13 +3918,45 @@ function revokeClient() {
selectClient selectClient
cd /etc/openvpn/server/easy-rsa/ || return cd /etc/openvpn/server/easy-rsa/ || return
# Read auth mode
local auth_mode="pki"
if [[ -f AUTH_MODE_GENERATED ]]; then
auth_mode=$(cat AUTH_MODE_GENERATED)
fi
log_info "Revoking certificate for $CLIENT..." log_info "Revoking certificate for $CLIENT..."
if [[ $auth_mode == "pki" ]]; then
# PKI mode: use Easy-RSA revocation and CRL
run_cmd_fatal "Revoking certificate" ./easyrsa --batch revoke-issued "$CLIENT" run_cmd_fatal "Revoking certificate" ./easyrsa --batch revoke-issued "$CLIENT"
regenerateCRL regenerateCRL
run_cmd "Backing up index" cp /etc/openvpn/server/easy-rsa/pki/index.txt{,.bk}
else
# Fingerprint mode: remove fingerprint from server.conf and delete cert files
log_info "Removing client fingerprint from server configuration..."
# Remove comment line and fingerprint line below it from server.conf
sed -i "/^# $CLIENT\$/{N;d;}" /etc/openvpn/server/server.conf
# Remove client certificate and key
rm -f "pki/issued/$CLIENT.crt" "pki/private/$CLIENT.key"
# Mark as revoked in index.txt if it exists (for client listing)
if [[ -f pki/index.txt ]]; then
sed -i "s|^V\(.*\)/CN=$CLIENT\$|R\1/CN=$CLIENT|" pki/index.txt
fi
# Reload OpenVPN to apply fingerprint removal
log_info "Reloading OpenVPN to apply fingerprint removal..."
if systemctl is-active --quiet openvpn-server@server; then
systemctl reload openvpn-server@server 2>/dev/null || systemctl restart openvpn-server@server
fi
fi
run_cmd "Removing client config from /home" find /home/ -maxdepth 2 -name "$CLIENT.ovpn" -delete run_cmd "Removing client config from /home" find /home/ -maxdepth 2 -name "$CLIENT.ovpn" -delete
run_cmd "Removing client config from /root" rm -f "/root/$CLIENT.ovpn" run_cmd "Removing client config from /root" rm -f "/root/$CLIENT.ovpn"
run_cmd "Removing IP assignment" sed -i "/^$CLIENT,.*/d" /etc/openvpn/server/ipp.txt run_cmd "Removing IP assignment" sed -i "/^$CLIENT,.*/d" /etc/openvpn/server/ipp.txt
run_cmd "Backing up index" cp /etc/openvpn/server/easy-rsa/pki/index.txt{,.bk}
# Disconnect the client if currently connected # Disconnect the client if currently connected
disconnectClient "$CLIENT" disconnectClient "$CLIENT"
@@ -4102,6 +4328,7 @@ function manageMenu() {
case $menu_option in case $menu_option in
1) 1)
newClient newClient
exit 0
;; ;;
2) 2)
listClients listClients

View File

@@ -42,6 +42,10 @@ TLS_KEY_FILE="${TLS_KEY_FILE:-tls-crypt-v2.key}"
TLS_VERSION_MIN="${TLS_VERSION_MIN:-1.2}" TLS_VERSION_MIN="${TLS_VERSION_MIN:-1.2}"
TLS13_CIPHERSUITES="${TLS13_CIPHERSUITES:-TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256}" TLS13_CIPHERSUITES="${TLS13_CIPHERSUITES:-TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256}"
# Authentication mode configuration
# AUTH_MODE: pki (default, CA-based) or fingerprint (peer-fingerprint, OpenVPN 2.6+)
AUTH_MODE="${AUTH_MODE:-pki}"
# Build install command with CLI flags (using array for proper quoting) # Build install command with CLI flags (using array for proper quoting)
INSTALL_CMD=(/opt/openvpn-install.sh install) INSTALL_CMD=(/opt/openvpn-install.sh install)
INSTALL_CMD+=(--endpoint openvpn-server) INSTALL_CMD+=(--endpoint openvpn-server)
@@ -75,6 +79,12 @@ if [ "$TLS13_CIPHERSUITES" != "TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS
echo "Testing TLS 1.3 ciphersuites: $TLS13_CIPHERSUITES" echo "Testing TLS 1.3 ciphersuites: $TLS13_CIPHERSUITES"
fi fi
# Add auth mode if non-default
if [ "$AUTH_MODE" != "pki" ]; then
INSTALL_CMD+=(--auth-mode "$AUTH_MODE")
echo "Testing authentication mode: $AUTH_MODE"
fi
echo "Running OpenVPN install script..." echo "Running OpenVPN install script..."
echo "Command: ${INSTALL_CMD[*]}" echo "Command: ${INSTALL_CMD[*]}"
# Run in subshell because the script calls 'exit 0' after generating client config # Run in subshell because the script calls 'exit 0' after generating client config
@@ -104,16 +114,26 @@ fi
# Verify all expected files were created # Verify all expected files were created
echo "Verifying installation..." echo "Verifying installation..."
MISSING_FILES=0 MISSING_FILES=0
# Build list of required files # Build list of required files based on auth mode
REQUIRED_FILES=( REQUIRED_FILES=(
/etc/openvpn/server/server.conf /etc/openvpn/server/server.conf
/etc/openvpn/server/ca.crt
/etc/openvpn/server/ca.key
"/etc/openvpn/server/$TLS_KEY_FILE" "/etc/openvpn/server/$TLS_KEY_FILE"
/etc/openvpn/server/crl.pem
/etc/openvpn/server/easy-rsa/pki/ca.crt
/root/testclient.ovpn /root/testclient.ovpn
) )
if [ "$AUTH_MODE" = "pki" ]; then
# PKI mode requires CA and CRL files
REQUIRED_FILES+=(
/etc/openvpn/server/ca.crt
/etc/openvpn/server/ca.key
/etc/openvpn/server/crl.pem
/etc/openvpn/server/easy-rsa/pki/ca.crt
)
else
# Fingerprint mode requires server fingerprint file
REQUIRED_FILES+=(
/etc/openvpn/server/server-fingerprint
)
fi
# Only check for iptables script if firewalld and nftables are not active # Only check for iptables script if firewalld and nftables are not active
if ! systemctl is-active --quiet firewalld && ! systemctl is-active --quiet nftables; then if ! systemctl is-active --quiet firewalld && ! systemctl is-active --quiet nftables; then
REQUIRED_FILES+=(/etc/iptables/add-openvpn-rules.sh) REQUIRED_FILES+=(/etc/iptables/add-openvpn-rules.sh)
@@ -197,13 +217,16 @@ sed -i 's/^remote .*/remote openvpn-server 1194/' /shared/client.ovpn
echo "Client config copied to /shared/client.ovpn" echo "Client config copied to /shared/client.ovpn"
# Write VPN network info to shared volume for client tests # Write VPN network info to shared volume for client tests
echo "VPN_SUBNET_IPV4=$VPN_SUBNET_IPV4" >/shared/vpn-config.env {
echo "VPN_GATEWAY=$VPN_GATEWAY" >>/shared/vpn-config.env echo "VPN_SUBNET_IPV4=$VPN_SUBNET_IPV4"
echo "CLIENT_IPV6=$CLIENT_IPV6" >>/shared/vpn-config.env echo "VPN_GATEWAY=$VPN_GATEWAY"
if [ "$CLIENT_IPV6" = "y" ]; then echo "CLIENT_IPV6=$CLIENT_IPV6"
echo "VPN_SUBNET_IPV6=$VPN_SUBNET_IPV6" >>/shared/vpn-config.env echo "AUTH_MODE=$AUTH_MODE"
echo "VPN_GATEWAY_IPV6=$VPN_GATEWAY_IPV6" >>/shared/vpn-config.env if [ "$CLIENT_IPV6" = "y" ]; then
fi echo "VPN_SUBNET_IPV6=$VPN_SUBNET_IPV6"
echo "VPN_GATEWAY_IPV6=$VPN_GATEWAY_IPV6"
fi
} >/shared/vpn-config.env
echo "VPN config written to /shared/vpn-config.env" echo "VPN config written to /shared/vpn-config.env"
# ===================================================== # =====================================================
@@ -396,12 +419,14 @@ else
exit 1 exit 1
fi fi
# Verify CRL was updated # Verify CRL was updated (PKI mode only)
if [ -f /etc/openvpn/server/crl.pem ]; then if [ "$AUTH_MODE" = "pki" ]; then
if [ -f /etc/openvpn/server/crl.pem ]; then
echo "PASS: CRL file exists" echo "PASS: CRL file exists"
else else
echo "FAIL: CRL file missing after renewal" echo "FAIL: CRL file missing after renewal"
exit 1 exit 1
fi
fi fi
# Update shared client config with renewed certificate # Update shared client config with renewed certificate
@@ -815,13 +840,25 @@ else
exit 1 exit 1
fi fi
# Verify certificate is marked as revoked in index.txt # Verify revocation was applied correctly
if tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -q "^R.*CN=$REVOKE_CLIENT\$"; then if [ "$AUTH_MODE" = "pki" ]; then
# PKI mode: verify certificate is marked as revoked in index.txt
if tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -q "^R.*CN=$REVOKE_CLIENT\$"; then
echo "PASS: Certificate marked as revoked in index.txt" echo "PASS: Certificate marked as revoked in index.txt"
else else
echo "FAIL: Certificate not marked as revoked" echo "FAIL: Certificate not marked as revoked"
cat /etc/openvpn/server/easy-rsa/pki/index.txt cat /etc/openvpn/server/easy-rsa/pki/index.txt
exit 1 exit 1
fi
else
# Fingerprint mode: verify fingerprint was removed from server.conf
if ! grep -q "# $REVOKE_CLIENT\$" /etc/openvpn/server/server.conf; then
echo "PASS: Client fingerprint removed from server.conf"
else
echo "FAIL: Client fingerprint still present in server.conf"
grep "$REVOKE_CLIENT" /etc/openvpn/server/server.conf || true
exit 1
fi
fi fi
# Wait for client to confirm it was disconnected by the revoke # Wait for client to confirm it was disconnected by the revoke
@@ -883,13 +920,26 @@ else
exit 1 exit 1
fi fi
# Verify certificate count (3 certs: testclient valid, testclient revoked from renewal, revoketest revoked) # Verify certificate count (varies by auth mode)
if grep -q "Found 3 client certificate(s)" "$LIST_OUTPUT"; then if [ "$AUTH_MODE" = "pki" ]; then
# PKI mode: 3 certs (testclient valid, testclient revoked from renewal, revoketest revoked)
if grep -q "Found 3 client certificate(s)" "$LIST_OUTPUT"; then
echo "PASS: List shows correct certificate count" echo "PASS: List shows correct certificate count"
else else
echo "FAIL: List does not show correct certificate count" echo "FAIL: List does not show correct certificate count"
cat "$LIST_OUTPUT" cat "$LIST_OUTPUT"
exit 1 exit 1
fi
else
# Fingerprint mode: 2 certs (testclient valid, revoketest revoked)
# In fingerprint mode, renewal doesn't create a separate revoked entry
if grep -q "Found [23] client certificate(s)" "$LIST_OUTPUT"; then
echo "PASS: List shows correct certificate count for fingerprint mode"
else
echo "FAIL: List does not show correct certificate count"
cat "$LIST_OUTPUT"
exit 1
fi
fi fi
# Test JSON output # Test JSON output
@@ -906,14 +956,25 @@ else
exit 1 exit 1
fi fi
# Verify client count in JSON # Verify client count in JSON (varies by auth mode)
JSON_CLIENT_COUNT=$(jq '.clients | length' "$LIST_JSON_OUTPUT") JSON_CLIENT_COUNT=$(jq '.clients | length' "$LIST_JSON_OUTPUT")
if [ "$JSON_CLIENT_COUNT" -eq 3 ]; then if [ "$AUTH_MODE" = "pki" ]; then
if [ "$JSON_CLIENT_COUNT" -eq 3 ]; then
echo "PASS: Client list JSON has correct count ($JSON_CLIENT_COUNT)" echo "PASS: Client list JSON has correct count ($JSON_CLIENT_COUNT)"
else else
echo "FAIL: Client list JSON has wrong count: $JSON_CLIENT_COUNT (expected 3)" echo "FAIL: Client list JSON has wrong count: $JSON_CLIENT_COUNT (expected 3)"
cat "$LIST_JSON_OUTPUT" cat "$LIST_JSON_OUTPUT"
exit 1 exit 1
fi
else
# Fingerprint mode may have fewer entries
if [ "$JSON_CLIENT_COUNT" -ge 2 ] && [ "$JSON_CLIENT_COUNT" -le 3 ]; then
echo "PASS: Client list JSON has correct count for fingerprint mode ($JSON_CLIENT_COUNT)"
else
echo "FAIL: Client list JSON has wrong count: $JSON_CLIENT_COUNT (expected 2-3)"
cat "$LIST_JSON_OUTPUT"
exit 1
fi
fi fi
# Verify valid client in JSON # Verify valid client in JSON
@@ -955,25 +1016,37 @@ else
exit 1 exit 1
fi fi
# Verify the new certificate is valid (V) in index.txt # Verify the new certificate is valid
if tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -q "^V.*CN=$REVOKE_CLIENT\$"; then if [ "$AUTH_MODE" = "pki" ]; then
# PKI mode: verify in index.txt
if tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -q "^V.*CN=$REVOKE_CLIENT\$"; then
echo "PASS: New certificate is valid in index.txt" echo "PASS: New certificate is valid in index.txt"
else else
echo "FAIL: New certificate not marked as valid" echo "FAIL: New certificate not marked as valid"
cat /etc/openvpn/server/easy-rsa/pki/index.txt cat /etc/openvpn/server/easy-rsa/pki/index.txt
exit 1 exit 1
fi fi
# Verify there's also a revoked entry (both should exist) # Verify there's also a revoked entry (both should exist)
REVOKED_COUNT=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^R.*CN=$REVOKE_CLIENT\$") REVOKED_COUNT=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^R.*CN=$REVOKE_CLIENT\$")
VALID_COUNT=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^V.*CN=$REVOKE_CLIENT\$") VALID_COUNT=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^V.*CN=$REVOKE_CLIENT\$")
echo "Certificates for '$REVOKE_CLIENT': $REVOKED_COUNT revoked, $VALID_COUNT valid" echo "Certificates for '$REVOKE_CLIENT': $REVOKED_COUNT revoked, $VALID_COUNT valid"
if [ "$REVOKED_COUNT" -ge 1 ] && [ "$VALID_COUNT" -eq 1 ]; then if [ "$REVOKED_COUNT" -ge 1 ] && [ "$VALID_COUNT" -eq 1 ]; then
echo "PASS: Both revoked and new valid certificate entries exist" echo "PASS: Both revoked and new valid certificate entries exist"
else else
echo "FAIL: Unexpected certificate state" echo "FAIL: Unexpected certificate state"
cat /etc/openvpn/server/easy-rsa/pki/index.txt cat /etc/openvpn/server/easy-rsa/pki/index.txt
exit 1 exit 1
fi
else
# Fingerprint mode: verify fingerprint was added back to server.conf
if grep -q "# $REVOKE_CLIENT\$" /etc/openvpn/server/server.conf; then
echo "PASS: New client fingerprint added to server.conf"
else
echo "FAIL: New client fingerprint not found in server.conf"
cat /etc/openvpn/server/server.conf | grep -A5 "<peer-fingerprint>" || true
exit 1
fi
fi fi
# Copy the new config # Copy the new config