From f4b053d6739b69ddf4ea903be888fe552cd2c4d2 Mon Sep 17 00:00:00 2001 From: hwdsl2 Date: Sun, 16 Jun 2024 22:21:37 -0500 Subject: [PATCH] Improve VPN script - Refactor the script into Bash functions for improved organization and readability. --- openvpn-install.sh | 820 +++++++++++++++++++++++++-------------------- 1 file changed, 448 insertions(+), 372 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 9407eee..45c2bce 100644 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -21,6 +21,26 @@ check_ip() { printf '%s' "$1" | tr -d '\n' | grep -Eq "$IP_REGEX" } +check_root() { + if [ "$(id -u)" != 0 ]; then + exiterr "This installer must be run as root. Try 'sudo bash $0'" + fi +} + +check_shell() { + # Detect Debian users running the script with "sh" instead of bash + if readlink /proc/$$/exe | grep -q "dash"; then + exiterr 'This installer needs to be run with "bash", not "sh".' + fi +} + +check_kernel() { + # Detect OpenVZ 6 + if [[ $(uname -r | cut -d "." -f 1) -eq 2 ]]; then + exiterr "The system is running an old kernel, which is incompatible with this installer." + fi +} + check_os() { if grep -qs "ubuntu" /etc/os-release; then os="ubuntu" @@ -71,6 +91,30 @@ This version of CentOS is too old and unsupported." fi } +check_tun() { + if [[ ! -e /dev/net/tun ]] || ! ( exec 7<>/dev/net/tun ) 2>/dev/null; then + exiterr "The system does not have the TUN device available. +TUN needs to be enabled before running this installer." + fi +} + +parse_args() { + while [ "$#" -gt 0 ]; do + case $1 in + --auto) + auto=1 + shift + ;; + -h|--help) + show_usage + ;; + *) + show_usage "Unknown parameter: $1" + ;; + esac + done +} + check_nftables() { if [ "$os" = "centos" ]; then if grep -qs "hwdsl2 VPN script" /etc/sysconfig/nftables.conf \ @@ -403,6 +447,305 @@ confirm_setup() { fi } +disable_limitnproc() { + # 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 +} + +install_pkgs() { + if [[ "$os" = "debian" || "$os" = "ubuntu" ]]; then + export DEBIAN_FRONTEND=noninteractive + ( + set -x + apt-get -yqq update || apt-get -yqq update + apt-get -yqq --no-install-recommends install openvpn >/dev/null + ) || exiterr2 + ( + set -x + apt-get -yqq install openssl ca-certificates $firewall >/dev/null + ) || exiterr2 + 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 + ) || exiterr3 + fi + ( + set -x + yum -y -q install openvpn openssl ca-certificates tar $firewall >/dev/null 2>&1 + ) || exiterr3 + elif [[ "$os" = "fedora" ]]; then + ( + set -x + dnf install -y openvpn openssl ca-certificates tar $firewall >/dev/null + ) || exiterr "'dnf install' failed." + else + # Else, OS must be openSUSE + ( + set -x + zypper install -y openvpn openssl ca-certificates tar $firewall >/dev/null + ) || exiterr4 + fi + # If firewalld was just installed, enable it + if [[ "$firewall" == "firewalld" ]]; then + ( + set -x + systemctl enable --now firewalld.service >/dev/null 2>&1 + ) + fi +} + +remove_pkgs() { + if [[ "$os" = "debian" || "$os" = "ubuntu" ]]; then + ( + set -x + rm -rf /etc/openvpn/server + apt-get remove --purge -y openvpn >/dev/null + ) + elif [[ "$os" = "openSUSE" ]]; then + ( + set -x + zypper remove -y openvpn >/dev/null + rm -rf /etc/openvpn/server + ) + rm -f /etc/openvpn/ipp.txt + else + # Else, OS must be CentOS or Fedora + ( + set -x + yum -y -q remove openvpn >/dev/null + rm -rf /etc/openvpn/server + ) + fi +} + +create_firewall_rules() { + 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 MASQUERADE + firewall-cmd -q --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j MASQUERADE + 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 MASQUERADE + firewall-cmd -q --permanent --direct --add-rule ipv6 nat POSTROUTING 0 -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j MASQUERADE + 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 MASQUERADE +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 MASQUERADE +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 MASQUERADE +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 MASQUERADE +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 +} + +remove_firewall_rules() { + port=$(grep '^port ' "$OVPN_CONF" | cut -d " " -f 2) + protocol=$(grep '^proto ' "$OVPN_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 MASQUERADE + firewall-cmd -q --permanent --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j MASQUERADE + if grep -qs "server-ipv6" "$OVPN_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 MASQUERADE + firewall-cmd -q --permanent --direct --remove-rule ipv6 nat POSTROUTING 0 -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j MASQUERADE + 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 +} + +install_easyrsa() { + # Get easy-rsa + easy_rsa_url='https://github.com/OpenVPN/easy-rsa/releases/download/v3.2.0/EasyRSA-3.2.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 + exiterr "Failed to download EasyRSA from $easy_rsa_url." + fi + chown -R root:root /etc/openvpn/server/easy-rsa/ +} + +create_pki_and_certs() { + cd /etc/openvpn/server/easy-rsa/ || exit 1 + ( + set -x + # Create the PKI, set up the CA and the server and client certificates + ./easyrsa --batch init-pki >/dev/null + ./easyrsa --batch build-ca nopass >/dev/null 2>&1 + ./easyrsa --batch --days=3650 build-server-full server nopass >/dev/null 2>&1 + ./easyrsa --batch --days=3650 build-client-full "$client" nopass >/dev/null 2>&1 + ./easyrsa --batch --days=3650 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 +} + +create_dns_config() { + 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\"" >> "$OVPN_CONF" + done + ;; + 2|"") + echo 'push "dhcp-option DNS 8.8.8.8"' >> "$OVPN_CONF" + echo 'push "dhcp-option DNS 8.8.4.4"' >> "$OVPN_CONF" + ;; + 3) + echo 'push "dhcp-option DNS 1.1.1.1"' >> "$OVPN_CONF" + echo 'push "dhcp-option DNS 1.0.0.1"' >> "$OVPN_CONF" + ;; + 4) + echo 'push "dhcp-option DNS 208.67.222.222"' >> "$OVPN_CONF" + echo 'push "dhcp-option DNS 208.67.220.220"' >> "$OVPN_CONF" + ;; + 5) + echo 'push "dhcp-option DNS 9.9.9.9"' >> "$OVPN_CONF" + echo 'push "dhcp-option DNS 149.112.112.112"' >> "$OVPN_CONF" + ;; + 6) + echo 'push "dhcp-option DNS 94.140.14.14"' >> "$OVPN_CONF" + echo 'push "dhcp-option DNS 94.140.15.15"' >> "$OVPN_CONF" + ;; + 7) + echo "push \"dhcp-option DNS $dns1\"" >> "$OVPN_CONF" + if [ -n "$dns2" ]; then + echo "push \"dhcp-option DNS $dns2\"" >> "$OVPN_CONF" + fi + ;; + esac +} + +create_server_config() { + # 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 SHA256 +tls-crypt tc.key +topology subnet +server 10.8.0.0 255.255.255.0" > "$OVPN_CONF" + # IPv6 + if [[ -z "$ip6" ]]; then + echo 'push "block-ipv6"' >> "$OVPN_CONF" + echo 'push "ifconfig-ipv6 fddd:1194:1194:1194::2/64 fddd:1194:1194:1194::1"' >> "$OVPN_CONF" + else + echo 'server-ipv6 fddd:1194:1194:1194::/64' >> "$OVPN_CONF" + fi + echo 'push "redirect-gateway def1 ipv6 bypass-dhcp"' >> "$OVPN_CONF" + echo 'ifconfig-pool-persist ipp.txt' >> "$OVPN_CONF" + create_dns_config + echo 'push "block-outside-dns"' >> "$OVPN_CONF" + echo "keepalive 10 120 +cipher AES-128-GCM +user nobody +group $group_name +persist-key +persist-tun +verb 3 +crl-verify crl.pem" >> "$OVPN_CONF" + if [[ "$protocol" = "udp" ]]; then + echo "explicit-exit-notify" >> "$OVPN_CONF" + fi +} + +show_clients() { + tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') ' +} + get_export_dir() { export_to_home_dir=0 export_dir=~/ @@ -491,6 +834,70 @@ EOF fi } +update_selinux() { + # 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 + ) || exiterr3 + else + # CentOS 8/9 or Fedora + ( + set -x + dnf install -y policycoreutils-python-utils >/dev/null + ) || exiterr "'dnf install' failed." + fi + fi + semanage port -a -t openvpn_port_t -p "$protocol" "$port" + fi +} + +create_client_common() { + # 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 SHA256 +cipher AES-128-GCM +ignore-unknown-option block-outside-dns block-ipv6 +verb 3" > /etc/openvpn/server/client-common.txt +} + +start_openvpn_service() { + # Enable and start the OpenVPN service + if [ "$os" != "openSUSE" ]; then + ( + set -x + systemctl enable --now openvpn-server@server.service >/dev/null 2>&1 + ) + else + ln -s /etc/openvpn/server/* /etc/openvpn >/dev/null 2>&1 + ( + set -x + systemctl enable --now openvpn@server.service >/dev/null 2>&1 + ) + fi +} + +finish_setup() { + 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." +} + show_header() { cat <<'EOF' @@ -507,6 +914,24 @@ Copyright (c) 2013-2023 Nyr EOF } +select_menu_option() { + 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 +} + show_usage() { if [ -n "$1" ]; then echo "Error: $1" >&2 @@ -530,45 +955,19 @@ ovpnsetup() { export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" -if [ "$(id -u)" != 0 ]; then - exiterr "This installer must be run as root. Try 'sudo bash $0'" -fi - -# Detect Debian users running the script with "sh" instead of bash -if readlink /proc/$$/exe | grep -q "dash"; then - exiterr 'This installer needs to be run with "bash", not "sh".' -fi - -# Detect OpenVZ 6 -if [[ $(uname -r | cut -d "." -f 1) -eq 2 ]]; then - exiterr "The system is running an old kernel, which is incompatible with this installer." -fi - +check_root +check_shell +check_kernel check_os check_os_ver +check_tun -if [[ ! -e /dev/net/tun ]] || ! ( exec 7<>/dev/net/tun ) 2>/dev/null; then - exiterr "The system does not have the TUN device available. -TUN needs to be enabled before running this installer." -fi +OVPN_CONF="/etc/openvpn/server/server.conf" auto=0 -if [[ ! -e /etc/openvpn/server/server.conf ]]; then +if [[ ! -e "$OVPN_CONF" ]]; then check_nftables - while [ "$#" -gt 0 ]; do - case $1 in - --auto) - auto=1 - shift - ;; - -h|--help) - show_usage - ;; - *) - show_usage "Unknown parameter: $1" - ;; - esac - done + parse_args "$@" install_wget install_iproute show_start_setup @@ -593,305 +992,26 @@ if [[ ! -e /etc/openvpn/server/server.conf ]]; then confirm_setup 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 --no-install-recommends install openvpn >/dev/null - ) || exiterr2 - ( - set -x - apt-get -yqq install openssl ca-certificates $firewall >/dev/null - ) || exiterr2 - 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 - ) || exiterr3 - fi - ( - set -x - yum -y -q install openvpn openssl ca-certificates tar $firewall >/dev/null 2>&1 - ) || exiterr3 - elif [[ "$os" = "fedora" ]]; then - ( - set -x - dnf install -y openvpn openssl ca-certificates tar $firewall >/dev/null - ) || exiterr "'dnf install' failed." - else - # Else, OS must be openSUSE - ( - set -x - zypper install -y openvpn openssl ca-certificates tar $firewall >/dev/null - ) || exiterr4 - 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.2.0/EasyRSA-3.2.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 - exiterr "Failed to download EasyRSA from $easy_rsa_url." - 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 --batch init-pki >/dev/null - ./easyrsa --batch build-ca nopass >/dev/null 2>&1 - ./easyrsa --batch --days=3650 build-server-full server nopass >/dev/null 2>&1 - ./easyrsa --batch --days=3650 build-client-full "$client" nopass >/dev/null 2>&1 - ./easyrsa --batch --days=3650 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 SHA256 -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 "block-ipv6"' >> /etc/openvpn/server/server.conf - echo 'push "ifconfig-ipv6 fddd:1194:1194:1194::2/64 fddd:1194:1194:1194::1"' >> /etc/openvpn/server/server.conf - else - echo 'server-ipv6 fddd:1194:1194:1194::/64' >> /etc/openvpn/server/server.conf - fi - echo 'push "redirect-gateway def1 ipv6 bypass-dhcp"' >> /etc/openvpn/server/server.conf - 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 'push "block-outside-dns"' >> /etc/openvpn/server/server.conf - echo "keepalive 10 120 -cipher AES-128-GCM -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 + disable_limitnproc + install_pkgs + install_easyrsa + create_pki_and_certs + create_server_config 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 MASQUERADE - firewall-cmd -q --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j MASQUERADE - 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 MASQUERADE - firewall-cmd -q --permanent --direct --add-rule ipv6 nat POSTROUTING 0 -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j MASQUERADE - 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 MASQUERADE -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 MASQUERADE -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 MASQUERADE -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 MASQUERADE -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 + create_firewall_rules if [ "$os" != "openSUSE" ]; then update_rclocal 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 - ) || exiterr3 - else - # CentOS 8/9 or Fedora - ( - set -x - dnf install -y policycoreutils-python-utils >/dev/null - ) || exiterr "'dnf install' failed." - fi - fi - semanage port -a -t openvpn_port_t -p "$protocol" "$port" - fi + update_selinux # 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 SHA256 -cipher AES-128-GCM -ignore-unknown-option block-outside-dns block-ipv6 -verb 3" > /etc/openvpn/server/client-common.txt - # Enable and start the OpenVPN service - if [ "$os" != "openSUSE" ]; then - ( - set -x - systemctl enable --now openvpn-server@server.service >/dev/null 2>&1 - ) - else - ln -s /etc/openvpn/server/* /etc/openvpn >/dev/null 2>&1 - ( - set -x - systemctl enable --now openvpn@server.service >/dev/null 2>&1 - ) - fi - # Generates the custom client.ovpn + create_client_common + start_openvpn_service 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." + finish_setup else show_header - 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 + select_menu_option case "$option" in 1) echo @@ -925,7 +1045,7 @@ else 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 ') ' + show_clients read -rp "Client: " client_num [ -z "$client_num" ] && abort_and_exit until [[ "$client_num" =~ ^[0-9]+$ && "$client_num" -le "$num_of_clients" ]]; do @@ -949,7 +1069,7 @@ else exit fi echo - tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') ' + show_clients if [ "$num_of_clients" = 1 ]; then printf '\n%s\n' "Total: 1 client" elif [ -n "$num_of_clients" ]; then @@ -966,7 +1086,7 @@ else 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 ') ' + show_clients read -rp "Client: " client_num [ -z "$client_num" ] && abort_and_exit until [[ "$client_num" =~ ^[0-9]+$ && "$client_num" -le "$num_of_clients" ]]; do @@ -1018,31 +1138,7 @@ else 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 MASQUERADE - firewall-cmd -q --permanent --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j MASQUERADE - 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 MASQUERADE - firewall-cmd -q --permanent --direct --remove-rule ipv6 nat POSTROUTING 0 -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j MASQUERADE - 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 + remove_firewall_rules if [ "$os" != "openSUSE" ]; then systemctl disable --now openvpn-server@server.service else @@ -1059,27 +1155,7 @@ else if grep -qs "$ipt_cmd" /etc/rc.local; then sed --follow-symlinks -i "/^$ipt_cmd/d" /etc/rc.local fi - if [[ "$os" = "debian" || "$os" = "ubuntu" ]]; then - ( - set -x - rm -rf /etc/openvpn/server - apt-get remove --purge -y openvpn >/dev/null - ) - elif [[ "$os" = "openSUSE" ]]; then - ( - set -x - zypper remove -y openvpn >/dev/null - rm -rf /etc/openvpn/server - ) - rm -f /etc/openvpn/ipp.txt - else - # Else, OS must be CentOS or Fedora - ( - set -x - yum -y -q remove openvpn >/dev/null - rm -rf /etc/openvpn/server - ) - fi + remove_pkgs echo echo "OpenVPN removed!" else