Programming, electronics, lifestyle
И так вначале я использовал широко распространенное решение от kylemanna.
Оно представляет из себя Docker образ на базе Alpine состоящий из:
OpenVPN
собственно в качестве VPN сервераopenvpn-auth-pam
, google-authenticator
, pamtester
- дополнительные модули для авторизации в OpenVPN
easy-rsa
как инструмент для создания и работы центра сертификации (CA)ovpn_initpki
, ovpn_genconfig
, ovpn_copy_server_files
, ovpn_run
, ovpn_status
)ovpn_getclient
, ovpn_getclient_all
, ovpn_listclients
, ovpn_revokeclient
, ovpn_otp_user
)Тут нужно сделать важное замечание, что я вначале не понимал как работает CA, сертификаты пользователей, OpenVPN и easy-rsa вместе. И, наверное, многим будет полезно тоже в этом разобраться.
Начнем с базового, что такое сертификат
, центр сертификации
и для чего они нужны.
Любой сертификат нужен для того, чтобы удостоверить что-то. Например, освоенную на курсах дисциплину, факт заключения брака или факт получения образования. Однако ключевой вопрос, в том почему мы доверяем сертификату? И на самом деле мы доверяем не ему, а доверяем органу, который выдает сертификат (конечно учитывая, что сертификат не подделан).
Давайте разберем на примере ВУЗов. Скажем мы знаем топовые университеты с хорошей репутацией. И если на работу приходит выпускник с дипломом такого ВУЗа, то мы понимаем, что это хороший специалист, потому что мы доверяем конкретному университету.
В этом случае центром сертификации является университет, а сертификатом - диплом выпускника.
На этом принципе и построен алгоритм работы авторизации в OpenVPN
. То-есть по сути OpenVPN сервер может ничего не знать о клиенте, тк даже не он “регистрирует” его, а доверяет он потому, что сертификат клиента подписан тем же CA (Certificate Authority), что и сертификат самого сервера. На мой взгляд это красивое решение.
OpenVPN использует ряд сертификатов и ключей:
ca.crt
– сертификат центра сертификации
, которым у нас все подписано.server.crt
– сертификат самого сервера, с помощью него клиенты могут удостовериться, что подключаются именно к тому серверу.server.key
– ключ от сертификата выше, с помощью которого сервер и подписывает сообщения.dh.pem
– специальный ключ для осуществления алгоритма Diffie Hellman, для создания нужен файл openssl-easyrsa.cnf
генерируемый вместе с CA.ta.key
– дополнительный симметричный ключ шифрования, который есть и на клиенте и на сервере (HMAC firewall
). Создается с помощью OpenVPN, для защиты от DoS атак и флуда в UDP порт сервера.crl.pem
– база данных отозванных сертификатов. Тк в общем случае любой сертификат подписанный CA, будет валиден на OpenVPN, для отключения доступа используется база данных отозванных сертификатов
. После операции revoke
в CA, в этот файл вносятся изменения (зависит от CA). Может быть задан с помощью параметра crl-verify
.В решении kylemanna в одном образе находится и центр сертификации
, и сам VPN-сервер. Что подразумевает выпуск клиентских сертификатов на той же машине, что честно говоря убивает всю красоту и главное нарушает безопасность. Тк ключ от сертификата центра сертификации (crt.key
) хранится вместе со всеми другими файлами, собственно как и файлы сертификатов и ключей клиентов.
Для работы с центром сертификации используется утилита easy-rsa
. Данная утилита является надстройкой над openssl
, которая упрощает операции с центром сертификации. Она использует директорию /etc/openvpn/pki
для хранения всей информации по работе с центром сертификации: все сертификаты и их ключи. Как мы уже поняли, быть этого на самом сервере не должно.
В механизме подписей, есть возможность отделить создание клиентов от их подписи центром сертификации, делается это с помощью CSR-запросов. Поэтому при необходимости
easy-rsa
можно оставить в этом образе для создания и управления клиентами. В данном случае я не вижу необходимости в этом в данном решении.
Отдельную путаницу в /etc/openvpn
создают директории:
/etc/openvpn/clients
– здесь хранятся сертификаты, ключи клиентов и собранные воедино .ovpn
файлы необходимые для подключения. Это директория не используются сервером./etc/openvpn/ccd
– это специальный каталог, в котором хранятся файлы совпадающие по имени с именами клиентов, в каждом из файлов хранятся отдельные конфигурации и опции необходимые для работы клиента, например опция ifconfig-push 192.168.1.10 255.255.255.0
, которая выдает конкретному клиенту IP адрес 192.168.1.10
. Эта директория используется сервером. Задается с помощью параметра client-config-dir
.Собственно основная идея заключается в том, чтобы вынести центр сертификации и обвязки для разворачивания и конфигурирования сервера из образа.
Получился следующий Dockerfile:
FROM alpine:latest
# Unused: openvpn-auth-pam google-authenticator pamtester easy-rsa
RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing/" >> /etc/apk/repositories && \
apk add --update openvpn iptables bash && \
rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/*
Я убрал все лишнее. И оставил только необходимое:
openvpn.conf
;ccd
;entrypoint.sh
;certs
- папка с сертификатом и ключом сервера, сертификатом CA, ta.key, dh.key и листом отозванных сертификатов.Отдельно пару слов стоит сказать про файл openvpn.conf. Тк я отказался от ovpn_genconfig
я взял за основу базовый файл предлагаемый OpenVPN и внес туда только необходимые мне изменения, из плюсов теперь весь файл хорошо комментирован.
И еще отдельного внимания заслуживает файл entrypoint.sh
. Дело в том, что в нём делается пару важных вещей:
Создается /dev/net/tun
устройство.
Прописываются дополнительные маршруты и настройки iptables
. Здесь все достаточно специфично и зависит от конкретного применения OpenVPN. У меня получился следующий файл:
#!/bin/bash
set -e
mkdir -p /dev/net
if [ ! -c /dev/net/tun ]; then
mknod /dev/net/tun c 10 200
fi
########################## ROUTES ##########################
iptables -C POSTROUTING -t nat -s 192.168.255.0/24 -o eth0 -j MASQUERADE || {
iptables -A POSTROUTING -t nat -s 192.168.255.0/24 -o eth0 -j MASQUERADE
}
iptables -A FORWARD -i tun0 -o tun0 -j ACCEPT
iptables -A POSTROUTING -o tun0 -t nat -j MASQUERADE
# Route to the docker-compose network
iptables -A FORWARD -i tun0 -o eth0 -j ACCEPT
iptables -A FORWARD -i eth0 -o tun0 -j ACCEPT
ip addr add 192.168.1.0/24 dev eth0
###################### END / ROUTES ########################
echo "Running '${@}'"
exec ${@}
Все вышеперечисленный файлы я храню в отдельной директории которую монтирую в контейнер. Для деплоя я использую Docker Compose, так я могу определить ряд сервисов помимо Openvpn и задать адресацию локальной сети через функционал Docker Compose.
version: "2.4"
services:
openvpn:
build:
dockerfile: Dockerfile
context: ./openvpn/docker
restart: unless-stopped
entrypoint: /etc/openvpn/entrypoint.sh
command: openvpn --config /etc/openvpn/openvpn.conf
cap_add:
- NET_ADMIN
volumes:
- ./openvpn/bin:/usr/lib/openvpn/bin:ro
- ./openvpn/etc:/etc/openvpn:ro
- openvpn-logs:/tmp:rw
ports:
- 33896:33896/udp
logging:
driver: "json-file"
options:
max-size: "100m"
networks:
intranet:
ipv4_address: 192.168.250.4
cpus: 0.5
mem_limit: 100m
memswap_limit: 100m
volumes:
openvpn-logs:
networks:
intranet:
driver: bridge
ipam:
config:
- subnet: 192.168.250.0/24
Все файлы сертификатов и ключей генерируются и задаются один раз извне, из другого с центром сертификации. О чём – ниже.
Для этого я создал отдельный Docker образ:
FROM alpine:latest
RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing/" >> /etc/apk/repositories && \
apk add --update bash easy-rsa && \
ln -s /usr/share/easy-rsa/easyrsa /usr/local/bin && \
rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/*
И Makefile для работы с этим образом:
default:
docker run --rm -it -v $$(pwd)/mnt:/mnt -w /mnt -e EASYRSA=/mnt -e EASYRSA_PKI=/mnt/pki -e EASYRSA_EXT_DIR=/usr/share/easy-rsa/x509-types urpylka/easy-rsa:local
build:
docker build ./docker --tag urpylka/easy-rsa:local
clean:
docker image rm urpylka/easy-rsa:local
По сути три команды: make build
– собирает образ, make
– включает окружение внутри образа, ну и make clean
– удаляет образ.
make
– монтирует текущую директорию в/mnt
контейнера.
Дальнейшие команды исполняются внутри этого окружения.
Инициализация pki
директории для easy-rsa
:
easyrsa init-pki
Далее создаем файл vars
, в моём случае со следующим содержанием:
set_var EASYRSA_REQ_COUNTRY "RU"
set_var EASYRSA_REQ_PROVINCE "Samara Region"
set_var EASYRSA_REQ_CITY "Samara"
set_var EASYRSA_REQ_ORG "urpylka՚s projects!"
set_var EASYRSA_REQ_EMAIL "[email protected]"
set_var EASYRSA_REQ_OU "myworld"
set_var EASYRSA_REQ_CN "urpylka՚s projects!"
set_var EASYRSA_BATCH "yes"
set_var EASYRSA_ALGO "ec"
set_var EASYRSA_DIGEST "sha512"
Я использовал относительно новый алгоритм подписи основанный на эллиптических кривых
ec
вместоrsa
.
Генерируем CRL файл:
easyrsa gen-crl
В начале нам нужно собрать и зайти в образ с easy-rsa
:
make build
make
Затем нужно задать CLIENTNAME
.
Если вы используете DNS, я советую создавать названия клиентов, соответствующие их доменным именам, это позволит избежать путаницы.
CLIENTNAME="client.example.com"
Это специальное название клиента используемое в сертификате, а также используется OpenVPN для задания отдельных правил через директорию /etc/openvpn/ccd
.
# /etc/openvpn/ccd/CLIENTNAME
ifconfig-push 192.168.255.10 255.255.255.0
Затем генерируем сертификат для клиента:
easyrsa build-client-full ${CLIENTNAME} nopass
Далее сохраним сгенерированный сертификат в файл с расширением ovpn
.
VPN_SERVER="vpn.example.com 1194"
cat > ${CLIENTNAME}.ovpn << EOF
client
nobind
dev tun
remote-cert-tls server
# Forward all traffic through VPN
redirect-gateway def1
# Address of the VPN server
remote ${VPN_SERVER} udp
<key>
$(cat $EASYRSA_PKI/private/${CLIENTNAME}.key)
</key>
<cert>
$(openssl x509 -in $EASYRSA_PKI/issued/${CLIENTNAME}.crt)
</cert>
<ca>
$(cat $EASYRSA_PKI/ca.crt)
</ca>
key-direction 1
<tls-auth>
$(cat $EASYRSA_PKI/ta.key)
</tls-auth>
EOF
Для отзыва клиентского сертификата необходимо вызвать revoke
. Затем скопировать файл crl.pem
с сервера центра сертификации на OpenVPN сервер.
easyrsa revoke <CLIENTNAME>
Подробнее openvpn.net.
В конце нужно заметить, что я пока не перенес функционал средств подготовки конфигурации сервера в образ easy-rsa
. Это нужно прежде всего для удобства.
Касаемо того, что у нас получилось, теперь на OpenVPN отсутствует приватный ключ, и даже если кто-то получит доступ к OpenVPN серверу, он не сможет создать своих клиентов. Максимум, что у него получится – поднять такой же OpenVPN сервер и сделать так, чтобы текущие клиенты подключились к нему, однако для этого нужно ещё получить доступ к домену, что маловероятно.