MAATRIX / Блог / cert-manager: автопродление сертификатов для IKEv2

cert-manager: автопродление сертификатов для IKEv2

MAATRIX

Классическая боль сертификатной IKEv2: сертификат сервера или клиента незаметно доходит до конца срока действия, туннель перестаёт подниматься, а разбираться приходится в проде, когда команда уже не может подключиться. Если вы вручную гоняли ipsec pki --issue и следили за датами в календаре — есть способ снять это с себя: cert-manager, контроллер Kubernetes для PKI-автоматизации, отлично справляется с выпуском и продлением TLS/EAP-сертификатов для strongSwan, даже если сам VPN-сервер к Kubernetes отношения не имеет.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество 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 ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →