#!/bin/bash # # https://github.com/hwdsl2/openvpn-install # # Based on the work of Nyr and contributors at: # https://github.com/Nyr/openvpn-install # # Copyright (c) 2022 Lin Song # Copyright (c) 2013-2022 Nyr # # Released under the MIT License. # # Permission is hereby granted, free of charge, to any person obtaining a copy of # this software and associated documentation files (the "Software"), to deal in # the Software without restriction, including without limitation the rights to # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of # the Software, and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. check_ip() { IP_REGEX='^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$' printf '%s' "$1" | tr -d '\n' | grep -Eq "$IP_REGEX" } abort_and_exit() { echo "Abort. No changes were made." >&2 exit 1 } get_export_dir() { export_to_home_dir=0 export_dir=~/ if [ -n "$SUDO_USER" ] && getent group "$SUDO_USER" >/dev/null 2>&1; then user_home_dir=$(getent passwd "$SUDO_USER" 2>/dev/null | cut -d: -f6) if [ -d "$user_home_dir" ] && [ "$user_home_dir" != "/" ]; then export_dir="$user_home_dir/" export_to_home_dir=1 fi fi } find_public_ip() { ip_url1="http://ipv4.icanhazip.com" ip_url2="http://ip1.dynupdate.no-ip.com" # Get public IP and sanitize with grep get_public_ip=$(grep -m 1 -oE '^[0-9]{1,3}(\.[0-9]{1,3}){3}$' <<< "$(wget -T 10 -t 1 -4qO- "$ip_url1" || curl -m 10 -4Ls "$ip_url1")") if ! check_ip "$get_public_ip"; then get_public_ip=$(grep -m 1 -oE '^[0-9]{1,3}(\.[0-9]{1,3}){3}$' <<< "$(wget -T 10 -t 1 -4qO- "$ip_url2" || curl -m 10 -4Ls "$ip_url2")") fi } update_sysctl() { # Enable net.ipv4.ip_forward for the system echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-openvpn-forward.conf if [[ -n "$ip6" ]]; then # Enable net.ipv6.conf.all.forwarding for the system echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.d/99-openvpn-forward.conf fi # Optimize sysctl settings such as TCP buffer sizes cat > /etc/sysctl.d/99-openvpn-optimize.conf <<'EOF' kernel.msgmnb = 65536 kernel.msgmax = 65536 net.core.wmem_max = 16777216 net.core.rmem_max = 16777216 net.ipv4.tcp_rmem = 4096 87380 16777216 net.ipv4.tcp_wmem = 4096 87380 16777216 EOF # Enable TCP BBR congestion control if kernel version >= 4.20 if modprobe -q tcp_bbr \ && printf '%s\n%s' "4.20" "$(uname -r)" | sort -C -V; then cat >> /etc/sysctl.d/99-openvpn-optimize.conf <<'EOF' net.core.default_qdisc = fq net.ipv4.tcp_congestion_control = bbr EOF fi # Apply sysctl settings sysctl -e -q -p /etc/sysctl.d/99-openvpn-forward.conf sysctl -e -q -p /etc/sysctl.d/99-openvpn-optimize.conf } new_client() { get_export_dir # Generates the custom client.ovpn { cat /etc/openvpn/server/client-common.txt echo "" cat /etc/openvpn/server/easy-rsa/pki/ca.crt echo "" echo "" sed -ne '/BEGIN CERTIFICATE/,$ p' /etc/openvpn/server/easy-rsa/pki/issued/"$client".crt echo "" echo "" cat /etc/openvpn/server/easy-rsa/pki/private/"$client".key echo "" echo "" sed -ne '/BEGIN OpenVPN Static key/,$ p' /etc/openvpn/server/tc.key echo "" } > "$export_dir$client".ovpn if [ "$export_to_home_dir" = 1 ]; then chown "$SUDO_USER:$SUDO_USER" "$export_dir$client".ovpn fi chmod 600 "$export_dir$client".ovpn } show_usage() { if [ -n "$1" ]; then echo "Error: $1" >&2 fi cat 1>&2 </dev/net/tun ) 2>/dev/null; then echo "The system does not have the TUN device available. TUN needs to be enabled before running this installer." exit 1 fi auto=0 if [[ ! -e /etc/openvpn/server/server.conf ]]; then if [ "$os" = "centos" ]; then if grep -qs "hwdsl2 VPN script" /etc/sysconfig/nftables.conf \ || systemctl is-active --quiet nftables 2>/dev/null; then echo "This system has nftables enabled, which is not supported by this installer." exit 1 fi fi while [ "$#" -gt 0 ]; do case $1 in --auto) auto=1 shift ;; -h|--help) show_usage ;; *) show_usage "Unknown parameter: $1" ;; esac done # Detect some Debian minimal setups where neither wget nor curl are installed if ! hash wget 2>/dev/null && ! hash curl 2>/dev/null; then if [ "$auto" = 0 ]; then echo "Wget is required to use this installer." read -n1 -r -p "Press any key to install Wget and continue..." fi export DEBIAN_FRONTEND=noninteractive ( set -x apt-get -yqq update || apt-get -yqq update apt-get -yqq install wget >/dev/null ) || exit 1 fi if [ "$auto" = 0 ]; then echo echo 'Welcome to this OpenVPN server installer!' echo echo 'I need to ask you a few questions before starting setup.' echo 'You can use the default options and just press enter if you are OK with them.' else echo echo 'Starting OpenVPN setup using default options.' fi # If system has a single IPv4, it is selected automatically. if [[ $(ip -4 addr | grep inet | grep -vEc '127(\.[0-9]{1,3}){3}') -eq 1 ]]; then ip=$(ip -4 addr | grep inet | grep -vE '127(\.[0-9]{1,3}){3}' | cut -d '/' -f 1 | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}') else # Use the IP address on the default route ip=$(ip -4 route get 1 | sed 's/ uid .*//' | awk '{print $NF;exit}' 2>/dev/null) if ! check_ip "$ip"; then find_public_ip ip_match=0 if [ -n "$get_public_ip" ]; then ip_list=$(ip -4 addr | grep inet | grep -vE '127(\.[0-9]{1,3}){3}' | cut -d '/' -f 1 | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}') while IFS= read -r line; do if [ "$line" = "$get_public_ip" ]; then ip_match=1 ip="$line" fi done <<< "$ip_list" fi if [ "$ip_match" = 0 ]; then if [ "$auto" = 0 ]; then echo echo "Which IPv4 address should be used?" number_of_ip=$(ip -4 addr | grep inet | grep -vEc '127(\.[0-9]{1,3}){3}') ip -4 addr | grep inet | grep -vE '127(\.[0-9]{1,3}){3}' | cut -d '/' -f 1 | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}' | nl -s ') ' read -rp "IPv4 address [1]: " ip_number until [[ -z "$ip_number" || "$ip_number" =~ ^[0-9]+$ && "$ip_number" -le "$number_of_ip" ]]; do echo "$ip_number: invalid selection." read -rp "IPv4 address [1]: " ip_number done [[ -z "$ip_number" ]] && ip_number=1 else ip_number=1 fi ip=$(ip -4 addr | grep inet | grep -vE '127(\.[0-9]{1,3}){3}' | cut -d '/' -f 1 | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}' | sed -n "$ip_number"p) fi fi fi # If $ip is a private IP address, the server must be behind NAT if printf '%s' "$ip" | grep -qE '^(10|127|172\.(1[6-9]|2[0-9]|3[0-1])|192\.168|169\.254)\.'; then find_public_ip if ! check_ip "$get_public_ip"; then if [ "$auto" = 0 ]; then echo echo "This server is behind NAT. What is the public IPv4 address?" read -rp "Public IPv4 address: " public_ip until check_ip "$public_ip"; do echo "Invalid input." read -rp "Public IPv4 address: " public_ip done else echo "Error: Could not detect this server's public IP." >&2 echo "Abort. No changes were made." >&2 exit 1 fi else public_ip="$get_public_ip" fi fi if [ "$auto" != 0 ]; then echo printf '%s' "Server IP: " [ -n "$public_ip" ] && printf '%s\n' "$public_ip" || printf '%s\n' "$ip" echo "Port: UDP/1194" echo "Client name: client" echo "Client DNS: Google Public DNS" fi # If system has a single IPv6, it is selected automatically if [[ $(ip -6 addr | grep -c 'inet6 [23]') -eq 1 ]]; then ip6=$(ip -6 addr | grep 'inet6 [23]' | cut -d '/' -f 1 | grep -oE '([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}') fi # If system has multiple IPv6, ask the user to select one if [[ $(ip -6 addr | grep -c 'inet6 [23]') -gt 1 ]]; then if [ "$auto" = 0 ]; then echo echo "Which IPv6 address should be used?" number_of_ip6=$(ip -6 addr | grep -c 'inet6 [23]') ip -6 addr | grep 'inet6 [23]' | cut -d '/' -f 1 | grep -oE '([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}' | nl -s ') ' read -rp "IPv6 address [1]: " ip6_number until [[ -z "$ip6_number" || "$ip6_number" =~ ^[0-9]+$ && "$ip6_number" -le "$number_of_ip6" ]]; do echo "$ip6_number: invalid selection." read -rp "IPv6 address [1]: " ip6_number done [[ -z "$ip6_number" ]] && ip6_number=1 else ip6_number=1 fi ip6=$(ip -6 addr | grep 'inet6 [23]' | cut -d '/' -f 1 | grep -oE '([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}' | sed -n "$ip6_number"p) fi if [ "$auto" = 0 ]; then echo echo "Which protocol should OpenVPN use?" echo " 1) UDP (recommended)" echo " 2) TCP" read -rp "Protocol [1]: " protocol until [[ -z "$protocol" || "$protocol" =~ ^[12]$ ]]; do echo "$protocol: invalid selection." read -rp "Protocol [1]: " protocol done else protocol=1 fi case "$protocol" in 1|"") protocol=udp ;; 2) protocol=tcp ;; esac if [ "$auto" = 0 ]; then echo echo "What port should OpenVPN listen to?" read -rp "Port [1194]: " port until [[ -z "$port" || "$port" =~ ^[0-9]+$ && "$port" -le 65535 ]]; do echo "$port: invalid port." read -rp "Port [1194]: " port done [[ -z "$port" ]] && port=1194 else port=1194 fi if [ "$auto" = 0 ]; then echo echo "Select a DNS server for the clients:" echo " 1) Current system resolvers" echo " 2) Google Public DNS" echo " 3) Cloudflare DNS" echo " 4) OpenDNS" echo " 5) Quad9" echo " 6) AdGuard DNS" echo " 7) Custom" read -rp "DNS server [2]: " dns until [[ -z "$dns" || "$dns" =~ ^[1-7]$ ]]; do echo "$dns: invalid selection." read -rp "DNS server [2]: " dns done else dns=2 fi if [ "$dns" = 7 ]; then read -rp "Enter primary DNS server: " dns1 until check_ip "$dns1"; do echo "Invalid DNS server." read -rp "Enter primary DNS server: " dns1 done read -rp "Enter secondary DNS server (Enter to skip): " dns2 until [ -z "$dns2" ] || check_ip "$dns2"; do echo "Invalid DNS server." read -rp "Enter secondary DNS server (Enter to skip): " dns2 done fi if [ "$auto" = 0 ]; then echo echo "Enter a name for the first client:" read -rp "Name [client]: " unsanitized_client # Allow a limited set of characters to avoid conflicts client=$(sed 's/[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-]/_/g' <<< "$unsanitized_client") [[ -z "$client" ]] && client=client else client=client fi if [ "$auto" = 0 ]; then echo echo "OpenVPN installation is ready to begin." fi # Install a firewall if firewalld or iptables are not already available if ! systemctl is-active --quiet firewalld.service && ! hash iptables 2>/dev/null; then if [[ "$os" == "centos" || "$os" == "fedora" ]]; then firewall="firewalld" # We don't want to silently enable firewalld, so we give a subtle warning # If the user continues, firewalld will be installed and enabled during setup echo echo "Note: firewalld, which is required to manage routing tables, will also be installed." elif [[ "$os" == "debian" || "$os" == "ubuntu" ]]; then # iptables is way less invasive than firewalld so no warning is given firewall="iptables" fi fi if [ "$auto" = 0 ]; then read -n1 -r -p "Press any key to continue..." fi echo echo "Installing OpenVPN, please wait..." # If running inside a container, disable LimitNPROC to prevent conflicts if systemd-detect-virt -cq; then mkdir /etc/systemd/system/openvpn-server@server.service.d/ 2>/dev/null echo "[Service] LimitNPROC=infinity" > /etc/systemd/system/openvpn-server@server.service.d/disable-limitnproc.conf fi if [[ "$os" = "debian" || "$os" = "ubuntu" ]]; then export DEBIAN_FRONTEND=noninteractive ( set -x apt-get -yqq update || apt-get -yqq update apt-get -yqq install openvpn openssl ca-certificates $firewall >/dev/null ) || exit 1 elif [[ "$os" = "centos" ]]; then if grep -qs "Amazon Linux release 2" /etc/system-release; then ( set -x amazon-linux-extras install epel -y >/dev/null ) || exit 1 else ( set -x yum -y -q install epel-release >/dev/null ) || exit 1 fi ( set -x yum -y -q install openvpn openssl ca-certificates tar $firewall >/dev/null 2>&1 ) || exit 1 else # Else, OS must be Fedora ( set -x dnf install -y openvpn openssl ca-certificates tar $firewall >/dev/null ) || exit 1 fi # If firewalld was just installed, enable it if [[ "$firewall" == "firewalld" ]]; then ( set -x systemctl enable --now firewalld.service >/dev/null 2>&1 ) fi # Get easy-rsa easy_rsa_url='https://github.com/OpenVPN/easy-rsa/releases/download/v3.1.0/EasyRSA-3.1.0.tgz' mkdir -p /etc/openvpn/server/easy-rsa/ { wget -t 3 -T 30 -qO- "$easy_rsa_url" 2>/dev/null || curl -m 30 -sL "$easy_rsa_url" ; } | tar xz -C /etc/openvpn/server/easy-rsa/ --strip-components 1 if [ ! -f /etc/openvpn/server/easy-rsa/easyrsa ]; then echo "Error: Failed to download EasyRSA from $easy_rsa_url." exit 1 fi chown -R root:root /etc/openvpn/server/easy-rsa/ cd /etc/openvpn/server/easy-rsa/ || exit 1 ( set -x # Create the PKI, set up the CA and the server and client certificates ./easyrsa init-pki >/dev/null ./easyrsa --batch build-ca nopass >/dev/null 2>&1 EASYRSA_CERT_EXPIRE=3650 ./easyrsa build-server-full server nopass >/dev/null 2>&1 EASYRSA_CERT_EXPIRE=3650 ./easyrsa build-client-full "$client" nopass >/dev/null 2>&1 EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl >/dev/null 2>&1 ) # Move the stuff we need cp pki/ca.crt pki/private/ca.key pki/issued/server.crt pki/private/server.key pki/crl.pem /etc/openvpn/server # CRL is read with each client connection, while OpenVPN is dropped to nobody chown nobody:"$group_name" /etc/openvpn/server/crl.pem # Without +x in the directory, OpenVPN can't run a stat() on the CRL file chmod o+x /etc/openvpn/server/ ( set -x # Generate key for tls-crypt openvpn --genkey --secret /etc/openvpn/server/tc.key >/dev/null ) # Create the DH parameters file using the predefined ffdhe2048 group echo '-----BEGIN DH PARAMETERS----- MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== -----END DH PARAMETERS-----' > /etc/openvpn/server/dh.pem # Generate server.conf echo "local $ip port $port proto $protocol dev tun ca ca.crt cert server.crt key server.key dh dh.pem auth SHA512 tls-crypt tc.key topology subnet server 10.8.0.0 255.255.255.0" > /etc/openvpn/server/server.conf # IPv6 if [[ -z "$ip6" ]]; then echo 'push "redirect-gateway def1 bypass-dhcp"' >> /etc/openvpn/server/server.conf else echo 'server-ipv6 fddd:1194:1194:1194::/64' >> /etc/openvpn/server/server.conf echo 'push "redirect-gateway def1 ipv6 bypass-dhcp"' >> /etc/openvpn/server/server.conf fi echo 'ifconfig-pool-persist ipp.txt' >> /etc/openvpn/server/server.conf # DNS case "$dns" in 1) # Locate the proper resolv.conf # Needed for systems running systemd-resolved if grep '^nameserver' "/etc/resolv.conf" | grep -qv '127.0.0.53' ; then resolv_conf="/etc/resolv.conf" else resolv_conf="/run/systemd/resolve/resolv.conf" fi # Obtain the resolvers from resolv.conf and use them for OpenVPN grep -v '^#\|^;' "$resolv_conf" | grep '^nameserver' | grep -v '127.0.0.53' | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}' | while read line; do echo "push \"dhcp-option DNS $line\"" >> /etc/openvpn/server/server.conf done ;; 2|"") echo 'push "dhcp-option DNS 8.8.8.8"' >> /etc/openvpn/server/server.conf echo 'push "dhcp-option DNS 8.8.4.4"' >> /etc/openvpn/server/server.conf ;; 3) echo 'push "dhcp-option DNS 1.1.1.1"' >> /etc/openvpn/server/server.conf echo 'push "dhcp-option DNS 1.0.0.1"' >> /etc/openvpn/server/server.conf ;; 4) echo 'push "dhcp-option DNS 208.67.222.222"' >> /etc/openvpn/server/server.conf echo 'push "dhcp-option DNS 208.67.220.220"' >> /etc/openvpn/server/server.conf ;; 5) echo 'push "dhcp-option DNS 9.9.9.9"' >> /etc/openvpn/server/server.conf echo 'push "dhcp-option DNS 149.112.112.112"' >> /etc/openvpn/server/server.conf ;; 6) echo 'push "dhcp-option DNS 94.140.14.14"' >> /etc/openvpn/server/server.conf echo 'push "dhcp-option DNS 94.140.15.15"' >> /etc/openvpn/server/server.conf ;; 7) echo "push \"dhcp-option DNS $dns1\"" >> /etc/openvpn/server/server.conf if [ -n "$dns2" ]; then echo "push \"dhcp-option DNS $dns2\"" >> /etc/openvpn/server/server.conf fi ;; esac echo "keepalive 10 120 cipher AES-256-CBC user nobody group $group_name persist-key persist-tun verb 3 crl-verify crl.pem" >> /etc/openvpn/server/server.conf if [[ "$protocol" = "udp" ]]; then echo "explicit-exit-notify" >> /etc/openvpn/server/server.conf fi update_sysctl if systemctl is-active --quiet firewalld.service; then # Using both permanent and not permanent rules to avoid a firewalld # reload. # We don't use --add-service=openvpn because that would only work with # the default port and protocol. firewall-cmd -q --add-port="$port"/"$protocol" firewall-cmd -q --zone=trusted --add-source=10.8.0.0/24 firewall-cmd -q --permanent --add-port="$port"/"$protocol" firewall-cmd -q --permanent --zone=trusted --add-source=10.8.0.0/24 # Set NAT for the VPN subnet firewall-cmd -q --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to "$ip" firewall-cmd -q --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to "$ip" if [[ -n "$ip6" ]]; then firewall-cmd -q --zone=trusted --add-source=fddd:1194:1194:1194::/64 firewall-cmd -q --permanent --zone=trusted --add-source=fddd:1194:1194:1194::/64 firewall-cmd -q --direct --add-rule ipv6 nat POSTROUTING 0 -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j SNAT --to "$ip6" firewall-cmd -q --permanent --direct --add-rule ipv6 nat POSTROUTING 0 -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j SNAT --to "$ip6" fi else # Create a service to set up persistent iptables rules iptables_path=$(command -v iptables) ip6tables_path=$(command -v ip6tables) # nf_tables is not available as standard in OVZ kernels. So use iptables-legacy # if we are in OVZ, with a nf_tables backend and iptables-legacy is available. if [[ $(systemd-detect-virt) == "openvz" ]] && readlink -f "$(command -v iptables)" | grep -q "nft" && hash iptables-legacy 2>/dev/null; then iptables_path=$(command -v iptables-legacy) ip6tables_path=$(command -v ip6tables-legacy) fi echo "[Unit] Before=network.target [Service] Type=oneshot ExecStart=$iptables_path -t nat -A POSTROUTING -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to $ip ExecStart=$iptables_path -I INPUT -p $protocol --dport $port -j ACCEPT ExecStart=$iptables_path -I FORWARD -s 10.8.0.0/24 -j ACCEPT ExecStart=$iptables_path -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT ExecStop=$iptables_path -t nat -D POSTROUTING -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to $ip ExecStop=$iptables_path -D INPUT -p $protocol --dport $port -j ACCEPT ExecStop=$iptables_path -D FORWARD -s 10.8.0.0/24 -j ACCEPT ExecStop=$iptables_path -D FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT" > /etc/systemd/system/openvpn-iptables.service if [[ -n "$ip6" ]]; then echo "ExecStart=$ip6tables_path -t nat -A POSTROUTING -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j SNAT --to $ip6 ExecStart=$ip6tables_path -I FORWARD -s fddd:1194:1194:1194::/64 -j ACCEPT ExecStart=$ip6tables_path -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT ExecStop=$ip6tables_path -t nat -D POSTROUTING -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j SNAT --to $ip6 ExecStop=$ip6tables_path -D FORWARD -s fddd:1194:1194:1194::/64 -j ACCEPT ExecStop=$ip6tables_path -D FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT" >> /etc/systemd/system/openvpn-iptables.service fi echo "RemainAfterExit=yes [Install] WantedBy=multi-user.target" >> /etc/systemd/system/openvpn-iptables.service ( set -x systemctl enable --now openvpn-iptables.service >/dev/null 2>&1 ) fi # If SELinux is enabled and a custom port was selected, we need this if sestatus 2>/dev/null | grep "Current mode" | grep -q "enforcing" && [[ "$port" != 1194 ]]; then # Install semanage if not already present if ! hash semanage 2>/dev/null; then if [[ "$os_version" -eq 7 ]]; then # Centos 7 ( set -x yum -y -q install policycoreutils-python >/dev/null ) || exit 1 else # CentOS 8 or Fedora ( set -x dnf install -y policycoreutils-python-utils >/dev/null ) || exit 1 fi fi semanage port -a -t openvpn_port_t -p "$protocol" "$port" fi # If the server is behind NAT, use the correct IP address [[ -n "$public_ip" ]] && ip="$public_ip" # client-common.txt is created so we have a template to add further users later echo "client dev tun proto $protocol remote $ip $port resolv-retry infinite nobind persist-key persist-tun remote-cert-tls server auth SHA512 cipher AES-256-CBC ignore-unknown-option block-outside-dns block-outside-dns verb 3" > /etc/openvpn/server/client-common.txt # Enable and start the OpenVPN service ( set -x systemctl enable --now openvpn-server@server.service >/dev/null 2>&1 ) # Generates the custom client.ovpn new_client echo echo "Finished!" echo echo "The client configuration is available in: $export_dir$client.ovpn" echo "New clients can be added by running this script again." else echo echo "OpenVPN is already installed." echo echo "Select an option:" echo " 1) Add a new client" echo " 2) Export config for an existing client" echo " 3) List existing clients" echo " 4) Revoke an existing client" echo " 5) Remove OpenVPN" echo " 6) Exit" read -rp "Option: " option until [[ "$option" =~ ^[1-6]$ ]]; do echo "$option: invalid selection." read -rp "Option: " option done case "$option" in 1) echo echo "Provide a name for the client:" read -rp "Name: " unsanitized_client [ -z "$unsanitized_client" ] && abort_and_exit client=$(sed 's/[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-]/_/g' <<< "$unsanitized_client") while [[ -z "$client" || -e /etc/openvpn/server/easy-rsa/pki/issued/"$client".crt ]]; do echo "$client: invalid name." read -rp "Name: " unsanitized_client [ -z "$unsanitized_client" ] && abort_and_exit client=$(sed 's/[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-]/_/g' <<< "$unsanitized_client") done cd /etc/openvpn/server/easy-rsa/ || exit 1 ( set -x EASYRSA_CERT_EXPIRE=3650 ./easyrsa build-client-full "$client" nopass >/dev/null 2>&1 ) # Generates the custom client.ovpn new_client echo echo "$client added. Configuration available in: $export_dir$client.ovpn" exit ;; 2) number_of_clients=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^V") if [[ "$number_of_clients" = 0 ]]; then echo echo "There are no existing clients!" exit fi echo echo "Select the client to export:" tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') ' read -rp "Client: " client_number [ -z "$client_number" ] && abort_and_exit until [[ "$client_number" =~ ^[0-9]+$ && "$client_number" -le "$number_of_clients" ]]; do echo "$client_number: invalid selection." read -rp "Client: " client_number [ -z "$client_number" ] && abort_and_exit done client=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$client_number"p) new_client echo echo "$client exported. Configuration available in: $export_dir$client.ovpn" exit ;; 3) echo echo "Checking for existing client(s)..." number_of_clients=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^V") if [[ "$number_of_clients" = 0 ]]; then echo echo "There are no existing clients!" exit fi echo tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') ' if [ "$number_of_clients" = 1 ]; then printf '\n%s\n' "Total: 1 client" elif [ -n "$number_of_clients" ]; then printf '\n%s\n' "Total: $number_of_clients clients" fi ;; 4) number_of_clients=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^V") if [[ "$number_of_clients" = 0 ]]; then echo echo "There are no existing clients!" exit fi echo echo "Select the client to revoke:" tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') ' read -rp "Client: " client_number [ -z "$client_number" ] && abort_and_exit until [[ "$client_number" =~ ^[0-9]+$ && "$client_number" -le "$number_of_clients" ]]; do echo "$client_number: invalid selection." read -rp "Client: " client_number [ -z "$client_number" ] && abort_and_exit done client=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$client_number"p) echo read -rp "Confirm $client revocation? [y/N]: " revoke until [[ "$revoke" =~ ^[yYnN]*$ ]]; do echo "$revoke: invalid selection." read -rp "Confirm $client revocation? [y/N]: " revoke done if [[ "$revoke" =~ ^[yY]$ ]]; then echo echo "Revoking $client..." cd /etc/openvpn/server/easy-rsa/ || exit 1 ( set -x ./easyrsa --batch revoke "$client" >/dev/null 2>&1 EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl >/dev/null 2>&1 ) rm -f /etc/openvpn/server/crl.pem cp /etc/openvpn/server/easy-rsa/pki/crl.pem /etc/openvpn/server/crl.pem # CRL is read with each client connection, when OpenVPN is dropped to nobody chown nobody:"$group_name" /etc/openvpn/server/crl.pem get_export_dir ovpn_file="$export_dir$client.ovpn" if [ -f "$ovpn_file" ]; then echo "Removing $ovpn_file..." rm -f "$ovpn_file" fi echo echo "$client revoked!" else echo echo "$client revocation aborted!" fi exit ;; 5) echo read -rp "Confirm OpenVPN removal? [y/N]: " remove until [[ "$remove" =~ ^[yYnN]*$ ]]; do echo "$remove: invalid selection." read -rp "Confirm OpenVPN removal? [y/N]: " remove done if [[ "$remove" =~ ^[yY]$ ]]; then echo echo "Removing OpenVPN, please wait..." port=$(grep '^port ' /etc/openvpn/server/server.conf | cut -d " " -f 2) protocol=$(grep '^proto ' /etc/openvpn/server/server.conf | cut -d " " -f 2) if systemctl is-active --quiet firewalld.service; then ip=$(firewall-cmd --direct --get-rules ipv4 nat POSTROUTING | grep '\-s 10.8.0.0/24 '"'"'!'"'"' -d 10.8.0.0/24' | grep -oE '[^ ]+$') # Using both permanent and not permanent rules to avoid a firewalld reload. firewall-cmd -q --remove-port="$port"/"$protocol" firewall-cmd -q --zone=trusted --remove-source=10.8.0.0/24 firewall-cmd -q --permanent --remove-port="$port"/"$protocol" firewall-cmd -q --permanent --zone=trusted --remove-source=10.8.0.0/24 firewall-cmd -q --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to "$ip" firewall-cmd -q --permanent --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to "$ip" if grep -qs "server-ipv6" /etc/openvpn/server/server.conf; then ip6=$(firewall-cmd --direct --get-rules ipv6 nat POSTROUTING | grep '\-s fddd:1194:1194:1194::/64 '"'"'!'"'"' -d fddd:1194:1194:1194::/64' | grep -oE '[^ ]+$') firewall-cmd -q --zone=trusted --remove-source=fddd:1194:1194:1194::/64 firewall-cmd -q --permanent --zone=trusted --remove-source=fddd:1194:1194:1194::/64 firewall-cmd -q --direct --remove-rule ipv6 nat POSTROUTING 0 -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j SNAT --to "$ip6" firewall-cmd -q --permanent --direct --remove-rule ipv6 nat POSTROUTING 0 -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j SNAT --to "$ip6" fi else systemctl disable --now openvpn-iptables.service rm -f /etc/systemd/system/openvpn-iptables.service fi if sestatus 2>/dev/null | grep "Current mode" | grep -q "enforcing" && [[ "$port" != 1194 ]]; then semanage port -d -t openvpn_port_t -p "$protocol" "$port" fi systemctl disable --now openvpn-server@server.service rm -f /etc/systemd/system/openvpn-server@server.service.d/disable-limitnproc.conf rm -f /etc/sysctl.d/99-openvpn-forward.conf /etc/sysctl.d/99-openvpn-optimize.conf if [ ! -f /usr/bin/wg-quick ] && [ ! -f /usr/sbin/ipsec ] \ && [ ! -f /usr/local/sbin/ipsec ]; then echo 0 > /proc/sys/net/ipv4/ip_forward echo 0 > /proc/sys/net/ipv6/conf/all/forwarding fi if [[ "$os" = "debian" || "$os" = "ubuntu" ]]; then ( set -x rm -rf /etc/openvpn/server apt-get remove --purge -y openvpn >/dev/null ) else # Else, OS must be CentOS or Fedora ( set -x yum -y -q remove openvpn >/dev/null rm -rf /etc/openvpn/server ) fi echo echo "OpenVPN removed!" else echo echo "OpenVPN removal aborted!" fi exit ;; 6) exit ;; esac fi } ## Defer setup until we have the complete script ovpnsetup "$@" exit 0