
Всем привет! Nixys на связи!
С нами (в основном, со мной) произошёл очередной интересный кейс.
Нашему клиенту (условимся называть его Заказчик) была необходима готовая среда разработки «под ключ». Лёгкая в администрировании, масштабируемая, с минимумом ответственности и с максимумом отказоустойчивости. В то же время она должна быть защищена от несанкционированного доступа, чтобы наработки компании не попали в чужие руки. На тот момент у клиента было несколько железных серверов, на обслуживание которых уходило много человеческих ресурсов. Если не поддерживать и не развивать такую среду, то со временем можно столкнуться с ощутимыми проблемами.Таким образом, планировалось создать продакшн-контур в отечественном облаке.
Минусы, которые мы видим:
-
По окончании настройки весь доступ к внешнему миру (и интернету, конечно) будет отключён, что сразу убьёт лёгкость администрирования, ведь все обновления будут идти через согласование с ИБ.
-
Закрытые контуры требуют больших инвестиций в разработку и дальнейшую поддержку, так как все компоненты должны быть реализованы внутри компании.
-
Компания может столкнуться с проблемами при интеграции новых технологий или продуктов от других поставщиков. Это может привести к проблемам при масштабировании и расширении бизнеса.
Задача звучала тривиально: есть максимально закрытая и безопасная инфраструктура за UserGate. Заказчик хочет, чтобы программисты создавали классные сервисы в Yandex Cloud.
Из вводных (насколько мне известно) было:
-
настроить соединение между облаком и сетью Заказчика;
-
завязать всю авторизацию на LDAP.
Через множество брейнштормов и созвонов мы пришли к такому решению:
-
Создаём ipsec-тунель между Yandex Cloud и UserGate по протоколу IKEv2.
-
Где можем, авторизуемся через LDAP и Keycloak (вот, кстати, статья от моего коллеги, который уже прошёл эту битву).
-
Используя 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 в инфраструктуре Заказчика).
Сейчас покажу, как мы это сделали.
-
Устанавливаем StrongSwan.
-
Конфигурим /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) на обоих хостах должно быть одинаково. Подробности можно найти в официальной документации.
-
Настраиваем /etc/ipsec.secrets:
#source destination 84.0.0.1 202.0.0.3 : PSK "тут какой-то ключ в base64"
Тут важна только строчка с адресами и ключом, source/destination указаны для того, чтобы не запутаться.
-
Настраиваем сеть как описано в этой статье.
-
Добавляем правила в /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
Такие правила необходимо создать для каждой подсети, но важно указать корректный интерфейс и адреса.
Дополнительная информация с примерами есть на официальном сайте.
-
Создаём статические маршруты до сетей назначения.
После делаем аналогичные операции на втором сервере, только меняем местами адреса сетей и хостов.

Настраиваем инфраструктуру
После того как мы успешно протестировали передачу файлов размером в несколько гигабайт между удалёнными хостами через туннель и измерили скорость передачи, мы приступили ко второму этапу — настройке инфраструктуры. Это был самый сложный этап работы.
Порядок настройки, который я рекомендую, не является обязательным. Однако, если бы я настраивал всё именно в таком порядке с самого начала, проект был бы завершён гораздо быстрее. Большинство решений были приняты в первую очередь для того, чтобы упростить жизнь администраторам, которые будут всё это поддерживать.
-
VPN. «Зачем? Ведь уже есть ipsec… тоннель», — скажут самые внимательные читатели. А тут всё просто. Нам и каким-то внешним подрядчикам так будет быстрее ходить (мне, конечно, сказали, что решение временное…. Но мы то с вами знаем, что нет ничего более долговечного чем временное ;-). Выбор пал на Pritunl VPN. Плюсы очевидны: простота первичной настройки, начало работы сразу после установки, простое добавление сущностей, крайне легкий способ создания новых пользователей.
-
Сервер 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. Выделю основные моменты, которые я использовал:
-
создаём файл
/etc/nginx/snippets/self-signed.confв котором указываем расположение сертификатов (которые мы выпускаем с помощью Vault):
ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt; ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
-
Затем описываем конфигурацию 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 предлагает нам авторизоваться.
-
Кластер всеми горячо любимого Kubernetes (далее k8s). Решение — Managed от Yandex Cloud.
Во-первых: бюджеты позволяют;
во-вторых: снимаем с себя и с коллег из будущего бОльшую часть ответственности за здоровье кластера;
в-третьих: наша команда запилила умопомрачительный Terraform-модуль, с помощью которого кластер разворачивается быстро. Все необходимые опции описаны, да и модуль поддерживается и развивается. -
Кластер Vault. Бэкендом будет выступать Consul. Почему же именно Vault? Так всем нашим сервисам необходимы сертификаты! Соответственно, следующим шагом будет деплой и настройка Cert Manager в связке с Vault.
-
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
-
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/
Добавить комментарий