五分钟搭建你的专属ocserv服务

背景

在天朝,由于互联网审查制度(通常被称为“防火长城”或“GFW”)的存在,一些国际网站和服务(如Google、Facebook、Twitter等)被屏蔽,因此,很多人需要使用VPN(虚拟专用网络)来访问这些被屏蔽的资源。VPN通过创建一个加密的连接,可以使用户的网络流量从审查制度中绕过,从而访问这些资源。作为一名程序员,经常需要翻越全世界的资料,所以,突破GFW是必备技能,但是一定要合法合规使用。

选择 ocserv 原因

以下是市面上常见的几种VPN协议的简单对比:

  • PPTP(点对点隧道协议):这是最早的VPN协议之一,大部分操作系统都内置了对它的支持。但它的安全性较差,很容易被封锁,因此现在已经不推荐使用。
  • L2TP/IPSec(第二层隧道协议/安全IP):L2TP/IPSec相较于PPTP来说更为安全,但其流量特征明显,容易被防火长城检测并封锁。
  • OpenVPN:OpenVPN是目前最常用、最安全的VPN协议之一。它使用SSL/TLS进行密钥交换,可以提供非常高的安全性。但是,OpenVPN的流量特征也比较明显,因此有可能被防火长城封锁。
  • SSTP(安全套接字隧道协议):SSTP也是一个基于SSL的协议,其安全性较高。然而,SSTP主要被Windows系统支持,对于其他系统的支持性较差。
  • IKEv2(Internet Key Exchange version 2):IKEv2是一个非常安全且稳定的VPN协议,尤其适合移动设备。但是,像L2TP/IPSec一样,它的流量特征也较为明显,可能会被防火长城封锁。
  • ocserv(OpenConnect SSL VPN):ocserv使用的是Cisco的AnyConnect SSL VPN协议,这是一种在全球范围内广泛使用的协议,包括许多在中国大陆运营的外贸企业。它的流量特征和普通的HTTPS流量非常相似,这使得防火长城很难区分和封锁使用ocserv的VPN流量。所以,相比其他协议,ocserv的VPN服务更不容易被封锁。

用过很多种VPN也自己搭建过好几种,最稳定的就属于 ocserv,速度也是最快的,不好的地方就是不能使用 PAC,因为它是 VPN,不是代理软件。

一键安装

本文更多是针对自己成果的存档,相关概念请自行谷歌学习。
我写了个一键部署脚本,在CentOS7以及CentOS8上测试通过,理论上也支持Ubuntu16及以上的系统。

sudo -i yum install wget -y && wget https://raw.githubusercontent.com/wangwanjie/ocserv-install/master/ocserv-install.sh && chmod +x ocserv-install.sh && ./ocserv-install.sh

首次执行脚本会提示安装或者退出,跟随指引一路走下去即可。
安装后再次运行脚本将出现如下菜单:

-> # ./ocserv-install.sh 
检查是否安装了 ocserv ...
请选择要执行的功能:
1) 升级 ocserv           5) 配置域名             9) 关闭 ocserv
2) 卸载 ocserv           6) 查看ocserv登录日志  10) 查看 ocserv 状态
3) 添加 ocserv 用户      7) 查看系统日志        11) 退出
4) 移除 ocserv 用户      8) 启动或重启 ocserv
#? 

安装后事宜

安装后请执行命令“配置域名”,提前在服务商做好DNS解析为证书登录做准备。注意,配置域名这里只对阿里做了处理,其他服务商请自行准备ssl证书并填入ocserv配置文件,执行脚本重启 ocserv 即可。

脚本主要部分展示

主要部分

#!/bin/bash

# 判断系统版本,根据不同系统选择不同的安装命令
if cat /etc/os-release | grep -q "centos"; then
    PKG_MANAGER="yum"
elif cat /etc/os-release | grep -q "ubuntu\|debian"; then
    PKG_MANAGER="apt-get"
else
    echo "当前系统不受支持!"
    exit 1
fi

# Detect Debian users running the script with "sh" instead of bash
if readlink /proc/$$/exe | grep -q "dash"; then
	echo 'This installer needs to be run with "bash", not "sh".'
	exit
fi

# Discard stdin. Needed when running from an one-liner which includes a newline
read -N 999999 -t 0.001

# Detect OpenVZ 6
if [[ $(uname -r | cut -d "." -f 1) -eq 2 ]]; then
	echo "The system is running an old kernel, which is incompatible with this installer."
	exit
fi

# Detect OS
# $os_version variables aren't always in use, but are kept here for convenience
if grep -qs "ubuntu" /etc/os-release; then
	os="ubuntu"
	os_version=$(grep 'VERSION_ID' /etc/os-release | cut -d '"' -f 2 | tr -d '.')
	group_name="nogroup"
elif [[ -e /etc/debian_version ]]; then
	os="debian"
	os_version=$(grep -oE '[0-9]+' /etc/debian_version | head -1)
	group_name="nogroup"
elif [[ -e /etc/almalinux-release || -e /etc/rocky-release || -e /etc/centos-release ]]; then
	os="centos"
	os_version=$(grep -shoE '[0-9]+' /etc/almalinux-release /etc/rocky-release /etc/centos-release | head -1)
	group_name="nobody"
elif [[ -e /etc/fedora-release ]]; then
	os="fedora"
	os_version=$(grep -oE '[0-9]+' /etc/fedora-release | head -1)
	group_name="nobody"
else
	echo "This installer seems to be running on an unsupported distribution.
Supported distros are Ubuntu, Debian, AlmaLinux, Rocky Linux, CentOS and Fedora."
	exit
fi

if [[ "$os" == "ubuntu" && "$os_version" -lt 1804 ]]; then
	echo "Ubuntu 18.04 or higher is required to use this installer.
This version of Ubuntu is too old and unsupported."
	exit
fi

if [[ "$os" == "debian" && "$os_version" -lt 9 ]]; then
	echo "Debian 9 or higher is required to use this installer.
This version of Debian is too old and unsupported."
	exit
fi

if [[ "$os" == "centos" && "$os_version" -lt 7 ]]; then
	echo "CentOS 7 or higher is required to use this installer.
This version of CentOS is too old and unsupported."
	exit
fi

# Detect environments where $PATH does not include the sbin directories
if ! grep -q sbin <<< "$PATH"; then
	echo '$PATH does not include sbin. Try using "su -" instead of "su".'
	exit
fi

if [[ "$EUID" -ne 0 ]]; then
	echo "This installer needs to be run with superuser privileges."
	exit
fi

if [[ ! -e /dev/net/tun ]] || ! ( exec 7<>/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
fi

OCSERV=/etc/ocserv
PORT=443
ipv4_network="192.169.103.0"

# 升级ocserv
function upgradeOcserv() {
    echo "升级 ocserv ..."

    # 根据系统使用合适的更新命令
    if [[ $PKG_MANAGER == "yum" ]]; then
        $PKG_MANAGER -y upgrade ocserv
    else
        $PKG_MANAGER -y upgrade
        $PKG_MANAGER -y install ocserv
    fi

    echo "ocserv 升级完成!"
}

# 卸载ocserv
function uninstallOcserv() {
     read -p "此操作将会卸载 ocserv 及其所有相关文件和配置,确认执行吗? [y/n]: " confirm
    if [[ "$confirm" = [yY] ]]; then
        echo "卸载 ocserv ..."
        $PKG_MANAGER -y remove ocserv
        rm -rf $OCSERV/
        rm -rf /root/anyconnect
        rm -rf /var/www/html/user
        rm -rf /var/lib/ocserv
        echo "ocserv 卸载完成!"
    else
        echo "已取消操作。"
    fi
}

# 添加用户
function addUser() {
    sudo /root/anyconnect/user_add.sh
}

# 移除用户
function removeUser() {
    sudo /root/anyconnect/user_del.sh
}

# 启动或重启 ocserv
function startOrRestartOcserv() {
    if pgrep "ocserv" > /dev/null ; then
        echo "正在重启 ocserv ..."
        systemctl restart ocserv
    else 
        echo "正在启动 ocserv ..."
        systemctl start ocserv
    fi
    if pgrep "httpd" > /dev/null ; then
        echo "正在重启 httpd ..."
        sudo systemctl restart httpd
    else 
        echo "正在启动 httpd ..."
        sudo systemctl start httpd
    fi
}

# 关闭 ocserv
function stopOcserv() {
    echo "正在关闭 ocserv ..."
    systemctl stop ocserv
}

# 查看ocserv状态
function statusOcserv() {
    systemctl status ocserv
}

# 配置 ipv4防火墙
function configIpv4Firewall() {
    echo "配置 ipv4防火墙 ..."
    echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/60-custom.conf
    read -p "是否开启bbr? [y/n]: " confirm
    if [[ "$confirm" = [yY] ]]; then
        if [[ "$os" == "centos" ]]; then
                if [[ "$os_version" -eq "8" ]]; then
                    echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.d/60-custom.conf
                    echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.d/60-custom.conf
                elif [[ "$os_version" -eq "7" ]]; then
                    rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
                    rpm -Uvh https://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm
                    yum --enablerepo=elrepo-kernel install kernel-ml -y
                    # sudo egrep ^menuentry /etc/grub2.cfg | cut -f 2 -d \'
                    # sudo grub2-set-default 0
                    # grub2-mkconfig -o /boot/grub2/grub.cfg
                fi
        elif [[ "$os" == "ubuntu" ]]; then
            echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.d/60-custom.conf
            echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.d/60-custom.conf
        else
            echo "Unsupported OS type."
            exit 1
        fi
    else
        echo "不安装bbr"
    fi
    
    sudo sysctl -p /etc/sysctl.d/60-custom.conf

    # Check if firewall-cmd is installed
    if [ "$(which firewall-cmd)" ]; then
        echo "firewall-cmd is already installed."
    else
        if [[ "$os" == "centos" ]]; then
            if [[ "$os_version" -eq "8" ]]; then
                sudo dnf install firewalld -y
            elif [[ "$os_version" -eq "7" ]]; then
                sudo yum install firewalld -y
            fi
        elif [[ "$os" == "ubuntu" ]]; then
            sudo apt-get update
            sudo apt-get install firewalld -y
        else
            echo "Unsupported OS type."
            exit 1
        fi
    fi
     # Start firewall-cmd and enable at boot
    sudo systemctl start firewalld
    sudo systemctl enable firewalld
    sudo firewall-cmd --permanent --add-port=$PORT/tcp
    sudo firewall-cmd --permanent --add-port=$PORT/udp
    sudo firewall-cmd --permanent --add-port=80/tcp
    sudo firewall-cmd --permanent --add-service=https
    sudo firewall-cmd --permanent --add-service=ssh

    sudo firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='${ipv4_network}/24' masquerade"
    sudo systemctl reload firewalld

    echo "配置 ipv4防火墙结束,已重启防火墙"
}

function prepare() {

if [[ "$os" == "centos" ]]; then
    if [[ "$os_version" -eq "8" ]]; then
        cd /etc/yum.repos.d/
        sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
        sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
        wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo
        yum clean all
        yum makecache

        sudo yum install wget -y
        sudo yum install dnf -y
        sudo yum install expect -y

        sudo dnf install httpd -y
        sudo systemctl enable httpd

        sudo dnf install epel-release -y
        sudo yum install -y gnutls-utils
        sudo dnf install ocserv -y
    elif [[ "$os_version" -eq "7" ]]; then
        sudo yum install wget -y
        sudo yum install expect -y

        sudo yum install httpd -y
        sudo systemctl enable httpd

        sudo yum install epel-release -y
        sudo yum install -y gnutls-utils
        sudo yum install ocserv -y
    fi
elif [[ "$os" == "ubuntu" ]]; then
    sudo apt update
    sudo apt install ocserv
fi
    get_public_ip=$(grep -m 1 -oE '^[0-9]{1,3}(\.[0-9]{1,3}){3}$' <<< "$(wget -T 10 -t 1 -4qO- "http://ip1.dynupdate.no-ip.com/" || curl -m 10 -4Ls "http://ip1.dynupdate.no-ip.com/")")
    read -p "Public IPv4 address / hostname [$get_public_ip]: " public_ip
    # If the checkip service is unavailable and user didn't provide input, ask again
    until [[ -n "$get_public_ip" || -n "$public_ip" ]]; do
        echo "Invalid input."
        read -p "Public IPv4 address / hostname: " public_ip
    done
    [[ -z "$public_ip" ]] && public_ip="$get_public_ip"

    echo "公网 IP:$public_ip"

    cd $OCSERV
    mkdir -p pki user config-per-group config-per-user defaults tmpl pem
    mkdir -p /root/anyconnect
   
    remote_repo=https://raw.githubusercontent.com/wangwanjie/ocserv-install
    remote_repo_branch=master

    rm -rf ocserv.conf connect-script config-per-group/* tmpl/* pem/*

    wget --no-check-certificate $remote_repo/$remote_repo_branch/ocserv.conf
    wget --no-check-certificate $remote_repo/$remote_repo_branch/connect-script
    wget --no-check-certificate $remote_repo/$remote_repo_branch/config-per-group/main -O config-per-group/main
    wget --no-check-certificate $remote_repo/$remote_repo_branch/config-per-group/others -O config-per-group/others
    chmod +x connect-script

    cd /root/anyconnect
    wget --no-check-certificate $remote_repo/$remote_repo_branch/gen-client-cert.sh
    wget --no-check-certificate $remote_repo/$remote_repo_branch/user_add.sh
    wget --no-check-certificate $remote_repo/$remote_repo_branch/user_del.sh
    chmod +x gen-client-cert.sh
    chmod +x user_add.sh
    chmod +x user_del.sh

read -p "请输入证书签发组织名称 [vanjay.cn]: " sign_org
[[ -z "$sign_org" ]] && sign_org="vanjay.cn"

read -p "请输入证书有效期(天) [3650]: " cert_valid_days
[[ -z "$cert_valid_days" ]] && cert_valid_days="3650"

    cd $OCSERV/tmpl
cat >ca.tmpl <<EOF
cn = "VanJay AnyConnect CA"
organization = "$sign_org"
serial = 1
expiration_days = $cert_valid_days
ca
signing_key
cert_signing_key
crl_signing_key
EOF

cat >server.tmpl <<EOF
cn = "VanJay AnyConnect CA"
organization = "$sign_org"
serial = 2
expiration_days = $cert_valid_days
encryption_key
signing_key
tls_www_server
EOF

cat << _EOF_ >crl.tmpl
crl_next_update = 365
crl_number = 1
_EOF_
}

function configDomain() {
    read -p "请输入 VPN 域名!(默认为 tz.vanjay.cn): " domain_name
    if [ -z "$domain_name" ]; then
        domain_name="tz.vanjay.cn"
    fi

    read -p "请输入您的 Email!(默认为 396736694@qq.com): " mail_address
    if [ -z "$mail_address" ]; then
        mail_address="396736694@qq.com"
    fi

    while true; do
        read -p "请输入ali_key: " ali_key
        if [[ -n "$ali_key" ]]; then
            break
        else
            echo "无效的 ali_key"
        fi
    done

    while true; do
        read -p "请输入ali_secret: " ali_secret
        if [[ -n "$ali_secret" ]]; then
            break
        else
            echo "无效的 ali_secret"
        fi
    done

    export Ali_Key=$ali_key
    export Ali_Secret=$ali_secret

    yum install socat -y

    curl https://get.acme.sh | sh
    export PATH="$PATH:$HOME/.acme.sh"
    alias acme.sh=~/.acme.sh/acme.sh
    acme.sh  --register-account  -m $mail_address --server zerossl
    acme.sh --issue --dns dns_ali -d $domain_name
    mkdir -p $OCSERV/pki

    cp -Rf ~/.acme.sh/${domain_name}_ecc/ $OCSERV/pki

    cer_path=$OCSERV/pki/${domain_name}_ecc/${domain_name}.cer
    key_path=$OCSERV/pki/${domain_name}_ecc/${domain_name}.key

    # 更新 ocserv.conf 文件
    sed -i "s#\(server-cert = \).*#\1$cer_path#" $OCSERV/ocserv.conf
    sed -i "s#\(server-key = \).*#\1$key_path#" $OCSERV/ocserv.conf
    sed -i "s#\(default-domain = \).*#\1$domain_name#" $OCSERV/ocserv.conf

    startOrRestartOcserv

    echo "已修改 ocserv.conf,已重启 ocserv 服务"
} 

function generate_server_cert() {
    cd $OCSERV/pem
    # 生成 CA 证书
    certtool --generate-privkey --outfile ca-key.pem

    certtool --generate-self-signed --load-privkey ca-key.pem --template $OCSERV/tmpl/ca.tmpl --outfile ca-cert.pem

    # 生成本地服务器证书
    certtool --generate-privkey --outfile server-key.pem

    certtool --generate-certificate --load-privkey server-key.pem \
    --load-ca-certificate ca-cert.pem --load-ca-privkey ca-key.pem \
    --template $OCSERV/tmpl/server.tmpl --outfile server-cert.pem

    # 生成证书注销文件
    touch $OCSERV/pem/revoked.pem

    certtool --generate-crl --load-ca-privkey ca-key.pem \
            --load-ca-certificate ca-cert.pem \
            --template $OCSERV/tmpl/crl.tmpl --outfile crl.pem
}

function useSystemDNS() {
    sed -i -e "/^#*\s*dns\s*=.*$/d" $OCSERV/ocserv.conf

    # 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 "\ndns = $line" >> $OCSERV/ocserv.conf
    done
}

function useOtherDNS() {
    sed -i "s|dns = 1.1.1.1|dns = $1|g" $OCSERV/ocserv.conf
    sed -i "s|dns = 8.8.8.8 # the second|dns = $2|g" $OCSERV/ocserv.conf
}

# 配置 ocserv.conf
function configOcserv() {
    read -p "请输入要监听的端口号(推荐使用80或443或10443)[443]: " PORT
    until [[ -z "$PORT" || "$PORT" =~ ^[0-9]+$ && "$PORT" -le 65535 ]]; do
		echo "$PORT: invalid port."
		read -p "Port [443]: " PORT
	done
	[[ -z "$PORT" ]] && PORT="443"

    echo "请选择DNS:"
    select FUNC in "Current system resolvers" "Google" "1.1.1.1" "Google & 1.1.1.1" "OpenDNS" "Quad9" "AdGuard"; do
        case $FUNC in
            "Current system resolvers" ) useSystemDNS; break;;
            "Google" ) useOtherDNS 8.8.8.8 8.8.4.4; break;;
            "1.1.1.1" ) useOtherDNS 1.1.1.1 1.0.0.1; break;;
            "Google & 1.1.1.1" ) useOtherDNS 1.1.1.1 8.8.8.8; break;;
            "OpenDNS" ) useOtherDNS 208.67.222.222 208.67.220.220; break;;
            "Quad9" ) useOtherDNS 9.9.9.9 149.112.112.112; break;;
            "AdGuard" ) useOtherDNS 94.140.14.14 94.140.15.15; break;;
        esac
    done

    until [[ $valid_ip == true ]]
    do
        read -p "请输入 ipv4_network [192.168.103.0]: " ipv4_network

        [[ -z "$ipv4_network" ]] && ipv4_network="192.168.103.0"

        valid_ip=true
        IFS='.' read -ra ip_array <<< "$ipv4_network"
        if [[ ${#ip_array[@]} -ne 4 ]]; then
            valid_ip=false
        else
            for (( i=0; i<${#ip_array[@]}; i++ ))
            do
                octet=${ip_array[i]}
                if [[ "$i" -eq 0 ]]; then
                    if [[ "$octet" -eq 0 ]]; then
                        echo "$i ----- $octet"
                        valid_ip=false
                        break
                    else
                        # 判断第一个分段是否为1到3位数字,且不能以0开头
                        if [[ ! "$octet" =~ ^[1-9][0-9]{0,2}$ ]]; then
                            valid_ip=false
                            break
                        fi
                    fi
                else
                    # 判断每个分段是否为1到3位数字,可以以0开头
                    if [[ ! "$octet" =~ ^([0-9]|[1-9][0-9]{1,2})$ ]]; then
                        valid_ip=false
                        break
                    elif [[ "$octet" -lt 0 || "$octet" -gt 255 ]]; then
                        valid_ip=false
                        break
                    fi
                fi
            done
        fi

        if [[ $valid_ip == false ]]; then
            echo "输入的IP地址不合法,请重新输入!"
        fi
    done

    sed -i "s|tcp-port = 443|tcp-port = $PORT|g" $OCSERV/ocserv.conf
    sed -i "s|udp-port = 443|udp-port = $PORT|g" $OCSERV/ocserv.conf
    sed -i "s|ipv4-network = 192.168.103.0|ipv4-network = $ipv4_network|g" $OCSERV/ocserv.conf
    if [[ -n "$public_ip" ]]; then
        sed -i "s/47.242.201.43/$public_ip/g" $OCSERV/ocserv.conf
    fi

    echo "ocserv配置修改成功!"
}

# 启用开机自启
function enableAutoStart() {
    echo "是否开启开机自启?(yes或no)"
    select yn in "yes" "no"; do
        case $yn in
            yes ) systemctl enable ocserv; break;;
            no ) break;;
        esac
    done
}

function logOcserv() {
    if [ -f /etc/ocserv/login.log ]; then
        tail -f /etc/ocserv/login.log
    else
        echo "Error: /etc/ocserv/login.log not found!"
    fi 
}

function logSystem() {
     if [ -f /var/log/messages ]; then
        tail -f /var/log/messages
    else
        echo "Error: /var/log/messages not found!"
    fi 
}

# 安装ocserv
echo "检查是否安装了 ocserv ..."

if ! hash ocserv 2>/dev/null; then
    echo "ocserv 未安装!"
    echo "请选择安装 ocserv 或退出:"
    select yn in "安装" "退出"; do
        case $yn in
            安装 ) break;;
            退出 ) exit;;
        esac
    done

    # 根据系统使用合适的安装命令
    if [[ $PKG_MANAGER == "yum" ]]; then
        $PKG_MANAGER -y upgrade
        $PKG_MANAGER -y install epel-release
        $PKG_MANAGER -y install ocserv
    else
        sudo -i 
        $PKG_MANAGER install wget -y 
        $PKG_MANAGER -y update
        $PKG_MANAGER install epel-release wget -y
        $PKG_MANAGER install ocserv httpd -y
    fi

    prepare
    generate_server_cert
    configOcserv
    configIpv4Firewall
    enableAutoStart

    echo "ocserv 安装完成!"
else
    # 主程序
    echo "请选择要执行的功能:"
    select FUNC in "升级 ocserv" "卸载 ocserv" "添加 ocserv 用户" "移除 ocserv 用户" "配置域名" "查看ocserv登录日志" "查看系统日志" "启动或重启 ocserv" "关闭 ocserv" "查看 ocserv 状态" "退出"; do
        case $FUNC in
            "升级 ocserv" ) upgradeOcserv; break;;
            "卸载 ocserv" ) uninstallOcserv; break;;
            "添加 ocserv 用户" ) addUser; break;;
            "移除 ocserv 用户" ) removeUser; break;;
            "配置域名" ) configDomain; break;;
            "查看ocserv登录日志" ) logOcserv; break;;
            "查看系统日志" ) logSystem; break;;
            "启动或重启 ocserv" ) startOrRestartOcserv; break;;
            "关闭 ocserv" ) stopOcserv; break;;
            "查看 ocserv 状态" ) statusOcserv; break;;
            "退出" ) exit;;
        esac
    done
fi

echo "ocserv 脚本运行结束!"
echo "再次运行此脚本可选择功能!"

生成证书

#!/bin/bash

# 检查是否使用 root 账户执行脚本
if [[ $EUID -ne 0 ]]; then
   echo "This script must be run as root."
   exit 1
fi

# 从命令行参数获取用户名和路径
USER=$1
OCSERV=$2
USER_DIR=$OCSERV/user/$USER

# 确保目录存在
mkdir -p $USER_DIR && cd $USER_DIR

# 生成私钥
SERIAL=`date +%s`
certtool --generate-privkey --outfile $USER-key.pem

# 生成证书的模板文件
cat << _EOF_ >$USER.tmpl
cn = "$USER"
unit = "users"
serial = "$SERIAL"
expiration_days = 9999
signing_key
tls_www_client
_EOF_

# 用私钥、证书模板以及根证书生成证书文件
cd $USER_DIR
certtool --generate-certificate --load-privkey $USER-key.pem --load-ca-certificate $OCSERV/pem/ca-cert.pem --load-ca-privkey $OCSERV/pem/ca-key.pem --template $USER.tmpl --outfile $USER-cert.pem

# 将证书文件导出为 p12 格式
openssl pkcs12 -export -inkey $USER-key.pem -in $USER-cert.pem -name "$USER VPN Client Cert" -certfile $OCSERV/pem/ca-cert.pem -out $USER.p12

添加用户

#!/bin/bash

# 这个脚本是用来同时添加 VPN 用户和他们的证书的

# 检查是否使用 root 账户执行脚本
if [[ $EUID -ne 0 ]]; then
   echo "This script must be run as root."
   exit 1
fi

function input_user() {
	
	# 从两个数据源获取服务器的公网 IP
	get_public_ip=$(grep -m 1 -oE '^[0-9]{1,3}(\.[0-9]{1,3}){3}$' <<< "$(wget -T 10 -t 1 -4qO- "http://ip1.dynupdate.no-ip.com/" || curl -m 10 -4Ls "http://ip1.dynupdate.no-ip.com/")")
	public_ip="$get_public_ip"

	# 如果第一个数据源没有返回正确的公网 IP,就尝试第二个数据源
	if [[ -z "$public_ip" ]]; then
		public_ip=$(lynx --source www.monip.org | sed -nre 's/^.* (([0-9]{1,3}\.){3}[0-9]{1,3}).*$/\1/p')
	fi

	# 从 ocserv.conf 文件中获取 VPN 端口号
	PORT=$(grep ^\s*tcp-port /etc/ocserv/ocserv.conf | awk '{print $NF}')

	# 获取 VPN 用户名和组别
	read -p "请输入您的 VPN 用户名: " user_name
	if  [[ ! -n "$user_name" ]]; then
		echo "您没有输入用户名,请重新执行程序"
	else
		read -p "请输入您的 VPN 用户组别: " user_group
	fi

	# 获取 VPN 用户密码
	if [[ ! -n "$user_group" ]]; then
		echo "您没有输入用户组别,将使用配置文件中的默认组别"
		user_group="others"
	fi
	
	read -p "请输入您的密码: " user_pass

	if [[ ! -n "$user_pass" ]]; then
		echo "您没有输入密码,请重新执行程序"
	else
		user_add
		cert_add
	fi
}

function user_add() {
	# 根据不同的系统,选择不同的 expect 路径
	if [ -x "$(command -v yum)" ]; then
		EXPECT_CMD="/usr/bin/expect"
	else
		EXPECT_CMD="/usr/bin/env expect"
	fi

	sudo touch /etc/ocserv/ocpasswd
	sudo chmod 600 /etc/ocserv/ocpasswd

	$EXPECT_CMD <<-END
	spawn sudo ocpasswd -c /etc/ocserv/ocpasswd -g $user_group $user_name 
	expect "Enter password:"
	send "$user_pass\r"
	expect "Re-enter password:"
	send "$user_pass\r"
	expect eof
	exit
	END
}

# 为用户添加证书
function cert_add() {
	OCSERV=/etc/ocserv
	user_root_dir=$OCSERV/user
	mkdir -p $user_root_dir/$user_name
	cd $user_root_dir/$user_name

	# 根据不同的系统,选择不同的 expect 路径
	if [ -x "$(command -v yum)" ]; then
		EXPECT_CMD="/usr/bin/expect"
	else
		EXPECT_CMD="/usr/bin/env expect"
	fi

	$EXPECT_CMD <<-END
	spawn sudo /root/anyconnect/gen-client-cert.sh $user_name $OCSERV
	expect "Enter Export Password:"
	send "$user_pass\r"
	expect "Verifying - Enter Export Password:"
	send "$user_pass\r"
	expect eof
	exit
	END

	cd $user_root_dir && mkdir -p /var/www/html/user
    cp -R $user_name /var/www/html/user/$user_name
	echo "$user_name 用户已成功创建,密码为 $user_pass"
	echo "$user_name 的证书已成功创建,请点击以下链接进行下载。"
	echo "http://$public_ip/user/$user_name/$user_name.p12"
	echo "证书本地路径为:$user_root_dir/$user_name"
	echo "导入证书的密码是 $user_pass"
	echo "VPN 地址和端口是 $public_ip:$PORT"
}

# 安装 shell
function shell_install() {
	input_user
}

shell_install

删除用户

#!/bin/bash

# ocserv 删除用户及注销用户的证书脚本文件

# 检查是否使用 root 账户执行脚本
if [[ $EUID -ne 0 ]]; then
   echo "This script must be run as root."
   exit 1
fi

function user_del() {
    OCSERV=/etc/ocserv
    
    # 获取要删除用户的用户名
    read -p "请输入您想要删除的用户名!" user_name
    if  [[ ! -n "$user_name" ]]; then
        echo "您没有输入用户名,请重新执行程序"
    else
        # 使用 ocpasswd 命令删除用户
        /usr/bin/ocpasswd -d $user_name
        echo "$user_name 用户已成功删除"
        
        # 将用户证书添加到撤销列表,并生成 CRL 文件
        cat $OCSERV/user/$user_name/$user_name-cert.pem >> $OCSERV/pem/revoked.pem
        certtool --generate-crl --load-ca-privkey $OCSERV/pem/ca-key.pem  --load-ca-certificate $OCSERV/pem/ca-cert.pem --load-certificate $OCSERV/pem/revoked.pem  --template $OCSERV/tmpl/crl.tmpl --outfile $OCSERV/pem/crl.pem
        echo "$user_name 用户的证书已被注销"
        
        # 重启 ocserv 服务
        systemctl restart ocserv.service
    fi
}

# 调用函数
user_del

脚本

脚本已开源,如有定制需求可以fork自行修改。

ocserv-install

相关常用命令

journalctl -u ocserv -f
systemctl reload firewalld
firewall-cmd --list-all
systemctl enable firewalld
systemctl disable firewalld
systemctl start firewalld
firewall-cmd --permanent --add-service=https
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-port=xxx/tcp
firewall-cmd --permanent --remove-port=xxx/tcp
firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='your_ipv4_mask/24' masquerade"

资料

ocserv开源代码

OpenConnect

OpenConnect GUI

官方配置说明

客户端下载

可以使用 OpenConnect GUI 也可以使用 openconnect 命令行连接,使用方法自行搜索。
推荐使用 Cisco Anyconnect 或者 Cisco Secure Client 连接,下载链接