Сегодня будем разворачивать свой VPN на базе OpenConnect сервера (ocserv), работающего поверх HTTPS и который совместим с Cisco Anyconnect. Все это добро мы упакуем в docker контейнер для простоты использования и лёгкой переносимости.
Данный сервер планируется использовать, как основной связующий элемент, с помощью которого будем настраивать сетевое взаимодействие будущих проектов и сервисов. Сама установка сервера выполняется всего в несколько команд. Будет интересно 😉
🖐️Эй!
Подписывайтесь на наш телеграм @r4ven_me📱, чтобы не пропустить новые публикации на сайте😉. А если есть вопросы или желание пообщаться по тематике — заглядывайте в Вороний чат @r4ven_me_chat🧐.
Пожалуйста не пугайтесь «длинности» поста. В статье описано множество подробностей, но сама процедура развертывания очень проста и выполняется в несколько команд (загляните в TLDR). Настройка клиентов и то занимает больше времени.
#############################################################
## при использовании домена для получения SSL сертификатов ##
## расскоментируйте сервис certbot и параметр depends_on ##
## у сервиса openconnect + укажите свои значения вместо ##
## *example* в файле docker-compose.yml ##
#############################################################
### установка и настройка сервера с помощью готового образа ###
# создание директории проекта
mkdir /opt/openconnect && cd /opt/openconnect
# копирование файла docker-compose.yml
curl -sSLO https://raw.githubusercontent.com/r4ven-me/openconnect/main/docker-compose.yml
# запуск OpenConnect сервера
docker compose up -d && docker compose logs -f
# создание пользователя с id "exampleuser" и именем "Example User"
# файл сертификата .p12 будет создан в ./data/secrets
docker exec -it openconnect ocuser exampleuser 'Example User'
# настройка автозапуска с systemd
docker compose down
curl -fLo /etc/systemd/system/openconnect.service https://raw.githubusercontent.com/r4ven-me/openconnect/main/src/server/v1.3-sid/openconnect.service
systemctl daemon-reload
systemctl enable --now openconnect
### пример подключения клиента с помощью утилиты openconnect ###
# без домена
sudo openconnect -c /home/exampleuser/exampleuser.p12 12.345.67.89:43443 <<< $(echo "examplepassword"$'\n'yes$'\n')
# с доменом
sudo openconnect -c /home/exampleuser/exampleuser.p12 example.com:43443 <<< $(echo "examplepassword"$'\n')
❗️ Осторожно
Обращаю ваше внимание, что предоставленная в данной статье информация предназначена исключительно для образовательных целей. Любые действия, основанные на этой информации, осуществляются на ваш собственный риск и ответственность.
Введение
Пару слов о том, в чем смысОл всего происходящего.
В корпоративном мире существует продукт Cisco AnyConnect, который представляет собой проприетарное ПО для организации виртуальных частных сетей (VPN), разработанное компанией Cisco.
Как это часто бывает в IT индустрии, если образуется весомый спрос на продукт, который имеет закрытый исходный код, появляется его «открытая» реализация. Самым популярным примером такого явления является Linux.
Как вы могли догадаться, рассматриваемый нами сегодня OpenConnect — это open source реализация ПО для организации VPN сервиса, совместимого с Cisco AnyConnect.
Вот что говорит зарубежная вики про OpenConnect:
OpenConnect is a free and open-source cross-platform multi-protocol virtual private network (VPN) client software which implement secure point-to-point connections.
Wikipedia
Проект OpenConnect также включает в себя сервер, который называется ocserv (OpenConnect server) и тем самым обеспечивает полноценное клиент-серверное VPN решение.
Основные преимущества такого решения:
- Кроссплатформенность: клиенты под Linux, Windows, MacOS, Android, iOS, HarmonyOS;
- Гибкость настройки, в т.ч. для каждого пользователя;
- Шифрование соединения на основе SSL/TLS;
- Простота настройки и использования;
- Множество способов авторизации + двухфакторная и многое другое.
Предварительная подготовка
Для удобства и простоты использования мы с вами будем разворачивать OpenConnect сервер в docker контейнере, путем сборки образа и его запуска с помощью docker-compose. Обязательным условием является настроенный Linux сервер с установленным и запущенным в нем docker engine. А также наличие root привилегий (для моего примера) или группы docker у пользователя, если будете выполнять установку по своему.
Если какой-то из пунктов отсутствует, возможно вам будут полезны следующие статьи:
- Установка сервера Debian 12
- Начальная настройка Linux сервера на примере Debian
- Установка Docker engine на Linux сервер под управлением Debian
В статье про установку docker’а также есть ссылки на полезные материалы, где доступным языком объясняется, что это такое, для чего нужен и из чего состоит. Если вы ранее не работали с данной технологией контейнерной виртуализации, то я настоятельно рекомендую их к прочтению.
Так, как ocserv работает поверх HTTPS c SSL шифрованием, рекомендованным (но необязательным) условиям является наличие валидного доменного имени. Конкретнее — DNS записи типа A, указывающую на внешний IP адрес вашего сервера. Добавление такой записи производится в настройках DNS вашего провайдера домена. В моём примере используется адрес: vpn.r4ven.me
. При настройке OpenConnect адрес DNS необходим для бесплатного получения официальных SSL сертификатов от проекта Let’s Encrypt.
Если домена у вас нет, то при настройке сервера вместо DNS имени указывайте его IP адрес. При такой настройке на VPN клиентах при подключении будет выплывать уведомление о подключении к неподтвержденному источнику. В этом нет ничего страшного, просто это неудобно, и если честно, раздражает)
Схема проекта
Для визуального подкрепления, ниже представлена схема проекта будущего OpenConnect VPN сервера:
Сохранив картинку в голову, приступаем к установке и настройке.
Развертывание OpenConnect VPN сервера в docker
Действия из статьи выполнялись в среде дистрибутива: | Debian 12 |
Образ docker контейнера с ocserv основан на: | Debian sid |
Версия ocserv внутри контейнера: | 1.3 |
Вводные данные
Для быстрого и удобного поднятия своего VPN сервера я подготовил небольшой проект с использованием docker, docker-compose и bash.
Весь процесс настройки сервера OpenConnect внутрни контейнера был автоматизирован.
Вот краткое описание проекта:
- Сборка образа docker на основе Debian sid;
- Установка в образ сервера ocserv и вспомогательных утилит;
- Копирование в образ bash скрипта
oscerv.sh
; - При первом старте скрипт создает необходимые файлы и папки, в т.ч. основной конфиг
ocserv.conf
, скрипты подключения/отключения и файлы сертификатов; - Всё это сохраняется в docker volume, который монтируется в папочку
./data
; - Файлы сервера можно с легкостью переносить на другие системы/платформы, где работает docker (просто перенесите папку
/opt/openconnect
); - Конфиг файлы сервера можно кастомизировать под свои нужды;
- При старте контейнера скрипт
oscserv.sh
проверяет наличие файлов в директории./data
, если они есть, то просто запускается сервер, если нет, файлы создаются заново, и после запускается сервер; - Все необходимые значения для конфигов и сертификатов берутся из переменных окружения, определенные в файле
.env
(если не указать, будут использованы дефолтные значения);
Так, воды налил, теперь давайте приступать к установке и настройке нашего сервера.
Обновление системы
Первым делом рекомендуется обновить вашу систему до актуального состояния:
sudo -s
apt update && apt upgrade -y
Клонирование репозитория с исходными файлами проекта
Для клонирования git репозитория нам потребуется утилита командной строки, из состава пакета одноимённой системы контроля версий — git. Если эта система у вас не установлена, то выполняем:
apt install git
Теперь клонируем репозиторий с подготовленными файлами из моего GitHub, далее копируем директорию v1.3-sid по пути /opt/openconnect
и переходим в неё:
git clone https://github.com/r4ven-me/openconnect /tmp/openconnect
cp -rv /tmp/openconnect/src/server/v1.3-sid /opt/openconnect && rm -rf /tmp/openconnect
cd /opt/openconnect
Директория
/opt
(optional) предназначена для установки «дополнительного» программного обеспечения, которое не является стандартным для данной ОС.
Файлы проекта имеет следующую структуру:
Где:
.env
- файл с переменными:
1TZ="Europe/Moscow"
2SRV_PORT="43443"
3SRV_CN="example.com"
4SRV_CA="Example CA"
5USER_EMAIL="mail@example.com"
6#OTP_ENABLE="true"
7#OTP_SEND_BY_EMAIL="true"
8#MSMTP_HOST="smtp.example.com"
9#MSMTP_PORT="465"
10#MSMTP_USER="mail@example.com"
11#MSMTP_PASSWORD="SuPeRsEcReTpAsSwOrD"
12#MSMTP_FROM="mail@example.com"
13#OTP_SEND_BY_TELEGRAM="true"
14#TG_TOKEN="1234567890:QWERTYuio-PA1DFGHJ2_KlzxcVBNmqWEr3t"
docker-compose.yml
- файл описания сервисов (контейнеров) openconnect и certbot (Let’s Encrypt):
1---
2
3networks:
4
5 vpn:
6 ipam:
7 driver: default
8 config:
9 - subnet: 172.22.22.0/24
10 gateway: 172.22.22.1
11
12services:
13
14 certbot:
15 image: certbot/certbot
16 container_name: certbot
17 hostname: certbot
18 env_file:
19 - .env
20 volumes:
21 - ./data/ssl:/etc/letsencrypt
22 ports:
23 - 80:80
24 command: certonly --non-interactive --keep-until-expiring --standalone --preferred-challenges http --agree-tos --email ${USER_EMAIL} -d ${SRV_CN}
25
26 openconnect:
27 depends_on:
28 certbot:
29 condition: service_completed_successfully
30 build: .
31 image: openconnect
32 #image: r4venme/openconnect:v1.3-sid
33 container_name: openconnect
34 restart: unless-stopped
35 deploy:
36 resources:
37 limits:
38 cpus: '0.50'
39 memory: 200M
40 cap_add:
41 - NET_ADMIN
42 hostname: openconnect
43 env_file:
44 - .env
45 volumes:
46 - ./data:/etc/ocserv
47 #- /etc/passwd:/etc/passwd:ro
48 #- /etc/group:/etc/group:ro
49 #- /etc/shadow:/etc/shadow:ro
50 devices:
51 - /dev/net/tun:/dev/net/tun
52 ports:
53 - ${SRV_PORT}:443/tcp
54 networks:
55 vpn:
56 ipv4_address: 172.22.22.22
Dockerfile
- файл описания docker образа сервера openconnect для сборки:
1FROM debian:sid
2
3LABEL maintainer="Ivan Cherniy <kar-kar@r4ven.me>"
4
5ENV OCSERV_DIR="/etc/ocserv"
6ENV CERTS_DIR="${OCSERV_DIR}/certs"
7ENV SSL_DIR="${OCSERV_DIR}/ssl"
8ENV SECRETS_DIR="${OCSERV_DIR}/secrets"
9ENV SCRIPTS_DIR="${OCSERV_DIR}/scripts"
10ENV PATH="${SCRIPTS_DIR}:${PATH}"
11ENV SRV_CN="example.com"
12ENV SRV_CA="Example CA"
13ENV OTP_ENABLE="false"
14ENV OTP_SEND_BY_EMAIL="false"
15ENV OTP_SEND_BY_TELEGRAM="false"
16ENV MSMTP_HOST="smtp.example.com"
17ENV MSMTP_PORT="465"
18ENV MSMTP_USER="mail@example.com"
19ENV MSMTP_PASSWORD="PaSsw0rD"
20ENV MSMTP_FROM="mail@example.com"
21ENV TG_TOKEN="1234567890:QWERTYuio-PA1DFGHJ2_KlzxcVBNmqWEr3t"
22
23WORKDIR $OCSERV_DIR
24
25ARG DEBIAN_FRONTEND=noninteractive
26
27RUN apt update && \
28 apt install --yes --no-install-recommends \
29 tini \
30 ocserv \
31 gnutls-bin \
32 iptables \
33 iproute2 \
34 iputils-ping \
35 less \
36 ca-certificates \
37 xxd \
38 libpam-oath \
39 oathtool \
40 qrencode \
41 curl \
42 jq \
43 msmtp && \
44 apt autoremove --yes && \
45 apt clean --yes && \
46 rm -rf /var/lib/{apt,dpkg,cache,log}/*
47
48COPY ./ocserv.sh /
49
50ENTRYPOINT ["/ocserv.sh"]
51
52CMD ["/usr/bin/tini", "--", "/usr/sbin/ocserv", "--config", "/etc/ocserv/ocserv.conf", "--foreground"]
53
54HEALTHCHECK --interval=5m --timeout=3s \
55 CMD curl -k https://localhost:443/ || exit 1
ocserv.sh
- bash скрипт, запускаемый при старте контейнера, который выполняет начальную конфигурацию и инициализацию процесса ocserv:
1#!/bin/bash
2
3# Some protection
4set -Eeuo pipefail
5
6# Define default server vars if they are not set
7SRV_CN="${SRV_CN:=example.com}"
8SRV_CA="${SRV_CA:=Example CA}"
9OTP_ENABLE="${OTP_ENABLE:=false}"
10OTP_SEND_BY_EMAIL="${OTP_SEND_BY_EMAIL:=false}"
11OTP_SEND_BY_TELEGRAM="${OTP_SEND_BY_TELEGRAM:=false}"
12MSMTP_HOST="${MSMTP_HOST:=smtp.example.com}"
13MSMTP_PORT="${MSMTP_PORT:=465}"
14MSMTP_USER="${MSMTP_USER:=mail@example.com}"
15MSMTP_PASSWORD="${MSMTP_PASSWORD:=PaSsw0rD}"
16MSMTP_FROM="${MSMTP_FROM:=mail@example.com}"
17TG_TOKEN="${TG_TOKEN:=1234567890:QWERTYuio-PA1DFGHJ2_KlzxcVBNmqWEr3t}"
18
19# Ocserv vars (do not modify)
20OCSERV_DIR="/etc/ocserv"
21CERTS_DIR="${OCSERV_DIR}/certs"
22SSL_DIR="${OCSERV_DIR}/ssl"
23SECRETS_DIR="${OCSERV_DIR}/secrets"
24SCRIPTS_DIR="${OCSERV_DIR}/scripts"
25
26# Create certs dirs
27for sub_dir in "${OCSERV_DIR}"/{"ssl/live/${SRV_CN}","certs","secrets","scripts"}; do
28 if [[ ! -d "$sub_dir" ]]; then
29 mkdir -p "$sub_dir"
30 fi
31done
32
33if [[ -r /usr/share/doc/ocserv/sample.config && ! -e "${OCSERV_DIR}"/sample.config ]]; then
34 cp /usr/share/doc/ocserv/sample.config "${OCSERV_DIR}"/
35fi
36
37# Create ocserv config file
38if [[ ! -e "${OCSERV_DIR}"/ocserv.conf ]]; then
39cat << _EOF_ > "${OCSERV_DIR}"/ocserv.conf
40auth = "certificate"
41#auth = "plain[passwd=${OCSERV_DIR}/ocpasswd]"
42#auth = "plain[passwd=/etc/ocserv/ocpasswd,otp=/etc/ocserv/secrets/users.oath]"
43#enable-auth = "certificate"
44#enable-auth = "pam"
45tcp-port = 443
46socket-file = /run/ocserv-socket
47server-cert = ${SSL_DIR}/live/${SRV_CN}/fullchain.pem
48server-key = ${SSL_DIR}/live/${SRV_CN}/privkey.pem
49ca-cert = ${CERTS_DIR}/ca-cert.pem
50isolate-workers = true
51max-clients = 20
52max-same-clients = 2
53rate-limit-ms = 200
54server-stats-reset-time = 604800
55keepalive = 10
56dpd = 120
57mobile-dpd = 1800
58switch-to-tcp-timeout = 25
59try-mtu-discovery = false
60cert-user-oid = 0.9.2342.19200300.100.1.1
61tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.3"
62auth-timeout = 1000
63min-reauth-time = 300
64max-ban-score = 100
65ban-reset-time = 1200
66cookie-timeout = 600
67deny-roaming = false
68rekey-time = 172800
69rekey-method = ssl
70connect-script = ${SCRIPTS_DIR}/connect
71disconnect-script = ${SCRIPTS_DIR}/disconnect
72use-occtl = true
73pid-file = /run/ocserv.pid
74log-level = 1
75device = vpns
76predictable-ips = true
77default-domain = $SRV_CN
78ipv4-network = 10.10.10.0
79ipv4-netmask = 255.255.255.0
80tunnel-all-dns = true
81dns = 8.8.8.8
82ping-leases = false
83config-per-user = ${OCSERV_DIR}/config-per-user/
84cisco-client-compat = true
85dtls-legacy = true
86client-bypass-protocol = false
87crl = /etc/ocserv/certs/crl.pem
88#camouflage = true
89#camouflage_secret = "secretword"
90#camouflage_realm = "Welcome to admin panel"
91_EOF_
92fi
93
94# Create template for CA SSL cert
95if [[ ! -e "${CERTS_DIR}"/ca.tmpl ]]; then
96cat << _EOF_ > "${CERTS_DIR}"/ca.tmpl
97organization = $SRV_CN
98cn = $SRV_CA
99serial = 001
100expiration_days = -1
101ca
102signing_key
103cert_signing_key
104crl_signing_key
105_EOF_
106fi
107
108# Create template for users SSL certs
109if [[ ! -e "${CERTS_DIR}"/users.cfg ]]; then
110cat << _EOF_ > "${CERTS_DIR}"/users.cfg
111organization = $SRV_CN
112cn = Example User
113uid = exampleuser
114expiration_days = -1
115tls_www_client
116signing_key
117encryption_key
118_EOF_
119fi
120
121# Create template for server self-signed SSL cert
122if [[ ! -e "${SSL_DIR}"/server.tmpl ]]; then
123cat << _EOF_ > "${SSL_DIR}"/server.tmpl
124cn = $SRV_CA
125dns_name = $SRV_CN
126organization = $SRV_CN
127expiration_days = -1
128signing_key
129encryption_key #only if the generated key is an RSA one
130tls_www_server
131_EOF_
132fi
133
134# Generate empty revoke file
135if [[ ! -e "${CERTS_DIR}"/crl.tmpl ]]; then
136cat << _EOF_ > "${CERTS_DIR}"/crl.tmpl
137crl_next_update = 365
138crl_number = 1
139_EOF_
140fi
141
142# Create connect script which runs for every user connection
143if [[ ! -e "${SCRIPTS_DIR}"/connect ]]; then
144cat << _EOF_ > "${SCRIPTS_DIR}"/connect && chmod +x "${SCRIPTS_DIR}"/connect
145#!/bin/bash
146
147set -Eeuo pipefail
148
149echo "\$(date) User \${USERNAME} Connected - Server: \${IP_REAL_LOCAL} VPN IP: \${IP_REMOTE} Remote IP: \${IP_REAL} Device:\${DEVICE}"
150echo "Running iptables MASQUERADE for User \${USERNAME} connected with VPN IP \${IP_REMOTE}"
151iptables -t nat -A POSTROUTING -s "\${IP_REMOTE}"/32 -o eth0 -j MASQUERADE
152_EOF_
153fi
154
155# Create disconnect script which runs for every user disconnection
156if [[ ! -e "${SCRIPTS_DIR}"/disconnect ]]; then
157cat << _EOF_ > "${SCRIPTS_DIR}"/disconnect && chmod +x "${SCRIPTS_DIR}"/disconnect
158#!/bin/bash
159
160set -Eeuo pipefail
161
162echo "\$(date) User \${USERNAME} Disconnected - Bytes In: \${STATS_BYTES_IN} Bytes Out: \${STATS_BYTES_OUT} Duration:\${STATS_DURATION}"
163iptables -t nat -D POSTROUTING -s "\${IP_REMOTE}"/32 -o eth0 -j MASQUERADE
164_EOF_
165fi
166
167# Create script to create new users
168if [[ ! -e "${SCRIPTS_DIR}"/ocuser ]]; then
169cat << _EOF_ > "${SCRIPTS_DIR}"/ocuser && chmod +x "${SCRIPTS_DIR}"/ocuser
170#!/bin/bash
171
172set -Eeuo pipefail
173
174# Check and set script params
175if [[ \$# -eq 2 ]]; then
176 USER_UID="\$1"
177 USER_CN="\$2"
178elif [[ \$# -eq 3 ]]; then
179 if [[ "\$1" == "-A" ]]; then
180 USER_UID="\$2"
181 USER_CN="\$3"
182 else
183 echo "Use -A key as a first param to generate cert for IOS devices" >&2
184 exit 1
185 fi
186else
187 echo "Please run script with two params: username and 'Common Username'" >&2
188 echo "Example: ocuser john 'John Doe'" >&2
189 echo "For IOS or HarmonyOS devices add -A key as first param in command" >&2
190 echo "Example: ocuser -A steve 'Steve Jobs'" >&2
191 exit 1
192fi
193
194# Modify user cert template and generate user key, cert and protected .p12 file
195sed -i -e "s/^organization.*/organization = \$SRV_CN/" -e "s/^cn.*/cn = \$USER_CN/" -e "s/^uid.*/uid = \$USER_UID/g" "\${CERTS_DIR}"/users.cfg
196echo "\$(tr -cd "[:alnum:]" < /dev/urandom | head -c 60)" | ocpasswd -c "\${OCSERV_DIR}"/ocpasswd "\$USER_UID"
197certtool --generate-privkey --outfile "\${CERTS_DIR}"/"\${USER_UID}"-privkey.pem
198certtool --generate-certificate --load-privkey "\${CERTS_DIR}"/"\${USER_UID}"-privkey.pem --load-ca-certificate "\${CERTS_DIR}"/ca-cert.pem --load-ca-privkey "\${CERTS_DIR}"/ca-key.pem --template "\${CERTS_DIR}"/users.cfg --outfile "\${CERTS_DIR}"/"\${USER_UID}"-cert.pem
199if [[ "\$1" == "-A" ]]; then
200 sleep 1 && certtool --to-p12 --load-certificate "\${CERTS_DIR}"/"\${USER_UID}"-cert.pem --load-privkey "\${CERTS_DIR}"/"\${USER_UID}"-privkey.pem --pkcs-cipher 3des-pkcs12 --hash SHA1 --outder --outfile "\${SECRETS_DIR}"/"\${USER_UID}".p12
201else
202 sleep 1 && certtool --load-certificate "\${CERTS_DIR}"/"\${USER_UID}"-cert.pem --load-privkey "\${CERTS_DIR}"/"\${USER_UID}"-privkey.pem --pkcs-cipher aes-256 --to-p12 --outder --outfile "\${SECRETS_DIR}"/"\${USER_UID}".p12
203fi
204_EOF_
205fi
206
207# Add revoke script
208if [[ ! -e "${SCRIPTS_DIR}"/ocrevoke ]]; then
209cat << _EOF_ > "${SCRIPTS_DIR}"/ocrevoke && chmod +x "${SCRIPTS_DIR}"/ocrevoke
210#!/bin/bash
211
212set -Eeuo pipefail
213
214if [[ ! -e "\${CERTS_DIR}"/crl.tmpl ]]; then
215cat << __EOF__ > "\${CERTS_DIR}"/crl.tmpl
216crl_next_update = 365
217crl_number = 1
218__EOF__
219fi
220
221if [[ \$# -eq 1 ]]; then
222 if [[ "\$1" == "HELP" ]]; then
223 echo "Usage:
224 CMD to revoke cert of some user: ocrevoke <exist_user>
225 CMD to apply current revoked.pem: ocrevoke RELOAD
226 CMD to reset all revokes: ocrevoke RESET
227 CMD to print this help: ocrevoke HELP"
228 elif [[ "\$1" == "RESET" ]]; then
229 certtool --generate-crl --load-ca-privkey "\${CERTS_DIR}"/ca-key.pem --load-ca-certificate "\${CERTS_DIR}"/ca-cert.pem --template "\${CERTS_DIR}"/crl.tmpl --outfile "\${CERTS_DIR}"/crl.pem
230 occtl reload
231 elif [[ "\$1" == "RELOAD" ]]; then
232 certtool --generate-crl --load-ca-privkey "\${CERTS_DIR}"/ca-key.pem --load-ca-certificate "\${CERTS_DIR}"/ca-cert.pem --load-certificate "\${CERTS_DIR}"/revoked.pem --template "\${CERTS_DIR}"/crl.tmpl --outfile "\${CERTS_DIR}"/crl.pem
233 else
234 USER_UID="\$1"
235 cat "\${CERTS_DIR}"/"\${USER_UID}"-cert.pem >> "\${CERTS_DIR}"/revoked.pem
236 certtool --generate-crl --load-ca-privkey "\${CERTS_DIR}"/ca-key.pem --load-ca-certificate "\${CERTS_DIR}"/ca-cert.pem --load-certificate "\${CERTS_DIR}"/revoked.pem --template "\${CERTS_DIR}"/crl.tmpl --outfile "\${CERTS_DIR}"/crl.pem
237 occtl reload
238 fi
239else
240 echo "Usage:
241 CMD to revoke cert of some user: ocrevoke <exist_user>
242 CMD to apply current revoked.pem: ocrevoke RELOAD
243 CMD to reset all revokes: ocrevoke RESET
244 CMD to print this help: ocrevoke HELP"
245fi
246_EOF_
247fi
248
249# Add ocuser2fa script
250if [[ "$OTP_ENABLE" == "true" && ! -e "${SCRIPTS_DIR}"/ocuser2fa ]]; then
251cat << _EOF_ > "${SCRIPTS_DIR}"/ocuser2fa && chmod +x "${SCRIPTS_DIR}"/ocuser2fa
252#!/bin/bash
253
254set -Eeuo pipefail
255
256if [[ \$# -eq 1 ]]; then
257 USER_ID="\$1"
258 OTP_SECRET="\$(head -c 16 /dev/urandom | xxd -c 256 -ps)"
259 OTP_SECRET_BASE32="\$(echo 0x"\${OTP_SECRET}" | xxd -r -c 256 | base32)"
260 OTP_SECRET_QR="otpauth://totp/\$USER_ID?secret=\$OTP_SECRET_BASE32&issuer=$SRV_CA&algorithm=SHA1&digits=6&period=30"
261
262 if [[ ! -e "\${SECRETS_DIR}"/users.oath ]] || ! grep -qP "(?<!\\S)\${USER_ID}(?!\\S)" "\${SECRETS_DIR}"/users.oath; then
263 echo "HOTP/T30 \$USER_ID - \$OTP_SECRET" >> "\${SECRETS_DIR}"/users.oath
264 echo "OTP secret for \$USER_ID: \$OTP_SECRET"
265 echo "OTP secret in base32: \$OTP_SECRET_BASE32"
266 echo "OTP secret in QR code:"
267 qrencode -t ANSIUTF8 "\$OTP_SECRET_QR"
268 qrencode "\$OTP_SECRET_QR" -s 10 -o "\${SECRETS_DIR}"/otp_"\${USER_ID}".png
269 echo "TOTP secret in png image saved at: \${SECRETS_DIR}/otp_\${USER_ID}.png"
270
271 send_qr_by_email() {
272 EMAIL_REGEX="^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
273
274 if [[ \$USER_ID =~ \$EMAIL_REGEX ]]; then
275 cat << EOF | msmtp --file="\${SCRIPTS_DIR}"/msmtprc "\$USER_ID"
276Subject: TOTP QR code for OpenConnect auth
277MIME-Version: 1.0
278Content-Type: multipart/mixed; boundary="boundary"
279
280--boundary
281Content-Type: text/plain
282
283TOTP secret for OpenConnect (base32):
284\$OTP_SECRET_BASE32
285
286--boundary
287Content-Type: image/png; name="file.png"
288Content-Transfer-Encoding: base64
289Content-Disposition: attachment; filename="file.png"
290
291\$(base64 "\${SECRETS_DIR}"/otp_"\${USER_ID}".png)
292--boundary--
293EOF
294 echo "[\$(date '+%F %T')] - TOTP secret and QR code successfully sent to \$USER_ID via Email" | tee -a "\${OCSERV_DIR}"/pam.log
295 else
296 return 0
297 fi
298 }
299
300 if [[ "\$OTP_SEND_BY_EMAIL" == "true" ]]; then send_qr_by_email; fi
301
302 send_qr_by_telegram() {
303 TG_REGEX="^[a-zA-Z][a-zA-Z0-9_]{4,31}\$"
304
305 if [[ \$USER_ID =~ \$TG_REGEX ]]; then
306 TG_MESSAGE="TOTP secret for OpenConnect (base32):
307\$OTP_SECRET_BASE32"
308 TG_USER_FILE="\${SCRIPTS_DIR}/tg_users.txt"
309
310 if grep -qP "(?<!\\S)\${USER_ID}(?!\\S)" "\$TG_USER_FILE" 2> /dev/null; then
311 TG_CHAT_ID=\$(grep -P "(?<!\\S)\${USER_ID}(?!\\S)" "\$TG_USER_FILE" | awk '{print \$1}')
312 else
313 TG_RESPONSE="\$(curl -s "https://api.telegram.org/bot\$TG_TOKEN/getUpdates")"
314 TG_CHAT_ID=\$(echo "\$TG_RESPONSE" | jq -r --arg USERNAME "\$USER_ID" '.result[] | select(.message.from.username == \$USERNAME) | .message.chat.id')
315
316 if [[ -z "\$TG_CHAT_ID" ]]; then
317 echo "[\$(date '+%F %T')] - User was not found or did not interact with the bot" >> "\${OCSERV_DIR}"/pam.log
318 return 0
319 fi
320 echo "\$TG_CHAT_ID \$USER_ID" >> "\$TG_USER_FILE"
321 fi
322
323 curl -s -X POST "https://api.telegram.org/bot\$TG_TOKEN/sendPhoto" \\
324 -H "Content-Type: multipart/form-data" \\
325 -F "chat_id=\$TG_CHAT_ID" \\
326 -F "photo=@\${SECRETS_DIR}/otp_\${USER_ID}.png" \\
327 -F "caption=\$TG_MESSAGE" > /dev/null 2>> "\${OCSERV_DIR}"/pam.log
328
329 echo "[\$(date '+%F %T')] - TOTP secret and QR code successfully sent to \$USER_ID via Telegram" | tee -a "\${OCSERV_DIR}"/pam.log
330 fi
331 }
332
333 if [[ "\$OTP_SEND_BY_TELEGRAM" == "true" ]]; then send_qr_by_telegram; fi
334
335 else
336 echo "OTP token already exists for \$USER_ID in \${SECRETS_DIR}/users.oath"
337 exit 1
338 fi
339else
340 echo "Usage: \$(basename "\$0") <user_id>"
341 exit 1
342fi
343_EOF_
344fi
345
346if [[ "$OTP_ENABLE" == "true" && ! -e "${SCRIPTS_DIR}"/otp_sender ]]; then
347cat << _EOF_ > "${SCRIPTS_DIR}"/otp_sender && chmod +x "${SCRIPTS_DIR}"/otp_sender
348#!/bin/bash
349
350set -Eeuo pipefail
351
352OCSERV_DIR="$OCSERV_DIR"
353SECRETS_DIR="$SECRETS_DIR"
354SCRIPTS_DIR="$SCRIPTS_DIR"
355OTP_SEND_BY_EMAIL="$OTP_SEND_BY_EMAIL"
356OTP_SEND_BY_TELEGRAM="$OTP_SEND_BY_TELEGRAM"
357TG_TOKEN="$TG_TOKEN"
358
359echo "[\$(date '+%F %T')] - PAM user \$PAM_USER is trying to connect to ocserv" >> "\${OCSERV_DIR}"/pam.log
360
361otp_sender_by_email() {
362 EMAIL_REGEX="^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
363 if [[ \$PAM_USER =~ \$EMAIL_REGEX ]]; then true; else return 0; fi
364
365 if [[ -e "\${SECRETS_DIR}"/users.oath ]] && grep -qP "(?<!\\S)\${PAM_USER}(?!\\S)" "\${SECRETS_DIR}"/users.oath; then
366 OTP_TOKEN="\$(oathtool --totp=SHA1 --time-step-size=30 --digits=6 \$(grep -P "(?<!\\S)\${PAM_USER}(?!\\S)" \${SECRETS_DIR}/users.oath | awk '{print \$4}'))"
367
368 echo -e "Subject: TOTP token for OpenConnect\n\n\${OTP_TOKEN}" | msmtp --file="\${SCRIPTS_DIR}"/msmtprc "\$PAM_USER"
369 echo "[\$(date '+%F %T')] - TOTP token successfully sent to \$PAM_USER" >> "\${OCSERV_DIR}"/pam.log
370 fi
371}
372
373otp_sender_by_telegram() {
374 TG_REGEX="^[a-zA-Z][a-zA-Z0-9_]{4,31}\$"
375 if [[ \$PAM_USER =~ \$TG_REGEX ]]; then true; else return 0; fi
376
377 if grep -qP "(?<!\\S)\${PAM_USER}(?!\\S)" "\${SECRETS_DIR}"/users.oath 2> /dev/null; then
378 OTP_TOKEN="\$(oathtool --totp=SHA1 --time-step-size=30 --digits=6 \$(grep -P "(?<!\\S)\${PAM_USER}(?!\\S)" \${SECRETS_DIR}/users.oath | awk '{print \$4}'))"
379 TG_MESSAGE="TOTP token for OpenConnect: \$OTP_TOKEN"
380 TG_USER_FILE="\${SCRIPTS_DIR}/tg_users.txt"
381
382 if grep -qP "(?<!\\S)\$PAM_USER(?!\\S)" "\$TG_USER_FILE"; then
383 TG_CHAT_ID=\$(grep -P "(?<!\\S)\${PAM_USER}(?!\\S)" "\$TG_USER_FILE" | awk '{print \$1}')
384 else
385 TG_RESPONSE="\$(curl -s "https://api.telegram.org/bot\$TG_TOKEN/getUpdates")"
386 TG_CHAT_ID=\$(echo "\$TG_RESPONSE" | jq -r --arg USERNAME "\$PAM_USER" '.result[] | select(.message.from.username == \$USERNAME) | .message.chat.id')
387
388 if [[ -z "\$TG_CHAT_ID" ]]; then
389 echo "[\$(date '+%F %T')] - User was not found or did not interact with the bot" >> "\${OCSERV_DIR}"/pam.log
390 return 0
391 fi
392 echo "\$TG_CHAT_ID \$PAM_USER" >> "\$TG_USER_FILE"
393 fi
394
395 curl -s -X POST "https://api.telegram.org/bot\$TG_TOKEN/sendMessage" -d "chat_id=\$TG_CHAT_ID" -d "text=\$TG_MESSAGE" 2>> "\${OCSERV_DIR}"/pam.log
396 echo "[\$(date '+%F %T')] - TOTP token successfully sent to \$PAM_USER" >> "\${OCSERV_DIR}"/pam.log
397 fi
398}
399
400if [[ "\$OTP_SEND_BY_EMAIL" == "true" ]]; then otp_sender_by_email; fi &
401
402if [[ "\$OTP_SEND_BY_TELEGRAM" == "true" ]]; then otp_sender_by_telegram; fi &
403_EOF_
404elif [[ "$OTP_ENABLE" == "true" && -e "${SCRIPTS_DIR}"/otp_sender ]]; then
405 sed -i "s|OCSERV_DIR=.*|OCSERV_DIR=\"$OCSERV_DIR\"|" "${SCRIPTS_DIR}"/otp_sender
406 sed -i "s|SECRETS_DIR=.*|SECRETS_DIR=\"$SECRETS_DIR\"|" "${SCRIPTS_DIR}"/otp_sender
407 sed -i "s|SCRIPTS_DIR=.*|SCRIPTS_DIR=\"$SCRIPTS_DIR\"|" "${SCRIPTS_DIR}"/otp_sender
408 sed -i "s|OTP_SEND_BY_EMAIL=.*|OTP_SEND_BY_EMAIL=\"$OTP_SEND_BY_EMAIL\"|" "${SCRIPTS_DIR}"/otp_sender
409 sed -i "s|OTP_SEND_BY_TELEGRAM=.*|OTP_SEND_BY_TELEGRAM=\"$OTP_SEND_BY_TELEGRAM\"|" "${SCRIPTS_DIR}"/otp_sender
410 sed -i "s|TG_TOKEN=.*|TG_TOKEN=\"$TG_TOKEN\"|" "${SCRIPTS_DIR}"/otp_sender
411fi
412
413# Add msmtprc config
414if [[ "$OTP_ENABLE" == "true" && "$OTP_SEND_BY_EMAIL" == "true" && ! -e "${OCSERV_DIR}"/msmtprc ]]; then
415cat << _EOF_ > "${SCRIPTS_DIR}"/msmtprc && chmod 400 "${SCRIPTS_DIR}"/msmtprc
416account default
417host $MSMTP_HOST
418port $MSMTP_PORT
419auth on
420user $MSMTP_USER
421password $MSMTP_PASSWORD
422from $MSMTP_FROM
423tls on
424tls_starttls off
425logfile $OCSERV_DIR/pam.log
426_EOF_
427fi
428
429# Config OTP with PAM
430pam_otp() {
431 if [[ $OTP_ENABLE == "true" ]]; then
432 until [[ -e /etc/pam.d/ocserv ]]; do sleep 5; done
433 if grep -q 'otp_sender' /etc/pam.d/ocserv && grep -q 'users.oath' /etc/pam.d/ocserv; then return 0; fi
434 sleep 3
435 echo "auth optional pam_exec.so ${SCRIPTS_DIR}/otp_sender" >> /etc/pam.d/ocserv
436 echo "auth requisite pam_oath.so debug usersfile=${SECRETS_DIR}/users.oath window=20" >> /etc/pam.d/ocserv
437 fi
438}
439
440# Start ocserv service
441if [[ -e "${SSL_DIR}"/live/"${SRV_CN}"/privkey.pem && -e "${SSL_DIR}"/live/"${SRV_CN}"/fullchain.pem && -e "${CERTS_DIR}"/ca-key.pem && -e "${CERTS_DIR}"/ca-cert.pem ]]; then
442 pam_otp &
443 echo "Starting OpenConnect Server"
444 exec "$@" || { echo "Starting failed" >&2; exit 1; }
445else
446 # Server certificates generation
447 certtool --generate-privkey --outfile "${CERTS_DIR}"/ca-key.pem
448 certtool --generate-self-signed --load-privkey "${CERTS_DIR}"/ca-key.pem --template "${CERTS_DIR}"/ca.tmpl --outfile "${CERTS_DIR}"/ca-cert.pem
449 certtool --generate-crl --load-ca-privkey "${CERTS_DIR}"/ca-key.pem --load-ca-certificate "${CERTS_DIR}"/ca-cert.pem --template "${CERTS_DIR}"/crl.tmpl --outfile "${CERTS_DIR}"/crl.pem
450 if [[ ! -e "${SSL_DIR}"/live/"${SRV_CN}"/privkey.pem && ! -e "${SSL_DIR}"/live/"${SRV_CN}"/fullchain.pem ]]; then
451 certtool --generate-privkey --outfile "${SSL_DIR}"/live/"${SRV_CN}"/privkey.pem
452 certtool --generate-certificate --load-privkey "${SSL_DIR}"/live/"${SRV_CN}"/privkey.pem --load-ca-certificate "${CERTS_DIR}"/ca-cert.pem --load-ca-privkey "${CERTS_DIR}"/ca-key.pem --template "${SSL_DIR}"/server.tmpl --outfile "${SSL_DIR}"/live/"${SRV_CN}"/fullchain.pem
453 fi
454
455 pam_otp &
456 echo "Starting OpenConnect Server"
457 exec "$@" || { echo "Starting failed" >&2; exit 1; }
458fi
openconnect.service
- файл unit’а systemd для автозапуска:
1[Unit]
2Description=OpenConnect VPN service
3Requires=docker.service
4After=docker.service
5
6[Service]
7Restart=on-failure
8RestartSec=5
9WorkingDirectory=/opt/openconnect
10ExecStart=/usr/bin/docker compose up
11ExecStop=/usr/bin/docker compose down
12
13[Install]
14WantedBy=multi-user.target
ssl_update.sh
- bash скрипт обновления SSL сертификатов от Let’s Encrypt:
1#!/usr/bin/env bash
2
3set -e
4
5WORK_DIR="/opt/openconnect"
6CONTAINER_NAME="openconnect"
7
8to_log () {
9 local text="$1"
10 echo "[$(date '+%F %T')] ${text}"
11}
12
13cd "$WORK_DIR" || exit 1
14
15if [[ -r ./docker-compose.yml ]]; then
16 to_log "Run certbot service container"
17 docker compose up certbot
18 sleep 3
19 to_log "Reload ocserv config"
20 docker exec "$CONTAINER_NAME" occtl reload
21 to_log "Delete all unused docker images"
22 docker system prune -af
23fi
Переопределение переменных: домен, имя сервера, email и пр.
Теперь необходимо задать переменные окружения, которые будут использованы для автоматической подстановки при сборке и запуске контейнера. Для этого открываем любым редактором файл .env
, лежащий в директории с файлами проекта:
📝 Примечание
Файл .env
обычно используется для определения переменных окружения, которые считываются утилитой docker-compose, при обращении к ним в файле описания сервисов — docker-compose.yml
. Также эти переменные мы передадим внутрь контейнера openconnect.
vim .env
Всего 5 переменных:
TZ
— временная зона;SRV_PORT
— сетевой порт подключения к серверу;SRV_CN
— тут задаём доменное имя, которое указывает на Linux сервер (если домена нет, то указываем произвольное имя);SRV_CA
— названия центра сертификации для самоподписанных сертификатов (произвольное, задать обязательно);USER_EMAIL
— адрес электронной почты, если используется доменное имя. Данный параметр обязателен для получения SSL сертификатов от letsencrypt.
В комментариях подсказали, что в случае подключения к ocserv роутером Keenetic, «то порт в
.env
нужно ставить дефолтный 443 (который не нужно указывать в строке подключения)«. Иные порты, данный роутер, игнорирует.
Сохраняем файл и выходим:
:wq
Понравился мой конфиг Neovim?
Можете с легкостью создать аналогичный по статье: Neovim – Установка и настройка редактора кода с элементами IDE всего в несколько команд.
Сборка образа и первый запуск контейнера с помощью docker-compose
Если у вас нет домена, переходите к шагам внутри спойлера ниже: «Клик сюда, если домена нет».
Теперь просто запускаем команду сборки образа с последующим запуском контейнера в фоновом режиме и после подключаемся к стандартному выводу docker-compose:
docker compose up -d && docker compose logs -f
Обратите внимание, что сервис openconnect, описанный в файле docker-compose.yml намеренно ограничен в аппаратных ресурсах на использование
cpus: '0.50'
иmemory: 200M
, т.е максимально разрешенное использование CPU составляет 50% одного ядра и 200 мб RAM. При необходимости скорректируйте данные параметры в соответствии со своими потребностями.Подробнее про лимиты ресурсов сервисов при использовании docker-compose читайте тут.
В docker-compose.yml
параметром depends-on
указан порядок запуска сервисов. Сперва отрабатывает certbot
(Let’s Encrypt), который запрашивает и получает валидные SSL сертификаты, и только затем стартует контейнер с VPN сервером:
Подробную документацию по использованию certbot смотрите тут.
В случае успеха вывод будет, как на скрине выше. Просмотр вывода можно прервать комбинацией Ctrl+C
.
Чтобы убедится, что контейнер запущен выполняем:
docker compose ps
ls -l ./data
ls -l ./data/ssl/live/vpn.r4ven.me/
Должна создаться папка ./data
с файлами сервера + сертификаты, полученные контейнером cerbot.
ЛКМ для просмотра описания содержимого папки data
, которое создаёт скрипт ocserv.sh
при первом старте
ocserv.conf
— файл конфигурации нашего VPN сервера. Также можете модифицировать под свои нужды. К слову, полный конфиг ocserv с описанием доступных параметров можно изучить на официальном GitLab проекта по ссылке;
auth = "certificate"
#auth = "plain[passwd=/etc/ocserv/ocpasswd]"
#auth = "plain[passwd=/etc/ocserv/ocpasswd,otp=/etc/ocserv/secrets/users.oath]"
#enable-auth = "certificate"
#enable-auth = "pam"
tcp-port = 443
socket-file = /run/ocserv-socket
server-cert = /etc/ocserv/ssl/live/example.com/fullchain.pem
server-key = /etc/ocserv/ssl/live/example.com/privkey.pem
ca-cert = /etc/ocserv/certs/ca-cert.pem
isolate-workers = true
max-clients = 20
max-same-clients = 2
rate-limit-ms = 200
server-stats-reset-time = 604800
keepalive = 10
dpd = 120
mobile-dpd = 1800
switch-to-tcp-timeout = 25
try-mtu-discovery = false
cert-user-oid = 0.9.2342.19200300.100.1.1
tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.3"
auth-timeout = 1000
min-reauth-time = 300
max-ban-score = 100
ban-reset-time = 1200
cookie-timeout = 600
deny-roaming = false
rekey-time = 172800
rekey-method = ssl
connect-script = /etc/ocserv/scripts/connect
disconnect-script = /etc/ocserv/scripts/disconnect
use-occtl = true
pid-file = /run/ocserv.pid
log-level = 1
device = vpns
predictable-ips = true
default-domain = example.com
ipv4-network = 10.10.10.0
ipv4-netmask = 255.255.255.0
tunnel-all-dns = true
dns = 8.8.8.8
ping-leases = false
config-per-user = /etc/ocserv/config-per-user/
cisco-client-compat = true
dtls-legacy = true
client-bypass-protocol = false
crl = /etc/ocserv/certs/crl.pem
#camouflage = true
#camouflage_secret = "secretword"
#camouflage_realm = "Welcome to admin panel"
bin
— директория с внутренними скриптами/командами контейнера openconnect:connect
— bash скрипт, который выполняется для каждого клиента при подключении к нашему VPN серверу. Его можно расширять и кастоимизировать под любые ваши нужды. Главное не нарушьте инзначальную функциональность);
#!/bin/bash
set -Eeuo pipefail
echo "$(date) User ${USERNAME} Connected - Server: ${IP_REAL_LOCAL} VPN IP: ${IP_REMOTE} Remote IP: ${IP_REAL} Device:${DEVICE}"
echo "Running iptables MASQUERADE for User ${USERNAME} connected with VPN IP ${IP_REMOTE}"
iptables -t nat -A POSTROUTING -s ${IP_REMOTE}/32 -o eth0 -j MASQUERADE
disconnect
— bash скрипт, который выполняется при отключении пользователя;
#!/bin/bash
set -Eeuo pipefail
echo "$(date) User ${USERNAME} Disconnected - Bytes In: ${STATS_BYTES_IN} Bytes Out: ${STATS_BYTES_OUT} Duration:${STATS_DURATION}"
iptables -t nat -D POSTROUTING -s "${IP_REMOTE}"/32 -o eth0 -j MASQUERADE
ocuser
— bash скрипт для создания новых пользователей и сертификатов подключения.p12
;
#!/bin/bash
set -Eeuo pipefail
# Check and set script params
if [[ $# -eq 2 ]]; then
USER_UID="$1"
USER_CN="$2"
elif [[ $# -eq 3 ]]; then
if [[ "$1" == "-A" ]]; then
USER_UID="$2"
USER_CN="$3"
else
echo "Use -A key as a first param to generate cert for IOS devices" >&2
exit 1
fi
else
echo "Please run script with two params: username and 'Common Username'" >&2
echo "Example: ocuser john 'John Doe'" >&2
echo "For IOS or HarmonyOS devices add -A key as first param in command" >&2
echo "Example: ocuser -A steve 'Steve Jobs'" >&2
exit 1
fi
# Modify user cert template and generate user key, cert and protected .p12 file
sed -i -e "s/^organization.*/organization = $SRV_CN/" -e "s/^cn.*/cn = $USER_CN/" -e "s/^uid.*/uid = $USER_UID/g" "${CERTS_DIR}"/users.cfg
echo "$(tr -cd "[:alnum:]" < /dev/urandom | head -c 60)" | ocpasswd -c "${OCSERV_DIR}"/ocpasswd "$USER_UID"
certtool --generate-privkey --outfile "${CERTS_DIR}"/"${USER_UID}"-privkey.pem
certtool --generate-certificate --load-privkey "${CERTS_DIR}"/"${USER_UID}"-privkey.pem --load-ca-certificate "${CERTS_DIR}"/ca-cert.pem --load-ca-privkey "${CERTS_DIR}"/ca-key.pem --template "${CERTS_DIR}"/users.cfg --outfile "${CERTS_DIR}"/"${USER_UID}"-cert.pem
if [[ "$1" == "-A" ]]; then
sleep 1 && certtool --to-p12 --load-certificate "${CERTS_DIR}"/"${USER_UID}"-cert.pem --load-privkey "${CERTS_DIR}"/"${USER_UID}"-privkey.pem --pkcs-cipher 3des-pkcs12 --hash SHA1 --outder --outfile "${SECRETS_DIR}"/"${USER_UID}".p12
else
sleep 1 && certtool --load-certificate "${CERTS_DIR}"/"${USER_UID}"-cert.pem --load-privkey "${CERTS_DIR}"/"${USER_UID}"-privkey.pem --pkcs-cipher aes-256 --to-p12 --outder --outfile "${SECRETS_DIR}"/"${USER_UID}".p12
fi
ocuser2fa
— bash скрипт для генерации секретов TOTP при использовании двухфакторной авторизации;
#!/bin/bash
set -Eeuo pipefail
if [[ $# -eq 1 ]]; then
USER_ID="$1"
OTP_SECRET="$(head -c 16 /dev/urandom | xxd -c 256 -ps)"
OTP_SECRET_BASE32="$(echo 0x"${OTP_SECRET}" | xxd -r -c 256 | base32)"
OTP_SECRET_QR="otpauth://totp/$USER_ID?secret=$OTP_SECRET_BASE32&issuer=Example CA&algorithm=SHA1&digits=6&period=30"
if [[ ! -e "${SECRETS_DIR}"/users.oath ]] || ! grep -qP "(?<!\S)${USER_ID}(?!\S)" "${SECRETS_DIR}"/users.oath; then
echo "HOTP/T30 $USER_ID - $OTP_SECRET" >> "${SECRETS_DIR}"/users.oath
echo "OTP secret for $USER_ID: $OTP_SECRET"
echo "OTP secret in base32: $OTP_SECRET_BASE32"
echo "OTP secret in QR code:"
qrencode -t ANSIUTF8 "$OTP_SECRET_QR"
qrencode "$OTP_SECRET_QR" -s 10 -o "${SECRETS_DIR}"/otp_"${USER_ID}".png
echo "TOTP secret in png image saved at: ${SECRETS_DIR}/otp_${USER_ID}.png"
send_qr_by_email() {
EMAIL_REGEX="^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if [[ $USER_ID =~ $EMAIL_REGEX ]]; then
cat << EOF | msmtp --file="${SCRIPTS_DIR}"/msmtprc "$USER_ID"
Subject: TOTP QR code for OpenConnect auth
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="boundary"
--boundary
Content-Type: text/plain
TOTP secret for OpenConnect (base32):
$OTP_SECRET_BASE32
--boundary
Content-Type: image/png; name="file.png"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="file.png"
$(base64 "${SECRETS_DIR}"/otp_"${USER_ID}".png)
--boundary--
EOF
echo "[$(date '+%F %T')] - TOTP secret and QR code successfully sent to $USER_ID via Email" | tee -a "${OCSERV_DIR}"/pam.log
else
return 0
fi
}
if [[ "$OTP_SEND_BY_EMAIL" == "true" ]]; then send_qr_by_email; fi
send_qr_by_telegram() {
TG_REGEX="^[a-zA-Z][a-zA-Z0-9_]{4,31}$"
if [[ $USER_ID =~ $TG_REGEX ]]; then
TG_MESSAGE="TOTP secret for OpenConnect (base32):
$OTP_SECRET_BASE32"
TG_USER_FILE="${SCRIPTS_DIR}/tg_users.txt"
if grep -qP "(?<!\S)${USER_ID}(?!\S)" "$TG_USER_FILE" 2> /dev/null; then
TG_CHAT_ID=$(grep -P "(?<!\S)${USER_ID}(?!\S)" "$TG_USER_FILE" | awk '{print $1}')
else
TG_RESPONSE="$(curl -s "https://api.telegram.org/bot$TG_TOKEN/getUpdates")"
TG_CHAT_ID=$(echo "$TG_RESPONSE" | jq -r --arg USERNAME "$USER_ID" '.result[] | select(.message.from.username == $USERNAME) | .message.chat.id')
if [[ -z "$TG_CHAT_ID" ]]; then
echo "[$(date '+%F %T')] - User was not found or did not interact with the bot" >> "${OCSERV_DIR}"/pam.log
return 0
fi
echo "$TG_CHAT_ID $USER_ID" >> "$TG_USER_FILE"
fi
curl -s -X POST "https://api.telegram.org/bot$TG_TOKEN/sendPhoto" \
-H "Content-Type: multipart/form-data" \
-F "chat_id=$TG_CHAT_ID" \
-F "photo=@${SECRETS_DIR}/otp_${USER_ID}.png" \
-F "caption=$TG_MESSAGE" > /dev/null 2>> "${OCSERV_DIR}"/pam.log
echo "[$(date '+%F %T')] - TOTP secret and QR code successfully sent to $USER_ID via Telegram" | tee -a "${OCSERV_DIR}"/pam.log
fi
}
if [[ "$OTP_SEND_BY_TELEGRAM" == "true" ]]; then send_qr_by_telegram; fi
else
echo "OTP token already exists for $USER_ID in ${SECRETS_DIR}/users.oath"
exit 1
fi
else
echo "Usage: $(basename "$0") <user_id>"
exit 1
fi
otp_sender
— bash скрипт для отправки TOTP токенов с помощью Email и Telegram при использовании PAM авторизации и включенном втором факторе (2FA);
#!/bin/bash
set -Eeuo pipefail
OCSERV_DIR="/etc/ocserv"
SECRETS_DIR="/etc/ocserv/secrets"
SCRIPTS_DIR="/etc/ocserv/scripts"
OTP_SEND_BY_EMAIL="true"
OTP_SEND_BY_TELEGRAM="true"
TG_TOKEN="1234567890:QWERTYuio-PA1DFGHJ2_KlzxcVBNmqWEr3t"
echo "[$(date '+%F %T')] - PAM user $PAM_USER is trying to connect to ocserv" >> "${OCSERV_DIR}"/pam.log
otp_sender_by_email() {
EMAIL_REGEX="^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if [[ $PAM_USER =~ $EMAIL_REGEX ]]; then true; else return 0; fi
if [[ -e "${SECRETS_DIR}"/users.oath ]] && grep -qP "(?<!\S)${PAM_USER}(?!\S)" "${SECRETS_DIR}"/users.oath; then
OTP_TOKEN="$(oathtool --totp=SHA1 --time-step-size=30 --digits=6 $(grep -P "(?<!\S)${PAM_USER}(?!\S)" ${SECRETS_DIR}/users.oath | awk '{print $4}'))"
echo -e "Subject: TOTP token for OpenConnect\n\n${OTP_TOKEN}" | msmtp --file="${SCRIPTS_DIR}"/msmtprc "$PAM_USER"
echo "[$(date '+%F %T')] - TOTP token successfully sent to $PAM_USER" >> "${OCSERV_DIR}"/pam.log
fi
}
otp_sender_by_telegram() {
TG_REGEX="^[a-zA-Z][a-zA-Z0-9_]{4,31}$"
if [[ $PAM_USER =~ $TG_REGEX ]]; then true; else return 0; fi
if grep -qP "(?<!\S)${PAM_USER}(?!\S)" "${SECRETS_DIR}"/users.oath 2> /dev/null; then
OTP_TOKEN="$(oathtool --totp=SHA1 --time-step-size=30 --digits=6 $(grep -P "(?<!\S)${PAM_USER}(?!\S)" ${SECRETS_DIR}/users.oath | awk '{print $4}'))"
TG_MESSAGE="TOTP token for OpenConnect: $OTP_TOKEN"
TG_USER_FILE="${SCRIPTS_DIR}/tg_users.txt"
if grep -qP "(?<!\S)$PAM_USER(?!\S)" "$TG_USER_FILE"; then
TG_CHAT_ID=$(grep -P "(?<!\S)${PAM_USER}(?!\S)" "$TG_USER_FILE" | awk '{print $1}')
else
TG_RESPONSE="$(curl -s "https://api.telegram.org/bot$TG_TOKEN/getUpdates")"
TG_CHAT_ID=$(echo "$TG_RESPONSE" | jq -r --arg USERNAME "$PAM_USER" '.result[] | select(.message.from.username == $USERNAME) | .message.chat.id')
if [[ -z "$TG_CHAT_ID" ]]; then
echo "[$(date '+%F %T')] - User was not found or did not interact with the bot" >> "${OCSERV_DIR}"/pam.log
return 0
fi
echo "$TG_CHAT_ID $PAM_USER" >> "$TG_USER_FILE"
fi
curl -s -X POST "https://api.telegram.org/bot$TG_TOKEN/sendMessage" -d "chat_id=$TG_CHAT_ID" -d "text=$TG_MESSAGE" 2>> "${OCSERV_DIR}"/pam.log
echo "[$(date '+%F %T')] - TOTP token successfully sent to $PAM_USER" >> "${OCSERV_DIR}"/pam.log
fi
}
if [[ "$OTP_SEND_BY_EMAIL" == "true" ]]; then otp_sender_by_email; fi &
if [[ "$OTP_SEND_BY_TELEGRAM" == "true" ]]; then otp_sender_by_telegram; fi &
ocrevoke
— bash скрипт для отзыва сертификатов пользователей;
#!/bin/bash
set -Eeuo pipefail
if [[ ! -e "${CERTS_DIR}"/crl.tmpl ]]; then
cat << __EOF__ > "${CERTS_DIR}"/crl.tmpl
crl_next_update = 365
crl_number = 1
__EOF__
fi
if [[ $# -eq 1 ]]; then
if [[ "$1" == "HELP" ]]; then
echo "Usage:
CMD to revoke cert of some user: ocrevoke <exist_user>
CMD to apply current revoked.pem: ocrevoke RELOAD
CMD to reset all revokes: ocrevoke RESET
CMD to print this help: ocrevoke HELP"
elif [[ "$1" == "RESET" ]]; then
certtool --generate-crl --load-ca-privkey "${CERTS_DIR}"/ca-key.pem --load-ca-certificate "${CERTS_DIR}"/ca-cert.pem --template "${CERTS_DIR}"/crl.tmpl --outfile "${CERTS_DIR}"/crl.pem
occtl reload
elif [[ "$1" == "RELOAD" ]]; then
certtool --generate-crl --load-ca-privkey "${CERTS_DIR}"/ca-key.pem --load-ca-certificate "${CERTS_DIR}"/ca-cert.pem --load-certificate "${CERTS_DIR}"/revoked.pem --template "${CERTS_DIR}"/crl.tmpl --outfile "${CERTS_DIR}"/crl.pem
else
USER_UID="$1"
cat "${CERTS_DIR}"/"${USER_UID}"-cert.pem >> "${CERTS_DIR}"/revoked.pem
certtool --generate-crl --load-ca-privkey "${CERTS_DIR}"/ca-key.pem --load-ca-certificate "${CERTS_DIR}"/ca-cert.pem --load-certificate "${CERTS_DIR}"/revoked.pem --template "${CERTS_DIR}"/crl.tmpl --outfile "${CERTS_DIR}"/crl.pem
occtl reload
fi
else
echo "Usage:
CMD to revoke cert of some user: ocrevoke <exist_user>
CMD to apply current revoked.pem: ocrevoke RELOAD
CMD to reset all revokes: ocrevoke RESET
CMD to print this help: ocrevoke HELP"
fi
certs
— директория с SSL файлами нашего «центра сертификации»;secrets
— директория, в которой хранятся сертификаты (.p12
) пользователей после их создания;ssl
— SSL сертификаты openconnect сервера, самоподписанные или полученные от проекта Let’s Encrypt.
Также проверяем прослушиваемый и проброшенный в хост контейнером TCP порт, такой командой:
ss -tnap | grep -E '43443'
Всё, сервер запущен и работает. Осталось создать пользователей и настроить клиентское подключение.
Клик сюда, если домена нет
В таком случае правим файл описания сервисов docker-compose.yml
:
vim docker-compose.yml
В этом файле необходимо закоментировать сервис certbot
и директиву depends_on
у сервиса openconnect
, как показано на скриншоте:
После чего запускаем сборку и смотрим вывод:
docker compose up -d && docker compose logs -f
Если все отработало корректно, выходим из режима просмотра логов клавишей Ctrl+C
и проверяем запущенный сервис:
docker compose ps
ss -tnap | grep -E '43443'
Также проверяем наличие системных файлов ocserv:
ls -l ./data
Отлично, идём дальше.
Создание пользователей
Пару слов про способы авторизации на OpenConnect сервере.
Т.к.
ocserv
популярен в корпоративном мире, он поддерживает множество способов авторизации пользователей. От банального логина/пароля, до интеграции с централизованными инструментами, такими, как LDAP/Active Directory, PAM, Radius и пр. Из коробки даже имеется поддержка двухфакторной аутентификации на основе токенов — этот функционал + возможность отправки токенов TOTP по почте и в Telegram добавлен в контейнер, описание приведу позже в этой статье.В качестве надежного и в тоже время удобного способа авторизации я выбрал подключение на основе пользовательского сертификата, который упаковывается в шифрованный (при указании пароля) файл с расширением
.p12
. В результате со стороны клиента необходимо будет лишь указать путь до файла сертификата и пароль для его расшифровки. Обычно клиенты способны запоминать пароли и вводить их более не требуется.Стоит отметить, что для создания пользователя ему все таки необходимо задать пароль, хоть и такой способ авторизации отключен в конфиг файле сервера. Если вы проявили любопытство и заглянули в содержимое скрипта создания пользователей
ocuser
, то могли заметить команду генерации псевдослучайной строки: tr -cd «[:alnum:]» < /dev/urandom | head -c 60Вывод которой задается в качестве пароля для пользователя при его создании. Небольшая подстраховка)
Создание пользователей для Linux/Windows/Android
И так, создаём пользователя такой командой:
docker exec -it openconnect ocuser ivan 'Ivan Cherniy'
Где ivan — это имя и идентификатор пользователя в ocserv (старайтесь указывать идентификатор без пробелов, дабы избежать ошибок), а ‘Ivan Cherniy’ это полное имя пользователя, которое будет отображено в метаданных пользовательского сертификата.
После ввода команды необходимо будет в интерактивном режиме указать имя файла сертификата и пароль для его шифрования.
Если команда создания отработала успешно, для указанного пользователя будет создан файл сертификата .p12
в директории secrets
:
ls -l ./data/secrets
Список пользователей можно посмотреть в файле ./data/ocpasswd
.
Создание пользователей для HarmonyOS/iOS
При создании пользователей для мобильных ОС от Huawei и Apple команде ocuser
необходимо передать ключ -A
. В таком случае будет использован другой алгоритм шифрования сертификата.
Пример команды:
docker exec -it openconnect ocuser -A steve 'Steve Jobs'
Проверяем созданный сертификат:
ls -l ./data/secrets
Если для устройств под управлением HarmonyOS и iOS сгенерировать сертификаты без ключа -A
, то при настройке клиентского подключения, вы будете получать ошибку на этапе импорта сертификата.
Note that the Ciso AnyConnect app on iOS doesn’t support AES-256 cipher. It will refuse to import the client certificate. If the user is using iOS device, then you can choose the
3des-pkcs12
cipher.linuxbabe.com
Скачивание файла p12
Скачиваем созданный файл сертификата пользователя удобным для вас способом. Например:
cp ./data/secrets/ivan.p12 /tmp
А на клиентской машине:
scp vpn.r4ven.me:/tmp/ivan.p12 .
ls -l ivan.p12
На этом этапе уже можно переходить к настройке клиентов. Но если вам нужны персональные настройки каждого пользователя, такие как IP адрес клиента, маршруты, DNS, а также настройка автозапуска сервиса OpenConnect и автопродление SSL сертификатов, то идём последовательно.
Опционально: индивидуальные настройки ocserv для каждого пользователя
Создаём системную директорию config-per-user
и отдельный файл под каждого пользователя, с таким же именем, какое указывали при создании (если забыли, посмотрите в файле ./data/ocpasswd
):
mkdir ./data/config-per-user
vim ./data/config-per-user/ivan
Название параметров в файле конфига пользователя в большинстве своем идентичны основному конфигу сервера:
explicit-ipv4 = 10.10.10.5
route = 10.10.10.50/32
route = 64.233.164.139/32
dns = 1.1.1.1
В примере выше я задаю для пользователя ivan конкретный IP (из подсети, что указана в основном конфиге), отдельные маршруты и адрес DNS сервера.
Upd. Если вы предпочитаете повысить уровень конфиденциальности, то при тунелировании трафика желательно иметь собственный DNS сервер. Вот инструкция по его настройке и интеграции с OpenConnect:
Поднимаем свой DNS сервер Unbound и блокировщик рекламы Pihole в docker
При такой конфигурации данный пользователь будет обращаться к указанным ресурсам через VPN сервер, а остальной трафик будет идти через его шлюз по умолчанию (или нет, в зависимости от настроек сети).
После внесения изменений в конфиг необходимо выполнить перечитывание файла конфигурации процессом ocserv
внутри контейнера. Делается такой командой:
docker exec -it openconnect occtl reload
Автозапуск сервера OpenConnect с помощью systemd
Теперь настроим автоматический запуск/перезапуск нашего VPN сервера при помощи системы инициализации systemd.
Из директории с файлами проекта копируем файл openconnect.service
в системную директорию systemd, перезагружаем конфигурацию systemd и останавливаем наши запущенные контейнеры:
cp ./openconnect.service /etc/systemd/system/
systemctl daemon-reload
docker compose down
Теперь активируем автозапуск и пробуем выполнить старт контейнера с OpenConnect сервером как сервис systemd:
systemctl enable --now openconnect
systemctl status openconnect
docker compose ps
Все отлично.
Настройка автоматического обновления SSL сертификатов (если есть домен)
Особенностью сертификатов от центра сертификации Let’s Encrypt является то, что они выдаются на 3 месяца. В такой ситуации предусмотрительно будет настроить автоматическое продление. Для этого добавляем в системный планировщик заданий cron
запуск скрипта ssl_update.sh
раз в неделю, например, в воскресенье в 3 часа утра:
{ crontab -l; echo "0 3 * * 0 /opt/openconnect/ssl_update.sh"; } | crontab -
crontab -l
Синтаксис фигурных скобок в bash
{ command1; command2 }
позволяет объединить несколько команд в одну. Тут мы с его помощью первой командой выводим список заданий cron текущего пользователя, второй командой выводим строку с новым заданием и потом с помощью механизма перенаправления передаем весь вывод командеcrontab -
. Если командеcrontab -
передать только одну задачу, она затрет все предыдущие. Так делать не стоит)Хоть и в моём примере нет других заданий в планировщике, команда специально составлена с «защитой от дурака»)
Для проверки работоспособности скрипта продления сертификатов, можем выполнить его вручную:
UPD 16.05.2024
В команде обновления certbot заменил параметр
--force-renewal
на--keep-until-expiring
, который выполняет проверку срока действия сертификата, и если он не близок к завершению, то обновление сертификатов не происходит.
/opt/openconnect/ssl_update.sh
Скрипт также почистит дисковое пространство от неиспользуемых образов docker:
Проверяем время обновления SSL файлов:
ls -l ./data/ssl/live/vpn.r4ven.me/
Так, вроде все хорошо)
Обратите внимание, что если слишком часто обновлять сертификаты, то certbot (letsencrypt) будет выпадать в ошибку по этой причине.
Настройка подключения клиентов
Настройка OpenConnect клиента для Linux
Способ №1: менеджер сетевых соединений — NetworkManager
Если вы пользователь популярных дестопных дистрибутивов, то вероятнее всего вашей сетевой подсистемой управлет NetworkManager.
Сразу отмечу, что данный способ предпочтителен для десктопных систем, т.к. при использовании NM для подключения к ocserv не происходит конфликтов системы DNS.
В случае deb based систем, выполняем установку специального пакета-плагина NetworkManager для подключения к ocserv.
По традиции, продемонстрирую на примере Linux Mint 21 Cinnamon (Ubuntu 22.04).
Команда установки плагина:
sudo apt update && sudo apt install network-manager-openconnect-gnome
После установки:
- Заходим в «Сетевые соединения» через трей или любым удобным вам способом;
- Затем нажимаем кнопку добавления нового соединения, в списке выбираем «Cisco AntConnect or openconnect (OpenConnect)» и нажимаем «Создать«;
- В открвшемся окошке указываем
- «Имя соединения» — произвольное
- «Шлюз» — DNS имя нашего сервера или его IP адрес (если без домена) + порт подключения. Пример:
vpn.r4ven.me:43443
- В разделе «Аутентификации по сертификату» в параметре «Сертификат пользователя» выбираем наш
.p12
файл, который мы сгнерировали на этапе создания пользователя. В старых версиях nmapplet’а есть баг, когда при выборе сертификата он не видит файлы с расширенияем.p12
. В таком случае просто перетащите (drag-and-drop) файл с файлового менеджера в поле данного параметра, как показано на скриншоте ниже; - После нажимаем «Сохранить».
Если все сделано корректно, пробуем подключиться к нашему серверу. Кликаем на апплет сетевых соединений, затем на переключатель «VPN подключения«. Должно появиться окошко, где нас любезно попросят ввести пароль от файла-сертификата (если вы его задавали), который мы устанавливали на этапе создания пользователей ocserv. Ставим галочку «Сохранить пароль» и затем «Подключиться«. При успешном соединении на рабочем столе появится соответствующее уведомление:
Также новое подключение для NetworkManager можно создать с помощью консольной утилиты nmcli. Вот пример:
nmcli connection add \
type vpn \
con-name "vpn.r4ven.me" \
ifname '*' \
vpn-type openconnect \
vpn.data "gateway=vpn.r4ven.me:43443, usercert=/home/ivan/ivan.p12"
Пробуем подключиться:
nmcli connection up vpn.r4ven.me
Успешно.
Посмотреть сетевые параметры нашего VPN подключения можно с помощью команд:
ip -c address
nmcli
Узнать внешний IP в консоли можно такой командой:
curl ifconfig.me
В выводе команды будет одна строка с вашим внешним IP адресом.
Способ №2: утилита командной строки — openconnect
Другой способ клиентского подключения в Linux, где отсутствует NetworkManager — это одноименная консольная утилита openconnect
. Такой способ подключения чаще всего используется для клиентов без GUI, т.е. на Linux серверах.
Пример подключения все также для deb based систем.
Выполняем установку клиентского пакета openconnect
:
sudo apt update && sudo apt install openconnect
Подключится к серверу OpenConnect можно такой командой:
# если с доменом
sudo openconnect -c /home/ivan/ivan.p12 vpn.r4ven.me:43443 <<< $(echo "password"$'\n')
# если без домена (с доп. подтверждением самоподписанного сертификата)
sudo openconnect -c /home/ivan/ivan.p12 12.345.67.89:43443 <<< $(echo "password"$'\n'yes$'\n')
Где ключу -c
передается путь до файла сертификата .p12
а с помощью механизма here string (<<<
) и подстановки передается вывод команды echo "password"$'\n'
, которая выводит текстовую строку и выполняет перевод строки, в данном случае, это имитация ввода Enter, чтобы не вводить вручную.
Замените password
на ваш пароль от сертификата.
Для автоматизации процесса подключения/переподключения с помощью утилиты
openconnect
я написал небольшой скрипт. Изучить его можно в статье: Пишем bash скрипт для подключения к OpenConnect VPN серверу.
Результат команды подключения должен быть примерно таким:
Если вы используете подключение с помощью утилиты openconnect
на Linux сервере, и планируете туннелировать весь его трафик, то необходимо добавить специальные правила маршрутизации, без которых вы потеряете доступ до своего сервера, в т.ч. и по SSH:
ip rule add table 128 from <public-ip>
ip route add table 128 to <public_ip_subnet> dev <ineteface_name>
ip route add table 128 default via <gateway>
Где:
- **128** — имя новой таблицы маршрутизации;
- **<piblic-ip>** — основной IP вашего сервера;
- **<public\_ip\_subnet>** — подсеть основного IP сервера;
- **<interface\_name>** — имя физического интерфейса, к которому подключен основной IP;
- **<gateway>** — шлюз сети, через который ходит основной IP.
Настройка OpenConnect клиента для Windows/MacOS
Для настройки клиента на Windows и MacOS необходимо скачать графическую программу-клиент с официального сайта https://gui.openconnect-vpn.net/download/ или на странице релизов в GitLab: https://gitlab.com/openconnect/openconnect-gui/-/releasesGitLab:
Далее устанавливаем программу на рабочий стол Windows обычными кликами «Далее, далее..». А вот как установить клиента на MacOS я вам не подскажу. Тут уже вы сами изучите вопрос, ссылки на все материалы будут внизу.
После установки и запуска программы OpenConnectVPN выполняем настройку, как показано на скриншотах ниже. Отмечу лишь один момент: обязательно активируйте параметр «Disable UDP«, т.к. в нашей конфигурации сервера его использование отключено:
Проверяем сетевые параметры:
Все работает.
Настройка OpenConnect клиента для Android/HarmonyOS
Для мобильных ОС необходимо установить приложение Cisco Secure Client-AnyConnect.
Пожалуйста, обратите внимание, что данное мобильное приложение не имеет открытый исходный код. При его использовании придется довериться разработчику.
Существует open source мобильный клиент, но его разработка прекратилась n-е количество лет назад и у меня оно работало некорректно.
После открытия приложения необходимо создать подключение, указать адрес шлюза + порт и импортировать сертификат пользователя. Пошаговая инструкция показана на скриншотах ниже:
Настройка OpenConnect клиента для iOS
Инструкция любезно предоставлена подписчиком в Вороньем чате
Скачиваешь файл на телефон. Открываешь файлы и зажимаешь этот значок (просто при тычке он будет писать техническое сообщение о том, что профиль загружен). Выбираешь пункт поделиться — Еще — Листаешь в самый низ, там будет Any Connect. Открывается приложение и запрашивает пароль. Вводишь и тыкаешь импорт.
И дальше уже по твоей схеме в настройках туннеля выбираешь сертификат.
Ну вот, вроде бы всё.
Настройка OpenConnect клиента для OpenWrt
Задача не тривиальная, поэтому вынесена в отдельную статью:
Блокировка пользователей путём отзыва сертификата
При необходимости заблокировать какого-то пользователя, сделать это можно с помощью отзыва его SSL сертификата.
Если вы только что подняли ocserv по этой инструкции, просто воспользуйтесь командой ocrevoke
внутри контейнера:
# отозвать сертификат пользователя ivan
docker exec -it openconnect ocrevoke ivan
# перечитать черный список пользователей
docker exec -it openconnect ocrevoke RELOAD
# удалить всех из черного списка
docker exec -it openconnect ocrevoke RESET
Где вместо ivan
подставьте имя пользователя, доступ по сертификату которого нужно заблокировать.
При следующем подключении пользователь получит ошибку:
Если же ваш сервер был поднят по этой инструкции некоторое время назад, придется выполнить немного ручной работы.
Сперва в конец конфига ./data/ocserv.conf
добавляем параметр:
crl = /etc/ocserv/certs/crl.pem
Затем выполняем команды:
cat ./data/certs/ivan-cert.pem >> ./data/certs/revoked.pem
docker exec -it openconnect certtool --generate-crl --load-ca-privkey /etc/ocserv/certs/ca-key.pem --load-ca-certificate /etc/ocserv/certs/ca-cert.pem --load-certificate /etc/ocserv/certs/revoked.pem --template /etc/ocserv/certs/crl.tmpl --outfile /etc/ocserv/certs/crl.pem
docker exec -it openconnect occtl reload
Где вместо ivan
в первой команде подставьте имя пользователя, доступ по сертификату которого нужно заблокировать.
Как вы догадались, ключевым тут является файл ./data/certs/revoked.pem
, куда добавляются публичные сертификаты пользователей <user_name>-cert.pem
.
Для очистки всего черного списка выполните:
docker exec -it openconnect certtool --generate-crl --load-ca-privkey /etc/ocserv/certs/ca-key.pem --load-ca-certificate /etc/ocserv/certs/ca-cert.pem --template /etc/ocserv/certs/crl.tmpl --outfile /etc/ocserv/certs/crl.pem
docker exec -it openconnect occtl reload
При отсутствии необходимости данного функционала, просто закомментируйте строку crl = /etc/ocserv/certs/crl.pem
в конфиге ocserv.
Авторизация по логину/паролю
В некоторых случаях может понадобиться включение возможности авторизации по логину и паролю. Например при настройке openconnect на роутерах Keenetic, который не поддерживает подключение на основе сертификата.
В файле конфигурации ocserv — /opt/openconnect/data/ocserv.conf
разрешаем авторизацию по логину/паролю, путем добавления такого параметра:
enable-auth = "plain[passwd=/etc/ocserv/ocpasswd]"
Стоит отметить, что при такой конфигурации авторизация по сертификату останется.
Затем перезапускаем сервер:
docker compose restart openconnect
Далее создаём нового пользователя:
docker exec -it openconnect ocpasswd exampleuser
И задаем ему пароль.
После чего можно подключаться с помощью пары логин/пароль.
Обязательно указывайте только длинные и сложные пароли.
Настройка двухфактороной аутентификации (2FA/TOTP) с помощью ocpasswd и PAM
Ocserv из коробки поддерживает двухфакторную аутентификацию двух типов: HOTP, TOTP. В моей конфигурации используется TOTP, как более распространенный вариант.
Настроить 2FA в ocserv можно для двух способов авторизации на сервере: для внутренних пользователей в файле ocpasswd
и внешних пользователей хоста с помощью модуля PAM.
Обратите, внимание, что в ocserv одновременно может использоваться только один вариант парольной аутентификации. Комментарий из оф. документации:
Note that authentication methods utilizing passwords cannot be combined (e.g., the plain, pam or radius methods).
Стоит отметить, что PAM аутентификация предоставляет более гибкий подход к настройке аутентификации пользователей на сервере. Именно с помощью PAM внутри контейнера реализован функционал отправки секретов и токенов TOTP по Email и в Telegram (об этом ниже).
Для активации 2FA внутри контейнера необходимо отредактировать файлы .env и ocserv.conf.
Правка .env
Добавить/изменить параметр:
OTP_ENABLE="true"
Правка ocserv.conf
Выбрать один из способов аутентификации, для которой будет применяться второй фактор:
- 2FA с помощью
ocpasswd
:
- 2FA с помощью
auth = "plain[passwd=/etc/ocserv/ocpasswd,otp=/etc/ocserv/secrets/users.oath]"
При работе такой аутентификации необходимо создать пользователя стандартным способом и задать ему пароль. Пример:
docker exec -it openconnect ocpasswd ivan
- 2FA с помощью PAM:
auth = "pam"
В случае PAM, вам также необходимо прокинуть файлы со списком локальных пользователей хостовой ОС в контейнер в файле docker-compose.yml
:
volumes:
- ./data:/etc/ocserv
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro
- /etc/shadow:/etc/shadow:ro
📝 Примечание
Обратите внимание, что при каждом добавлении нового пользователя в хостовой системе для синхронизации примонтированных файлов необходимо перезапускать контейнер.
Пример создания локального пользователя Linux без домашней директории и без возможности входа в сеанс оболочки ОС:
adduser --no-create-home --allow-bad-names --quiet --shell /bin/false --comment "Ocserv user" ivan@r4ven.me
Перезапуск сервиса
Теперь пересоздаем контейнер:
systemctl restart openconnect
Двухфакторная аутентификация активирована, осталось настроить ее для существующих пользователей. Для этого воспользуйтесь специальной командой/скриптом ocuser2fa
. Пример для пользователя ivan
:
docker exec -it openconnect ocuser2fa ivan
Команда выведет в консоль секрет, сгенерированный для указанного пользователя, а также этот секрет в формате base32 и в виде QR кода для сканирования-сохранения его в специальном приложении аутентификаторе на смартфоне, например: FreeOTP, Google authenticator, Yandex ключ и др.
Также для удобства QR код сохраняется в виде png файла в директории /opt/openconnect/data/secrets/otp_<user_id>.png
.
Чаще всего при сканировании QR проблем с генерацией корректных TOTP не возникает, но с добавлением секрета вручную есть нюансы. Вот правильные параметры TOTP, поддерживаемые ocserv при ручном добавлении секрета на примере приложения FreeOTP:
- Username — произвольное значение
- Company — произвольное значение
- Secret — секрет в кодировке base32
- Type — totp
- Digits — 6
- Algorithm — SHA1
- Interval — 30
Получать одноразовые пароли можно также в консоли Linux с помощью утилиты oathtool
:
apt install -y oathtool
oathtool --base32 --totp=SHA1 --time-step-size=30 --digits=6 <totp_base32_secret>
Замените <totp_base32_secret>
на сгенерированный командой ocuser2fa
секрет.
Каждый одноразовый токен доступен для применения в течении 20 «окон», т.е. 20 * 30 сек = 10 мин.
(Опиционально) Отправка TOTP секрета + QR код и одноразовых паролей по Email и в Telegram
В контейнере реализован функционал отправки секрета TOTP в виде строки base32 и QR кода в виде png файла на Email адрес/Telegram чат пользователя во время работы команды ocuser2fa
.
А также, при наличии соответствующей переменной, активируется возможность отправки по указанным каналам одноразовых паролей при каждом подключении к VPN серверу — работает только при использовании PAM авторизации, т.к. сам ocserv не умеет запускать кастомные скрипты конкретно в процессе аутентификации пользователя.
Отправка по Email
Отправка по почте осуществляется через внешний почтовый сервис с помощью консольного SMTP клиента — msmtp
. Для его работы необходимо задать соответствующие параметры через переменные среды в файле .env. Вот пример:
MSMTP_HOST="smtp.example.com"
MSMTP_PORT="465"
MSMTP_USER="email@example.com"
MSMTP_PASSWORD="supersecretpassword"
MSMTP_FROM="email@example.com"
# для отправки одноразовых паролей при каждой авторизации
OTP_SEND_BY_EMAIL="true"
При необходимости скорректируйте параметры msmtp в файле
/opt/openconnect/data/scrits/msmtprc
после пересоздания контейнера.
В качестве пароля рекомендуется использовать «пароль приложения», а не полноценный пароль от почтового аккаунта. Реквизиты SMTP серверов всех популярных провайдеров легко гуглятся.
Данные отправляются по Email, только если в качестве имени пользователя ocserv используется email адрес в стандартном формате: username@example.com
.
Отправка в Telegram
Отправка сообщений в телегу осуществляется путем отправки HTTP запросов с помощью curl
на api.telegram.org
. Для работы этой функции требуется:
1) токен телеграм бота (инструкция по получению из оф. доки или более понятно тут), который необходимо задать в файле .env
, пример:
TG_TOKEN="1234567890:QWERTYqwerty-QWERTY123_QwErTy123qWeRtY123"
# для отправки одноразовых паролей при каждой авторизации
OTP_SEND_BY_TELEGRAM="true"
2) пользователю, которому будут отправляться секрет с QR кодом и одноразовые токены необходимо написать вашему боту в чат любое сообщение в течение последних 24 часов (требуется единожды)
3) имя пользователя ocserv должно четко совпадать с nickname пользователя в Telegram
Перезапуск сервиса
Для применения изменений перезапускаем сервис:
systemctl restart openconnect
Проверка
Настраиваем 2FA для существующего пользователя с именем ввиде email адреса:
docker exec -it openconnect ocuser2fa ivan@r4ven.me
На почту должны приходить подобные письма:
Для пользователя с именем, совпадающим с именем телеграм:
docker exec -it openconnect ocuser2fa ivan
В чате с ботом будет подобное:
Защита от автоматизированного доступа — Camouflage
С версии ocserv >= 1.23 доступен дополнительный слой защиты от сканирования интернет ботами, которые могут в автоматическом режиме подобирать резкизиты для доступа к внутренней сети. Для активации защиты внесите следующие параметры в ваш ocserv.conf
:
# включение защиты от active probing
camouflage = true
# дополнительная часть url для подключения
camouflage_secret = "secretword"
# активация ложного окна авторизации, которое отклоняет любые попытки входа
#camouflage_realm = "My admin panel"
И перезапустите сервер:
systemctl restart openconnect
Теперь для подключения к серверу на клиенте необходимо указывать адрес с дополнительным сабурлом, указанного в конфиге. Пример:
https://vpn.r4ven.me:43443/?secretword
А все запросы к базовому урлу: https://vpn.r4ven.me:43443/
будут завершаться ошибкой клиента — 404. А если активен параметр camouflage_realm = "My admin panel"
, то запросы будут упираться в ложное окно авторизации.
Полезные команды systemctl/docker/docker-compose/ocserv
Небольшой список команд, который может вам пригодится при настройке и обслуживании OpenConnect сервера.
# управление сервисом openconnect с помощью systemd
systemctl stop openconnect
systemctl start openconnect
systemctl restart openconnect
# показывает список всех доступных образов docker
docker image ls
# показывает список всех контейнеров docker, включая работающие и остановленные
docker container ls -a
# удаляет все ненужные ресурсы docker, такие как неиспользуемые контейнеры, образы, сети и тома, без запроса подтверждения
docker system prune -af
# показывает статус и информацию о контейнерах, запущенных с помощью docker-compose
docker compose ps
# запускает контейнеры из файла docker-compose.yml в фоновом режиме
docker compose up -d
# останавливает и удаляет все контейнеры, сети, запущенные с помощью docker-compose
docker compose down
# отображает стандартный вывод контейнеров, запущенных с помощью docker-compose
docker compose logs -f
# перезапуск контейнера с именем "openconnect"
docker compose restart openconnect
# запускает интерактивную оболочку bash внутри контейнера "openconnect"
docker exec -it openconnect bash
# выводит справку по команде утилиты управления VPN сервером
docker exec -it openconnect occtl --help
# перечитывает файл конфигурации ocserv
docker exec -it openconnect occtl reload
# показывает статус ocserv
docker exec -it openconnect occtl show status
# показывает список пользователей ocserv
docker exec -it openconnect occtl show users
# показывает список активных сеансов ocserv
docker exec -it openconnect occtl show sessions all
# создает пользователя с id "ivan" и именем "Ivan Cherniy" для ocserv
docker exec -it openconnect ocuser ivan 'Ivan Cherniy'
Заключение
Фух! Создание данного материала заняло приличное количество времени и сил, но они было потрачены не зря. В процессе подготовки я узнал много нового и про работу сети в Linux, и множество нюансов bash при написании скриптов проекта и многое другое.
В будущем буду писать статьи по развертыванию различных персональных сервисов, и для доступа к ним будет использоваться VPN подключения на базе OpenConnect.
Если у вас возникли трудности с настройкой или остались вопросы, то смело оставляйте комментарии к данной статье или в нашем чате телеги: @r4ven_me_chat.
Также не забудьте подписаться на наш телеграм канал:@r4ven_me. Ссылки на все новые статьи появляются там в момент публикации.
Спасибо, что вместе со мной осилили данную статью. Успехов вам!
Используемые материалы
- Файлы проекта из статьи на моём GitHub
- Официальный репозиторий GitLab проекта ocserv (EN)
- Официальный репозиторий GitLab gui клинетов (EN)
- Пример файла ocserv.conf с описанием параметров (EN)
- Инструкция: Set Up OpenConnect VPN Server (ocserv) on Ubuntu 20.04 with Let’s Encrypt (EN)
- Инструкция: Ocserv Advanced (Split Tunneling, IPv6, Static IP, Per User Configs, Virtual Hosting) (EN)
- Инструкция: Setting up my OpenConnect Server (EN)
- Официальный мануал: GitHub (EN)
- Документация по certbot: User Guide — Certbot (EN)
- Документация по настройке 2FA (EN)
- Сайт мобильного приложения аутентификатора FreeOTP (EN)
Комментарии
📝 Примечание
Valentin:
camouflage поддерживает эта версия ?
Иван Чёрный:
Добрый день.
В статье устанавливается стабильная версия ovserv — 1.1, из репозитория Debian. Она не поддерживает указанный вами параметр.
Но по просьбе подписчиков, я вручную собрал отдельный образ с последней версий ocserv — 1.3.
Вы можете использовать ее, отредактировав параметр:
image: r4venme/openconnect:v1.3
Или же собрать его самостоятельно по отдельной статье:
Собираем OpenConnect (ocserv) версии 1.3 из исходников в Debian 12 + docker образ
📝 Примечание
Kirill Tarashev:
В статье есть упоминание про подключение к серверу с Keenetic… Так вот в последней текущей бете (а может и во всех), Keenetic не умеет сохранять адрес с портом… он его просто удаляет.. Ну и не коннектится, соответственно.
Имеет смысл добавить комментарий, что если планируется подсоединяться через эти роутеры, то порт в .env нужно ставить дефолтный 443 (который не нужно указывать в строке подключения)
Иван Чёрный:
Привет!
Полезная информация. Добавил в статью. Благодарю👍
📝 Примечание
Vladimir:
Добрый день.
А есть какие-то требования по настройке ufw и iptables?
Я второй день мучаюсь этими настройками.
Иван Чёрный:
Приветствую!
Пожалуйста, уточните, в чем именно проблема? ufw / iptables на хостовой системе или в контейнере?
Т.к. по моей инструкции контейнер с ocserv разворачивается с помощью docker, то в таком случае, при пробрасывании порта из контейнера в хостовую систему, docker самостоятельно добавляет нужные правила в iptables / ufw, в свою отдельную цепочку правил.
Вы можете в этом убедиться выполнив:
sudo iptables -L -n
Или
sudo iptables-save
Для более оперативной связи, рекомендую позадавать вопросы в нашем чате: @r4ven_me_chat. У нас там дружелюбное сообщество 🙂
📝 Примечание
survland:
У меня скорость на Download 0.70 а на Upload 27 мбит, как исправить скорость?
Иван Чёрный:
Зависит от большого числа факторов. Чаще всего дело не в ocserv, а в канале связи до вашего сервера.
Как можно проверить скорость Download|Upload в реальном времени с помощью ssh, чтобы определить проблема в VPN или нет.
Установите утилиту контроля пайплайна:
sudo apt update && sudo apt install pv
Затем отключите VPN и проверьте скорость download:
ssh root@123.45.67.89 dd if=/dev/zero bs=1M count=1024 | pv | cat > /dev/null
Затем upload:
dd if=/dev/zero bs=1M count=1024 | pv | ssh root@123.45.67.89 'cat > /dev/null'
Затем включите VPN и проверьте снова. Если без VPN скорость намного выше, значит дело в канале.
📝 Примечание
Mashrooms Peter:
При коннекте получаю такую ошибку,хотя ссл рукопожатие таки проходит.
Got inappropriate HTTP CONNECT response: HTTP/1.1 401 Cookie is not acceptable
Error establishing the CSTP channel
disconnect
траффик проксирую в контейнер через нжинкс,но пробовал и хапрокси.результат аналогичный.Не могу понять в чем дело
Иван Чёрный:
Покажите, как настроили nginx для проксирования.
Если подключаться напрямую, без nginx, все работает корректно?
Mashrooms Peter:
конфиг хапрокси
frontend https_frontend
bind haproxy:8443
mode tcp
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
acl host_vpn hdr(host) -i example.com
use_backend ocserv if { req_ssl_sni -i example.com }
backend ocserv
mode tcp
option ssl-hello-chk
server ocserv openconnect:9443 send-proxy-v2
слушаю 8443 в контейнере хапрокси,отправляю на 9443 ocerv
напрямую без прокси
| 198c | Failed to open HTTPS connection to example.com
| 198c | Authentication error; cannot obtain cookie
| 2d98 | Disconnected
с хапрокси вот такое. Смущает signer not found,но похоже это пол беды
Server certificate verify failed: signer not found
| 4088 | Connected to HTTPS on example.com with ciphersuite (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM)
| 4088 | Got HTTP response: HTTP/1.1 200 OK
| 4088 | Connection: Keep-Alive
| 4088 | Content-Type: text/xml
| 4088 | Content-Length: 189
| 4088 | X-Transcend-Version: 1
| 4088 | Set-Cookie: webvpncontext=AEG244K5UOM9lu9jQ4qa69GXypuR9ybC0YNRdqif4EI=; Secure; HttpOnly
| 4088 | Set-Cookie: webvpn=<elided>; Secure; HttpOnly
| 4088 | Set-Cookie: webvpnc=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; Secure; HttpOnly
| 4088 | Set-Cookie: webvpnc=bu:/&p:t&iu:1/&sh:F050A2A4D159D3086B9A5353CC5EDCD6C62F5515; path=/; Secure; HttpOnly
| 4088 | HTTP body length: (189)
| 4088 | XML POST enabled
| 4088 | SSL negotiation with example.com
| 4088 | Server certificate verify failed: signer not found
| 4088 | Connected to HTTPS on example.com with ciphersuite (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM)
| 4088 | TCP_MAXSEG 1440
| 4088 | Got inappropriate HTTP CONNECT response: HTTP/1.1 401 Cookie is not acceptable
| 4088 | Error establishing the CSTP channel
| 2d98 | Disconnected
Иван Чёрный:
Сообщения из вывода указывают на проблемы с сертификатами локального центр сертификации (CA), которые генерируются скриптом в контейнере при первом запуске.
В директории
/opt/openconnect/data/certs
Должны быть файлы
ca-key.pem
ca-cert.pem
Проверьте существуют ли эти файлы.
Если да, проверить файл сертификата можно такой командой:
openssl x509 -in /opt/openconnect/data/certs/ca-cert.pem -text -noout
Вывод должен начинаться примерно так:
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1 (0x1)
Signature Algorithm: sha256WithRSAEncryption
Issuer: O = vpn.r4ven.me, CN = My VPN
Validity
Not Before: May 23 13:19:12 2024 GMT
Not After : Dec 31 23:59:59 9999 GMT
Subject: O = vpn.r4ven.me, CN = My VPN
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (3072 bit)
Modulus:
00:f2:fe:18:f6:a2:29:34:5e:89:de:fb:b4:9c:2e:
Также стоит проверить сертификаты от cerbot, если вы используете подключение по доменному имени.
Рекомендую написать нам в чат телеги: https://t.me/r4ven_me_chat
А то тут можно долго перебирать варианты.
Иван Чёрный:
Решение пользователя из чата в телеграм:
В общем разобрался,заработало.дело было в прокидывании /dev/net/tun в тестовой среде
📝 Примечание
Татьяна Германова:
Добрый день.
А если порты 80 и 443 уже заняты, то как быть?
Иван Чёрный:
Добрый день.
Сообщите пожалуйста подробности.
В статье демонстрируется установка ocserv с использованием 43443 порта, а не 443.
Предположу, что вы хотите поднять ocserv на 443 порту, но при этом он уже используется другими сервисами. В таком случае выходом будет настройка обратного прокси сервера, типа nginx или haproxy.
Тема обратных прокси выходит за рамки данной статьи. Но наши подсписчики в чате телеграм уже обсуждали решение данного вопроса. При желании можете поискать ответ в истории сообщений или задать вопрос😌.
📝 Примечание
Денис Тутельян:
Здравствуйте. Подскажите, с чем связана эта ошибка? Происходит на стадии сборки образа.
ERROR: The Compose file ‘./docker-compose.yml’ is invalid because:
services.openconnect.ports is invalid: Invalid port «»43443″:443/tcp», should be [[remote_ip:]remote_port[-remote_port]:]port[/protocol]
networks.vpn.ipam.config value Additional properties are not allowed (‘gateway’ was unexpected)
services.openconnect.depends_on contains an invalid type, it should be an array
Иван Чёрный:
Добрый день.
Судя по всему ошибка в синтаксисе файла docker-compose.yml. Конкретно, “”43443″:443/tcp” кажется тут лишние кавычки.
Попробуйте просто указать — 43443:443/tcp
Также предположу, что лишние кавычки могут подтягиваться из значения переменной, ${SRV_PORT} если вы ее в docker-compose.yml обрамили дополнительными кавычками.
Денис Тутельян:
Синтаксис проверял, прочел, что это связано с новым docker-compose, в нем не поддерживается поле gateway. Образ собрался с командой docker compose, а не docker-compose
Иван Чёрный:
А какая у вас версия docker engine? Проверил у себя, на нескольких машинах, везде сетевой параметр gateway указан и контейнеры запускаются корректно.
По поводу docker-compose — раньше это был не официальный плагин от сторонних разработчиков. В последствии docker взяли его под свое крыло и с определенной версии docker compose стал встроенной командой.
Денис Тутельян:
Engine 27.4.1, а вот compose 1.2.5, так что ошибся, не новый compose, а наоборот слишком старый в debian 11
📝 Примечание
Kirill NS:
Доброго.
Пока не ставил, интересуюсь заранее. Такое подключение позволят получать доступ к ресурсам сети где этот linux сервер находится? В статье не нашёл явного описания.
Иван Чёрный:
Привет!
Сеть хоста по умолчанию доступна через бридж docker, который создаётся при запуске контейнера.
А вот для связи с клиентами из хоста нужно добавлять маршрут:
Условно:
ip route add 10.11.11.0/24 via 10.10.10.2
Где:
10.10.10.2 — адрес контейнера ocserv
10.11.11.0/24 — внутренняя сеть ocserv
Иван Чёрный:
Если нужно больше контроля, то для каждого клиента при его подключении выполняется bash скрипт /opt/openconnect/data/connect.sh
Ограничить доступ, например к сети 10.10.200.0/24 можно с помощью iptables так:
iptables -A FORWARD -s "${IP_REMOTE}"/32 -d 10.10.200.0/24 -j REJECT
Kirill NS:
Понятно, спасибо.
📝 Примечание
Родион Солошенко:
Аутентификация через LDAP (AD) не рассматривалась? Подскажите пожалуйста, где можно посмотреть настройку? Что бы не сильно отличалась от вашей конфигурации ocserv. Иначе, придётся всё переделывать заново.
Иван Чёрный:
Т.к. в LDAP у меня нет особой необходимости, поэтому пока не рассматривал. Но вопрос интересный, возможно добавлю функционал в будущем.
Для добавления нового функционала и сохранении конфигурации из статьи — вам придется пересобирать docker контейнер.
Подробно, как настраивается PAM авторизация, в т.ч. внутри контейнера расписал в одном из постов в телеге тут.
Авторизацию по LDAP в Linux можно реализовать через sssd и модуль PAM — pam_sss.so.
Нужно подробнее изучить вопрос. Пока не готов дать конкретных рекомендаций.
📝 Примечание
Егоров Иван:
Есть у кого рабочий, не могу запустить , в статье нет пояснений нашёл га хабре ))
[Unit]
Description=OpenConnect SSL VPN server
Documentation=man:ocserv(8)
After=network-online.target
[Service]
PrivateTmp=true
PIDFile=/run/ocserv.pid
Type=simple
ExecStart=/usr/sbin/ocserv —foreground —pid-file /run/ocserv.pid —config /etc/ocserv/ocserv.conf
ExecReload=/bin/kill -HUP $MAINPID
[Install]
WantedBy=multi-user.target
Иван Чёрный:
Все есть на сайте.
Пишем bash скрипт для подключения к OpenConnect VPN серверу
Егоров Иван:
service unit occlient.service: вот что там есть , как я понял это клиент )) А нужен сервер.
Иван Чёрный:
Юнит, что вы привели в своем сообщении не подошел?
📝 Примечание
Илья Сердобинцев:
Добрый день!
Все установил, спасибо большое!
Появилась сложность в установке разрешения авторизации по логину и паролю для роутера Keenetic. При вводе команды: enable-auth = «plain[passwd=/etc/ocserv/ocpasswd]»
Пишет следующее: /opt/openconnect# enable-auth = «plain[passwd=/etc/ocserv/ocpasswd]»
-bash: enable-auth: command not found
Подскажите, пожалуйста, что делаю не так?
Иван Чёрный:
Добрый день.
Это не команда, а параметр ocserv, который необходимо добавть в файл конфигурации:
/opt/openconnect/data/ocserv.conf
Илья Сердобинцев:
Добрый день!
Расскажите, пожалуйста, как добавить этот параметр?
Иван Чёрный:
echo 'enable-auth = "plain[passwd=/etc/ocserv/ocpasswd]"' >> /opt/openconnect/data/ocserv.conf
Иван Чёрный:
После чего перечитать конфигурацию ocserv:
docker exec -it openconnect occtl reload
📝 Примечание
Владимир Яруничев:
Привет. У меня перестал работать OpenConnect
У меня использовалась старая версия OpenConnect, устанавливал через ансибл роль https://github.com/hos7ein/ansible-ocserv
Решил попробовать Ваш образ версии 1.3, включил камуфляж, но всё без результатно, по wi-fi у меня даже до сервера не может достучаться, по мобильному интернету соединение устанавливется, но интернета на телефоне нету. Работает ли У Вас openconnect?
Иван Чёрный:
Добрый день.
Да, у меня openconnect работает. Но уже не первый раз слышу о сбоях.
К сожалению вынужден отключить комментарии на сайте. Для продолжения диалога пожалуйста переходите в чат телеграм: @r4ven_me_chat.