From 08f6f1e7cc7bdeb2610b3ef22c14979bb1d5ef67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Alvergnat?= Date: Sat, 13 Dec 2025 21:12:23 +0100 Subject: [PATCH] feat: add CLIENT_FILEPATH env var and fix client file ownership (#962) Fix #961 - Adds CLIENT_FILEPATH env var to specify custom output path for .ovpn files - Automatically sets correct ownership (chown) and permissions (chmod go-rw) when client name matches a system user --------- Co-authored-by: Stanislas Lange --- README.md | 10 +++++-- openvpn-install.sh | 72 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index f3f9675..e9764f6 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,9 @@ If you want to customise your installation, you can export them or specify them - `CLIENT_CERT_DURATION_DAYS=3650` - `SERVER_CERT_DURATION_DAYS=3650` - `NEW_CLIENT=y` (set to `n` to skip client creation after installation) +- `CLIENT_FILEPATH=/custom/path/client.ovpn` (optional, overrides default output path) + +The `.ovpn` file is saved to `CLIENT_FILEPATH` if defined, otherwise: the client's home directory if it exists (`/home/$CLIENT`), otherwise `SUDO_USER`'s home, otherwise `/root`. When the client name matches a system user, the script automatically sets proper ownership and permissions on the file. If the server is behind NAT, you can specify its endpoint with the `ENDPOINT` variable. If the endpoint is the public IP address which it is behind, you can use `ENDPOINT=$(curl -4 ifconfig.co)` (the script will default to this). The endpoint can be an IPv4 or a domain. @@ -119,18 +122,19 @@ The following Bash script adds a new user `foo` to an existing OpenVPN configura export MENU_OPTION="1" export CLIENT="foo" export PASS="1" # set to "2" for a password-protected client, and set PASSPHRASE +export CLIENT_FILEPATH="" # optional, custom path for .ovpn file ./openvpn-install.sh ``` ### Headless User Revocation -It's also possible to automate the revocation of an existing user. The key is to provide the `MENU_OPTION` variable set to `2` along with either `CLIENT` (client name) or `CLIENTNUMBER` (1-based index from the client list). +It's also possible to automate the revocation of an existing user. The key is to provide the `MENU_OPTION` variable set to `3` along with either `CLIENT` (client name) or `CLIENTNUMBER` (1-based index from the client list). The following Bash script revokes the existing user `foo`: ```bash #!/bin/bash -export MENU_OPTION="2" +export MENU_OPTION="3" export CLIENT="foo" ./openvpn-install.sh ``` @@ -139,7 +143,7 @@ Alternatively, you can use the client number: ```bash #!/bin/bash -export MENU_OPTION="2" +export MENU_OPTION="3" export CLIENTNUMBER="1" # Revokes the first client in the list ./openvpn-install.sh ``` diff --git a/openvpn-install.sh b/openvpn-install.sh index 97fe45b..54b0e62 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -1562,6 +1562,29 @@ function getHomeDir() { fi } +# Helper function to get the owner of a client config file (if client matches a system user) +function getClientOwner() { + local client="$1" + if [ -e "/home/${client}" ]; then + echo "${client}" + elif [ "${SUDO_USER}" ] && [ "${SUDO_USER}" != "root" ]; then + echo "${SUDO_USER}" + fi +} + +# Helper function to set proper ownership and permissions on client config file +function setClientConfigPermissions() { + local filepath="$1" + local owner="$2" + + if [[ -n "$owner" ]]; then + local owner_group + owner_group=$(id -gn "$owner") + chmod go-rw "$filepath" + chown "$owner:$owner_group" "$filepath" + fi +} + # Helper function to regenerate the CRL after certificate changes function regenerateCRL() { export EASYRSA_CRL_DAYS=$DEFAULT_CRL_VALIDITY_DURATION_DAYS @@ -1572,9 +1595,10 @@ function regenerateCRL() { } # Helper function to generate .ovpn client config file +# Usage: generateClientConfig function generateClientConfig() { local client="$1" - local home_dir="$2" + local filepath="$2" # Determine if we use tls-crypt-v2, tls-crypt, or tls-auth local tls_sig="" @@ -1587,7 +1611,7 @@ function generateClientConfig() { fi # Generate the custom client.ovpn - run_cmd "Creating client config" cp /etc/openvpn/server/client-template.txt "$home_dir/$client.ovpn" + run_cmd "Creating client config" cp /etc/openvpn/server/client-template.txt "$filepath" { echo "" cat "/etc/openvpn/server/easy-rsa/pki/ca.crt" @@ -1628,7 +1652,7 @@ function generateClientConfig() { echo "" ;; esac - } >>"$home_dir/$client.ovpn" + } >>"$filepath" } # Helper function to list valid clients and select one @@ -1821,12 +1845,26 @@ function newClient() { log_success "Client $CLIENT added and is valid for $CLIENT_CERT_DURATION_DAYS days." fi + # Determine output file path + local clientFilePath + if [[ -n "$CLIENT_FILEPATH" ]]; then + clientFilePath="$CLIENT_FILEPATH" + else + local homeDir + homeDir=$(getHomeDir "$CLIENT") + clientFilePath="$homeDir/$CLIENT.ovpn" + fi + # Generate the .ovpn config file - homeDir=$(getHomeDir "$CLIENT") - generateClientConfig "$CLIENT" "$homeDir" + generateClientConfig "$CLIENT" "$clientFilePath" + + # Set proper ownership and permissions if client matches a system user + local clientOwner + clientOwner=$(getClientOwner "$CLIENT") + setClientConfigPermissions "$clientFilePath" "$clientOwner" log_menu "" - log_success "The configuration file has been written to $homeDir/$CLIENT.ovpn." + log_success "The configuration file has been written to $clientFilePath." log_info "Download the .ovpn file and import it in your OpenVPN client." exit 0 @@ -1850,7 +1888,7 @@ function revokeClient() { } function renewClient() { - local homeDir client_cert_duration_days + local client_cert_duration_days log_header "Renew Client Certificate" log_prompt "Select the existing client certificate you want to renew" @@ -1883,13 +1921,27 @@ function renewClient() { # Regenerate the CRL regenerateCRL + # Determine output file path + local clientFilePath + if [[ -n "$CLIENT_FILEPATH" ]]; then + clientFilePath="$CLIENT_FILEPATH" + else + local homeDir + homeDir=$(getHomeDir "$CLIENT") + clientFilePath="$homeDir/$CLIENT.ovpn" + fi + # Regenerate the .ovpn file with the new certificate - homeDir=$(getHomeDir "$CLIENT") - generateClientConfig "$CLIENT" "$homeDir" + generateClientConfig "$CLIENT" "$clientFilePath" + + # Set proper ownership and permissions if client matches a system user + local clientOwner + clientOwner=$(getClientOwner "$CLIENT") + setClientConfigPermissions "$clientFilePath" "$clientOwner" log_menu "" log_success "Certificate for client $CLIENT renewed and is valid for $client_cert_duration_days days." - log_info "The new configuration file has been written to $homeDir/$CLIENT.ovpn." + log_info "The new configuration file has been written to $clientFilePath." log_info "Download the new .ovpn file and import it in your OpenVPN client." }