Интеграция защищённого контура в Yandex Cloud: делимся опытом

от автора

Всем привет! Nixys на связи!

С нами (в основном, со мной) произошёл очередной интересный кейс.

Нашему клиенту (условимся называть его Заказчик) была необходима готовая среда разработки «под ключ». Лёгкая в администрировании, масштабируемая, с минимумом ответственности и с максимумом отказоустойчивости. В то же время она должна быть защищена от несанкционированного доступа, чтобы наработки компании не попали в чужие руки. На тот момент у клиента было несколько железных серверов, на обслуживание которых уходило много человеческих ресурсов. Если не поддерживать и не развивать такую среду, то со временем можно столкнуться с ощутимыми проблемами.Таким образом, планировалось создать продакшн-контур в отечественном облаке.

Минусы, которые мы видим:

  • По окончании настройки весь доступ к внешнему миру (и интернету, конечно) будет отключён, что сразу убьёт лёгкость администрирования, ведь все обновления будут идти через согласование с ИБ.

  • Закрытые контуры требуют больших инвестиций в разработку и дальнейшую поддержку, так как все компоненты должны быть реализованы внутри компании.

  • Компания может столкнуться с проблемами при интеграции новых технологий или продуктов от других поставщиков. Это может привести к проблемам при масштабировании и расширении бизнеса.

Задача звучала тривиально: есть максимально закрытая и безопасная инфраструктура за UserGate. Заказчик хочет, чтобы программисты создавали классные сервисы в Yandex Cloud.

Из вводных (насколько мне известно) было:

  • настроить соединение между облаком и сетью Заказчика;

  • завязать всю авторизацию на LDAP.

Через множество брейнштормов и созвонов мы пришли к такому решению:

  1. Создаём ipsec-тунель между Yandex Cloud и UserGate по протоколу IKEv2.

  2. Где можем, авторизуемся через LDAP и Keycloak (вот, кстати, статья от моего коллеги, который уже прошёл эту битву).

  3. Используя Terraform-модули из репо nxs-marketplace-terraform, по щелчку пальцев разворачиваем инфраструктуру, жмём руки и на доброй ноте расходимся.

Сама инфраструктура будет состоять из:

  • StrongSwan

  • Kubernetes

  • GitLab

  • Vault by HashiCorp

  • Keycloak

  • и много чего ещё. Выше перечислил основное.

Настраиваем StrongSwan site-to-site IKEv2 ipsec tunnel

Для начала разберёмся, что такое IPSEC-туннель. Это виртуальный канал связи, который создается между двумя устройствами для безопасной передачи данных. Этот туннель шифрует данные перед их отправкой и дешифрует их после получения. Таким образом, даже если данные будут перехвачены в процессе передачи, злоумышленник не сможет их прочитать или изменить. Если сравнивать, например, с OpenVPN, который работает на уровне пользователя, то IPSEC, работающий на уровне ядра системы, позволяет повысить скорость и производительность.

Мы выбрали протокол IKEv2 (т. к. IKEv1, созданный в далёком 1990 году, просто морально устарел), а в качестве метода шифрования — PSK (Pre Shared Key — когда один и тот же ключ шифрования используется всеми участниками соединения). На самом деле перед нами стоял выбор: использовать PSK или сертификаты. Решили остановиться на PSK по нескольким причинам. Во-первых, это удобно: в случае компрометации быстрее и проще сгенерировать и передать новый ключ, нежели бодаться с сертификатами. Иногда время, потраченное на восстановление канала связи, может стоить очень дорого. Во-вторых, контур будет закрытый, знать про него будут только определённые люди.

Перейдём к делу.

Сперва было необходимо построить туннель между Yandex Cloud и закрытым контуром Заказчика. Всё просто. Что же могло пойти не так?

Да как обычно — баг в последней (на момент мая 2024 года) версии прошивки в UserGate. При создании IPSEC-туннеля UserGate создаёт множественные соединения, которые разрываются по тайм-ауту. Баг подтвержден разработчиком, версия ПО отправлена на доработку. А процесс, увы, уже был запущен. Пришлось переделывать. Так что будьте осторожны.

Дальше сделали связку StrongSwan (виртуалка в Яндекс клауд) — StrongSwan (виртуалка за UserGate в инфраструктуре Заказчика).

Сейчас покажу, как мы это сделали.

  1. Устанавливаем StrongSwan.

  2. Конфигурим /etc/ipsec.conf:

config setup         charondebug="all"          uniqueids=yes         strictcrlpolicy=no  conn connection         authby=secret         left=10.12.0.21         leftid=84.0.0.1         leftsubnet=10.12.0.0/24,10.13.0.0/24         right=202.0.0.3         rightid=%any         rightsubnet=10.1.1.0/24,10.1.4.0/24         ike=aes256-sha256-modp2048,aes256-sha256-modp1024,aes256-sha1-modp2048,aes256-sha1-modp1024!         esp=aes256-sha256,aes256-sha1!         keyingtries=3         ikelifetime=1h         lifetime=8h         dpdaction=hold         auto=start         type=tunnel         keyexchange=ikev2  

Если коротко, то left — это локальный (local) сервер, а right — удалённый (remote).

Всё, что связано с шифрованием (ike, esp) на обоих хостах должно быть одинаково. Подробности можно найти в официальной документации.

  1. Настраиваем /etc/ipsec.secrets:

#source      destination 84.0.0.1     202.0.0.3  : PSK "тут какой-то ключ в base64"   

Тут важна только строчка с адресами и ключом, source/destination указаны для того, чтобы не запутаться.

  1. Настраиваем сеть как описано в этой статье.

  2. Добавляем правила в /etc/ufw/before.rules:

# разрешаем ssh -A ufw-before-input -p tcp -m tcp --dport 22 -j ACCEPT -A ufw-before-output -p tcp -m tcp --sport 22 -j ACCEPT  

Включаем перенаправление трафика ESP:

-A ufw-before-forward --match policy --pol ipsec --dir in --proto esp -s 10.12.0.0/24 -j ACCEPT -A ufw-before-forward --match policy --pol ipsec --dir out --proto esp -d 10.12.0.0/24 -j ACCEPT  # разрешаем forwarding  -A ufw-before-forward -s 10.1.1.0/24 -d 10.12.0.0/24 -i eth0 -m policy --dir in --pol ipsec --reqid 1 --proto esp -j ACCEPT -A ufw-before-forward -s 10.12.0.0/24 -d 10.1.1.0/24 -o eth0 -m policy --dir out --pol ipsec --reqid 1 --proto esp -j ACCEPT  -A ufw-before-forward -s 10.1.4.0/24 -d 10.12.0.0/24 -i eth0 -m policy --dir in --pol ipsec --reqid 1 --proto esp -j ACCEPT -A ufw-before-forward -s 10.12.0.0/24 -d 10.1.4.0/24 -o eth0 -m policy --dir out --pol ipsec --reqid 1 --proto esp -j ACCEPT  

Такие правила необходимо создать для каждой подсети, но важно указать корректный интерфейс и адреса.

Дополнительная информация с примерами есть на официальном сайте.

  1. Создаём статические маршруты до сетей назначения.

После делаем аналогичные операции на втором сервере, только меняем местами адреса сетей и хостов.

Настраиваем инфраструктуру

После того как мы успешно протестировали передачу файлов размером в несколько гигабайт между удалёнными хостами через туннель и измерили скорость передачи, мы приступили ко второму этапу — настройке инфраструктуры. Это был самый сложный этап работы.

Порядок настройки, который я рекомендую, не является обязательным. Однако, если бы я настраивал всё именно в таком порядке с самого начала, проект был бы завершён гораздо быстрее. Большинство решений были приняты в первую очередь для того, чтобы упростить жизнь администраторам, которые будут всё это поддерживать.

  1. VPN. «Зачем? Ведь уже есть ipsec… тоннель», — скажут самые внимательные читатели. А тут всё просто. Нам и каким-то внешним подрядчикам так будет быстрее ходить (мне, конечно, сказали, что решение временное…. Но мы то с вами знаем, что нет ничего более долговечного чем временное ;-). Выбор пал на Pritunl VPN. Плюсы очевидны: простота первичной настройки, начало работы сразу после установки, простое добавление сущностей, крайне легкий способ создания новых пользователей.

  2. Сервер GitLab в Docker Compose файле. Почему? Удобство сопровождения! А именно:

  • меньше проблем с обновлениями;

  • всё находится в одном месте, что снижает порог вхождения для следующих инженеров, которые будут работать над проектом;

  • всё можно разместить за внешним Nginx, но это скорее опционально (контур-то у нас закрытый).

После того, как Gitlab будет развернут и настроен, каждый следующий шаг описываем с помощью IaC. Мы настраиваем CI/CD для всего, что можем, чтобы клиент одним нажатием кнопки мог создавать волшебство или вершить историю (нужное подчеркнуть). Делаем это всё максимально читабельным и удобным. И, конечно же, в каждый проект размещаем Readme с подробным описанием сервиса.

Немного про настройку GitLab

Ничего особенного не было придумано, docker-compose.yaml был взят из официальной документации. Вот как он выглядит:

version: '3' services:  gitlab:    image: gitlab/gitlab-ce:<тут какая-то версия образа>    logging:      options:        max-size: "1024m"    restart: always    hostname: 'gitlab.example.com'    container_name: 'gitlab'    environment:      GITLAB_OMNIBUS_CONFIG: |        external_url 'http://gitlab.example.com'        nginx['redirect_http_to_https'] = false        nginx['custom_gitlab_server_config'] = 'proxy_request_buffering off;'        letsencrypt['enable'] = false        prometheus['enable'] = false        alertmanager['enable'] = false        grafana['enable'] = false        registry['enable'] = false        gitlab_rails['gitlab_shell_ssh_port'] = 2222    ports:    - '8080:80'    - '8443:443'    - '2222:22'    volumes:    - './volumes/etc/gitlab:/etc/gitlab'    - './volumes/log/gitlab:/var/log/gitlab'    - './volumes/data/gitlab:/var/opt/gitlab'  

Следующий пункт — настройка Nginx. Подготовка почвы для сертификатов — на базе статей из мной горячо любимого Digital Ocean. Выделю основные моменты, которые я использовал:

  1. создаём файл /etc/nginx/snippets/self-signed.confв котором указываем расположение сертификатов (которые мы выпускаем с помощью Vault):

ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt; ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key; 
  1. Затем описываем конфигурацию SSL в /etc/nginx/snippets/ssl-params.conf

ssl_protocols TLSv1.3; ssl_prefer_server_ciphers on; ssl_dhparam /etc/nginx/dhparam.pem;  ssl_ciphers EECDH+AESGCM:EDH+AESGCM; ssl_ecdh_curve secp384r1; ssl_session_timeout  10m; ssl_session_cache shared:SSL:10m; ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on; resolver 77.88.8.8 77.88.8.1 valid=300s; resolver_timeout 5s; preload"; add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; 

И создаём DHparam-файл с помощью команды:
sudo openssl dhparam -out /etc/nginx/dhparam.pem 4096

Для чего это нужно? Этот протокол, позволяет двум и более сторонам получить общий секретный ключ через незащищённый от прослушивания канал связи. Полученный ключ используется для шифрования дальнейшего обмена с помощью алгоритмов симметричного шифрования. Он не защищён от уязвимости «Man-in-the-middle» и используется преимущественно как дополнительная мера защиты. Момент: генерация ключа может занимать рандомное количество времени, от нескольких секунд до десятков минут, так что если у вас подзавис процесс генерации, налейте горячего напитка, откиньтесь в кресле и посмотрите видео с котиками или почитайте что-нибудь полезное.

Далее сохраняем конфигурацию NGINX с указанными includ’ами:

server {         listen 80;         server_name gitlab.example.com;         location / {                 return 301 https://$host$request_uri;         } } server {         listen 443 ssl http2;         include snippets/self-signed.conf;         include snippets/ssl-params.conf;         server_name gitlab.example.com;         client_max_body_size 3000m;         access_log /var/log/nginx/git.access.log;         error_log /var/log/nginx/git.error.log;         location / {                 proxy_pass         http://127.0.0.1:8080;                 proxy_redirect     off;                 add_header    X-Frame-Options     "SAMEORIGIN";                   proxy_set_header    Host                $http_host;                 proxy_set_header    X-Real-IP           $remote_addr;                 proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;                 proxy_set_header    X-Forwarded-Proto   $scheme;         } } 

Затем выполняем следующие команды:
sudo nginx -s reload

Скорее всего, вы увидите что-то вроде:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful 

Это значит, что все окей. Теперь — самое время запустить nginx и проверить, что ранее запущенный GitLab предлагает нам авторизоваться.

  1. Кластер всеми горячо любимого Kubernetes (далее k8s). Решение — Managed от Yandex Cloud.
    Во-первых: бюджеты позволяют;
    во-вторых: снимаем с себя и с коллег из будущего бОльшую часть ответственности за здоровье кластера;
    в-третьих: наша команда запилила умопомрачительный Terraform-модуль, с помощью которого кластер разворачивается быстро. Все необходимые опции описаны, да и модуль поддерживается и развивается.

  2. Кластер Vault. Бэкендом будет выступать Consul. Почему же именно Vault? Так всем нашим сервисам необходимы сертификаты! Соответственно, следующим шагом будет деплой и настройка Cert Manager в связке с Vault.

  3. Cert Manager. Запускаем в кластере Kubernetes. Для того, чтобы быстро и просто получать сертификаты из Vault, настраиваем Cluster issuer. Для наших целей манифест будет выглядеть вот так:

apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata:  name: vault-issuer spec:  vault:    auth:      kubernetes:        mountPath: /v1/auth/kubernetes        role: clusterissuer        secretRef:          key: token          name: issuer-token-lmzpj    path: pki/sign/example    server: http://vault.example.com 

Это немного отличается от того, что написано в инструкции и, соответственно, нам нужны небольшие корректировки.

  • имя роли указываете то, которое вам необходимо. Как видите, у меня не самая богатая фантазия, поэтому “clusterissuer”

  • path у меня “pki/sign/example” (это расположение ваших сертификатов в Vault).

Ну и для того, чтобы наши Ingress стали защищёнными, добавляем в манифесты аннотацию с именем cluster issuer, пример ниже:

apiVersion: networking.k8s.io/v1 kind: Ingress metadata:  name: example-name  namespace: example  labels:    app: example  annotations:    cert-manager.io/cluster-issuer: vault-issuer    meta.helm.sh/release-name: example-name    meta.helm.sh/release-namespace: example spec:  ingressClassName: nginx  rules:    - host: example.example.com      http:        paths:          - path: /            pathType: ImplementationSpecific            backend:              service:                name: example                port:                  number: 8080 

После завершения истории с сертификатами, делаем все сервисы безопасными. Добавляем во все ингрессы аннотацию:

 annotations:    cert-manager.io/cluster-issuer: vault-issuer 
  1. Keycloak. Как я писал выше, мы планировали всё сделать на базе связки Keycloak + LDAP, но наши планы изменились: как оказалось, вместо LDAP уже настроили ADFS. Без проблем, подумали мы и начали работу с тем, что уже есть.

Нам нужна была единая точка авторизации в AD через Keycloak ⬌ ADFS.
В итоге стали настраивать SSO на базе SAML 2.0.

  • SSO. Упрощаем людям жизнь: один раз авторизовался и весь день свободен. Пока жива сессия, вы можете заходить в любые сервисы, которые добавлены в Keycloak. Как это работает: пользователи заводят в Active Directory, создают имя пользователя и пароль и всё, с помощью этих учётных данных пользователь будет авторизован в системе на время жизни сессии.

  • SAML. Если грубо, то это стандартный язык разметки, который используется для обмена информацией о безопасности между различными системами. Он позволяет передавать аутентификационные данные (например, имя пользователя и пароль) между сервисами без необходимости повторного ввода этих данных.

После настройки Keycloak мы настраиваем авторизацию всем сервисам, которые были развёрнуты ранее:

GitLab
Vault
Grafana
Kibana

Всё, что касается Keycloak, расписывать не будем, ибо статья превратится в галерею. Со стороны Keycloak настраивается identity provider согласно официальной инструкции. А вот и она.

После настраиваются Клиенты, каждый под свой сервис, кроме Kibana. Для Kibana используем oauth2-proxy. Инфомацию по его настройке и конфигурации можно найти в этой статье.

Остальное настраиваем через SAML согласно официальной документации.

Вуаля! Клиент получил полностью закрытый контур для разработки. Я получил крутой опыт. Вы получили статью. В итоге все счастливы.

Спасибо, что прочитали! Надеюсь, вам было полезно.


ссылка на оригинал статьи https://habr.com/ru/articles/830694/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *