cert-manager: автопродление сертификатов для IKEv2
Классическая боль сертификатной IKEv2: сертификат сервера или клиента незаметно доходит до конца срока действия, туннель перестаёт подниматься, а разбираться приходится в проде, когда команда уже не может подключиться. Если вы вручную гоняли ipsec pki --issue и следили за датами в календаре — есть способ снять это с себя: cert-manager, контроллер Kubernetes для PKI-автоматизации, отлично справляется с выпуском и продлением TLS/EAP-сертификатов для strongSwan, даже если сам VPN-сервер к Kubernetes отношения не имеет.
Содержание
- Почему ручное продление сертификатов IKEv2 — плохая идея
- Что такое cert-manager и как он автоматизирует PKI
- Разворачиваем k3s и cert-manager рядом со strongSwan
- Издатель и серверный сертификат для strongSwan
- Клиентские сертификаты и EAP-TLS без ручной генерации
- Синхронизация секретов в ipsec.d и релоад без обрыва туннелей
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Почему ручное продление сертификатов IKEv2 — плохая идея
При сертификатной аутентификации IKEv2 (rightauth=pubkey, как описано в статье про multi-user сертификаты на strongSwan) у вас в игре как минимум два типа сертификатов с разными сроками жизни: серверный, который живёт годами, и клиентские — их обычно выпускают на 1-2 года, чтобы ограничить окно, в котором скомпрометированный .p12 остаётся валидным.
Проблема не в том, что продление технически сложное — команда ipsec pki --issue та же, что и при первом выпуске. Проблема в том, что это ручной процесс, который никто не помнит выполнить вовремя. Типичный сценарий: сертификат истекает в пятницу вечером, IKE_AUTH начинает падать с certificate has expired в /var/log/syslog, а системы, которая предупредила бы заранее, нет. Для одного личного VPN это раздражает, для команды из 10-20 человек с индивидуальными сертификатами — регулярный источник тикетов «не могу подключиться».
Второй слой боли — серверный сертификат для встроенных клиентов iOS/macOS/Windows. Если он самоподписанный (свой CA), каждое устройство требует ручной установки корневого сертификата в доверенные. Публичный сертификат от Let's Encrypt снимает эту проблему, но у него самого срок жизни 90 дней — без автоматизации продлевать его руками приходится ещё чаще, чем самоподписанный.
Что такое cert-manager и как он автоматизирует PKI
cert-manager — open-source контроллер для Kubernetes, который управляет жизненным циклом X.509-сертификатов: выпускает их через ACME (Let's Encrypt, ZeroSSL), через собственный CA (self-signed или существующий intermediate) или через внешние Vault/Venafi-интеграции, следит за сроком действия и переиздаёт сертификат заранее, без вашего участия.
Три ключевых объекта, которые вам понадобятся:
- Issuer / ClusterIssuer — описывает, откуда брать подпись: ACME-аккаунт Let's Encrypt или self-signed CA, который сам cert-manager сгенерирует и будет хранить как
Secret. - Certificate — декларативный запрос «мне нужен сертификат с таким CN/SAN, живущий N дней, продлевай за M дней до истечения» (поле
renewBefore). cert-manager сam следит за таймером и переиздаёт без вмешательства. - Secret — итоговый сертификат и приватный ключ, которые контроллер кладёт в Kubernetes Secret и обновляет на месте при каждом продлении.
Здесь ключевая идея всей статьи: cert-manager ничего не знает про strongSwan и IKEv2 — ему всё равно, что происходит с секретом дальше. Ваша задача — научить внешний скрипт забирать обновлённый Secret и раскладывать его в /etc/ipsec.d/, а дальше страховать себя от рассинхронизации. Это честно проще, чем городить собственный демон продления на cron с самописной логикой дат.
Арендуйте сервер под свои задачи!
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверРазворачиваем k3s и cert-manager рядом со strongSwan
Ставить полноценный кластер ради PKI избыточно, но лёгкий однонодовый k3s на том же VPS (или на соседнем недорогом инстансе) — рабочий вариант: он не мешает strongSwan на хостовой сети и не требует отдельной инфраструктуры.
curl -sfL https://get.k3s.io | sh -
sudo k3s kubectl get nodes
Далее ставим cert-manager через Helm (самый предсказуемый способ обновлять его в дальнейшем):
curl -fsSL https://get.helm.sh/helm-v3.15.0-linux-amd64.tar.gz | tar xz
sudo mv linux-amd64/helm /usr/local/bin/
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager --create-namespace \
--set crds.enabled=true
Версию Helm-чарта проверяйте на момент установки — jetstack регулярно выпускает новые релизы. Точный расход памяти и CPU под k3s + cert-manager зависит от того, что ещё крутится на сервере — не полагайтесь на чужие цифры, замерьте kubectl top pod -n cert-manager у себя.
Проверка, что контроллер поднялся:
sudo k3s kubectl get pods -n cert-manager
# cert-manager, cert-manager-cainjector, cert-manager-webhook — Running
Издатель и серверный сертификат для strongSwan
Для серверного сертификата логичнее всего связка с Let's Encrypt через ACME — тогда встроенным клиентам iOS/macOS/Windows не нужно устанавливать ваш корневой CA вручную, они и так доверяют публичным CA. DNS-01 challenge удобнее HTTP-01, потому что не требует держать 80-й порт открытым на VPN-сервере:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-vpn
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-vpn-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: vpn-server-cert
namespace: default
spec:
secretName: vpn-server-cert-tls
duration: 2160h # 90 дней — стандарт Let's Encrypt
renewBefore: 720h # продлевать за 30 дней до истечения
dnsNames:
- vpn.example.com
issuerRef:
name: letsencrypt-vpn
kind: ClusterIssuer
usages:
- server auth
Если внешний CA по каким-то причинам не подходит (внутренний VPN без публичного DNS, требование держать весь PKI в закрытом контуре) — замените ClusterIssuer на self-signed CA, как в сертификатах для IKEv2/SSTP через Windows CA, только выпуск и хранение корневого ключа берёт на себя cert-manager, а не вы вручную:
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: selfsigned-bootstrap
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: vpn-root-ca
spec:
isCA: true
commonName: "Company VPN Root CA"
secretName: vpn-root-ca-tls
duration: 87600h # 10 лет
renewBefore: 720h
issuerRef:
name: selfsigned-bootstrap
kind: Issuer
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: vpn-ca-issuer
spec:
ca:
secretName: vpn-root-ca-tls
Клиентские сертификаты и EAP-TLS без ручной генерации
Каждому пользователю — отдельный Certificate с CN на его имя и коротким сроком жизни, чтобы ограничить ущерб от утечки .p12. С EAP-TLS сертификат клиента используется вместо пароля в EAP-обмене, но выпускается той же самой self-signed CA-цепочкой:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: client-ivan
namespace: default
spec:
secretName: client-ivan-tls
commonName: "ivan@company.vpn"
duration: 8760h # 1 год
renewBefore: 720h
usages:
- client auth
issuerRef:
name: vpn-ca-issuer
kind: ClusterIssuer
Для команды из десятка человек проще держать список пользователей в отдельном YAML и генерировать Certificate-объекты через kustomize или простой шаблон в CI, чем плодить их руками — так вы получаете единообразие CN и сроков без риска опечатки.
Честный нюанс: cert-manager сам не собирает .p12-контейнер для мобильных клиентов — он отдаёт PEM-сертификат и ключ отдельными файлами в Secret. Упаковку в .p12 (openssl pkcs12 -export) всё равно придётся делать отдельным шагом в скрипте синхронизации — автоматизация снимает выпуск и продление, а не всю ручную работу с профилями.
Синхронизация секретов в ipsec.d и релоад без обрыва туннелей
Это связующее звено, которого в самом cert-manager нет и не будет — он про Kubernetes, а strongSwan про хостовую файловую систему. Пишем скрипт, который забирает актуальные сертификаты из Secret'ов и раскладывает их туда, где их ждёт strongSwan:
#!/usr/bin/env bash
set -euo pipefail
KC="sudo k3s kubectl"
IPSEC_DIR=/etc/ipsec.d
sync_cert() {
local secret=$1 name=$2 dest_cert=$3 dest_key=$4
$KC get secret "$secret" -o jsonpath='{.data.tls\.crt}' | base64 -d > "$dest_cert.new"
$KC get secret "$secret" -o jsonpath='{.data.tls\.key}' | base64 -d > "$dest_key.new"
if ! cmp -s "$dest_cert.new" "$dest_cert" 2>/dev/null; then
mv "$dest_cert.new" "$dest_cert"
mv "$dest_key.new" "$dest_key"
chmod 600 "$dest_key"
echo "$(date -Iseconds) обновлён $name"
RELOAD_NEEDED=1
else
rm -f "$dest_cert.new" "$dest_key.new"
fi
}
RELOAD_NEEDED=0
sync_cert vpn-server-cert-tls server "$IPSEC_DIR/certs/server-cert.pem" "$IPSEC_DIR/private/server-key.pem"
sync_cert vpn-root-ca-tls ca "$IPSEC_DIR/cacerts/ca-cert.pem" /dev/null
if [ "$RELOAD_NEEDED" = "1" ]; then
ipsec rereadcerts
ipsec reload
fi
ipsec rereadcerts && ipsec reload не рвёт существующие SA — strongSwan подхватывает новые сертификаты для новых согласований, а уже поднятые туннели продолжают жить до своего штатного rekey. Это принципиально отличается от systemctl restart strongswan, который оборвёт всех сразу — не используйте restart в скрипте синхронизации, только reload.
Скрипт вешаем на systemd timer, а не на cron — удобнее логировать через journalctl и проще выставлять RandomizedDelaySec, чтобы не бить в API k3s ровно по расписанию с других скриптов:
# /etc/systemd/system/cert-sync.timer
[Unit]
Description=Sync cert-manager secrets into ipsec.d
[Timer]
OnCalendar=*-*-* */6:00:00
RandomizedDelaySec=300
Persistent=true
[Install]
WantedBy=timers.target
# /etc/systemd/system/cert-sync.service
[Unit]
Description=cert-manager -> strongSwan sync
[Service]
Type=oneshot
ExecStart=/usr/local/bin/cert-sync.sh
sudo systemctl enable --now cert-sync.timer
sudo systemctl list-timers cert-sync.timer
Проверять статус самих сертификатов удобно одной командой — она показывает, сколько до истечения и не залипло ли продление на стороне ACME:
sudo k3s kubectl get certificate -A
# NAME READY SECRET AGE
# vpn-server-cert True vpn-server-cert-tls 45d
# client-ivan True client-ivan-tls 12d
Если нужен алерт заранее, а не постфактум через get certificate, добавьте отдельную проверку истечения на VPS-стороне — тот же принцип, что описан в статье про мониторинг доступности VPN-сервера через Uptime Kuma: периодический openssl x509 -enddate -noout -in server-cert.pem с алертом, если до истечения меньше недели. Это резервная страховка на случай, если сам cert-manager не смог продлить (например, ACME rate limit или недоступен DNS-провайдер для challenge) — тогда вы узнаете за неделю, а не в момент отказа туннелей.
Арендуйте сервер под свои задачи!
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Обязательно ли ставить полноценный Kubernetes ради одного VPN-сервера?
Нет, достаточно k3s или k0s в однонодовом режиме — способ запустить контроллер cert-manager с минимальными накладными расходами, а не «настоящий» продакшн-кластер. Если у вас уже есть k8s для других задач, проще развернуть Issuer и Certificate там и добавить сетевой доступ до него со стороны VPN-сервера.
Что если k3s/API недоступен в момент, когда strongSwan должен перечитать сертификаты?
Скрипт синхронизации ничего не сломает — он просто не обновит файлы в этом прогоне и попробует на следующем тике таймера. strongSwan продолжает работать с текущими, ещё не истёкшими сертификатами, а renewBefore в 30 дней даёт запас на несколько неудачных попыток подряд.
Чем это лучше обычного certbot с cron-хуком?
Для одного серверного сертификата на публичном домене разница небольшая — certbot или acme.sh справятся сами без Kubernetes. cert-manager выигрывает, когда параллельно есть флот клиентских сертификатов с разными сроками и CN — декларативные YAML-объекты проще держать под git, чем множить bash-скрипты с датами.
Можно ли использовать cert-manager для EAP-MSCHAPv2, а не только сертификатной схемы?
Нет — EAP-MSCHAPv2 работает на паролях, а не на X.509, там cert-manager применить не к чему. Автоматизация закрывает только сценарии с rightauth=pubkey и EAP-TLS, где реально фигурируют сертификаты.
Что будет с активными подключениями в момент продления сертификата?
Ничего — ipsec reload не трогает установленные SA, только конфигурацию для новых согласований. Клиент почувствует смену только при следующем полном пересогласовании по rekeytime, и то лишь если старый сертификат к этому моменту уже истёк.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →