Programming, electronics, lifestyle
И так вначале я использовал широко распространенное решение от kylemanna.
Оно представляет из себя Docker образ на базе Alpine состоящий из:
OpenVPN собственно в качестве VPN сервераopenvpn-auth-pam, google-authenticator, pamtester - дополнительные модули для авторизации в OpenVPNeasy-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 сервер и сделать так, чтобы текущие клиенты подключились к нему, однако для этого нужно ещё получить доступ к домену, что маловероятно.