Хмельной Девопс
3.89K subscribers
12 photos
1 file
67 links
Будничный хаос и мрак при поддержке ИТ систем.
Канал об эксплуатации ИТ систем от действующего DevOps

Для фидбека и вопросов - @vasiliyozerov
Download Telegram
Если стоит писать такие мануальчики - то поставьте большой палец вверх этому сообщению. Если нафиг не надо такое делать - то ничего не ставьте 🙂 Всем хорошего дня!
Что-то в последнее время я стал часто сталкиваться с Kerberos. Все эти мутные слова в конфигурации - SASL/GSSAPI. Что за фигня? Никогда такого не было и вот опять! Но я признаюсь, что во время своей молодости я честно продолбал этот момент и в керберос глубоко не углублялся, хотя конечно приходилось настраивать домен контроллеры, где это все активно используется.

Но не страшно! Давайте вместе восполним мой пробел. Это будет вводная заметка, поскольку после теории мы будем настраивать интеграции различного софта с kerberos (первым пойдет настройка ssh, но доберемся и до кафки!).

Итак, kerberos - это network authentication protocol. Он используется для безопасной аутентификации клиента и сервера в открытых сетях. Он был разработан в далекие 80-ые. Это важный момент, который откладывает отпечаток на схему его работы.

Но хватит пустой информации, давайте посмотрим схему аутентификации с помощью kerberos (картинка отсюда - https://medium.com/identity-beyond-borders/kerberos-explained-3bc2ddb7b0eb).

Итак, какие основные участники процесса у нас имеются:

1. Client - наш клиентский компьютер, который хочет пройти аутентификацию на сервере (Server).
2. Server - сервер, который предоставляет какой-то сервис клиенту - например gitlab, nginx, kafka - что угодно.
3. KDC - Key Distribution Center - центральный узел Kerberos, который состоит из двух компонентов:
1. Authentication Server - сервер аутентификации.
2. Ticket Granting Server (TGT) - сервер выдачи тикетов пользователю.

Теперь давайте разберем как же клиент может пройти аутентификацию на Server с помощью kerberos:

1. Клиент обращается к Server и согласует с ним метод аутентификации. На самом деле этот шаг очень важен, поскольку раскрывает для нас два новых термина - SASL и GSSAPI. Что такое SASL?
SASL - Simple Authentication and Security Layer - это Framework, который позволяет разработчикам быстро внедрять различные механизмы аутентификации в свое приложение. Он описывает как клиент и сервер согласуют механизм аутентификации.
GSSAPI - Generic Security Services Application Program Interface - так же framework, который позволяет производить аутентификацию клиентов различными методами.

Итак, как бы мог выглядеть диалог нашего клиента с сервером, если оба используют SASL и GSSAPI? Примерно вот так:
1. Клиент → Серверу: Привет! Я использую SASL, как я могу пройти аутентификацию?
2. Сервер → Клиенту: Привет! Я тоже использую SASL, ты можешь пройти аутентификацию через GSSAPI, CRAM-MD5, PLAIN.
3. Клиент → Серверу: Ок, я выбираю GSSAPI.
4. Сервер → Клиенту: Отлично! Через GSSAPI я поддерживаю Kerberos и NTLM и SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism).
5. Клиент → Серверу: Отлично! Я выбираю Kerberos.
6. Клиент → Серверу: Данные по Kerberos аутентификации (**тикет Kerberos**).

То есть резюмируя - sasl и gssapi - просто фреймворки, которые позволяют клиенту и серверу договориться об используемом методе аутентификации. В нашем примере клиент выбрал аутентификацию через Kerberos.

2. После согласования механизма аутентификации клиент должен получить тикет Kerberos, который отправит серверу на шаге “**f”** из предыдущего пункта. Для этого он должен обратиться к AS (Authentication Server), который является одним из компонентов KDC (Key Distribution Center). Клиент не отправляет свой логин и пароль на AS, а всего лишь запрашивает специальный TGT тикет и отправляет свое имя на AS. AS проверяет что такой пользователь существует в базе, генерирует TGT тикет и шифрует его паролем клиента и отправляет обратно.
Клиент с помощью своего пароля расшифровывает сообщение от AS. Если пароль был введен не верно, то расшифровать сообщение не получится. После расшифровки клиент получает тикет для тикетов - TGT. Это специалный тикет, который можно обменивать на тикеты для сервисов.
3. Клиент обращается к TGS (Ticket Granting Server) со своим TGT тикетом, полученным на предыдущем этапе и запрашивает тикет для сервиса Server. TGS убеждается что TGT пользователя валиден и создает тикет для сервиса Server, который шифруется ключом Server (то есть пользователь не сможет его расшифровать).

4. Клиент передает серверу тикет для него. Server с помощью своего ключа расшифровывает тикет и тем самым убеждается, что тикет был выдан KDC, поскольку секретный ключ сервера есть только у самого сервера и у KDC. После проверки тикета сервис разрешает пользователю доступ.

Примерно вот так и работает Kerberos. Естественно я пытался описать весь процесс верхнеуровнево и опустил множество деталей (и возможно где-то накосячил). Но основные моменты я постарался подсветить:

1. Клиент получает TGT тикет от AS (Authentication Server). При этом пароль пользователя не передается на сервер.
2. После получения TGT клиент меняет его на тикет для нужного сервера / сервиса через TGS (Ticket Granting Server). При этом TGS шифрует тикет для сервиса секретным ключом этого сервиса.
3. Клиент отправляет тикет сервису / серверу, который расшифровывает его своим секретным ключом. Отсюда следует вывод, что на сервере вам необходимо как-то указать его секретный ключ, который вы создали в KDC.
Отлично, с теорией разобрались, в следующей заметке будем настраивать SSH!
Возвращаемся к Kerberos!

В прошлый раз мы обсудили теорию, а сейчас попробуем поднять свой собственный KDC. В качестве реализации можно использовать MIT Kerberos или Heimdal. Я буду приводить примеры на базе MIT Kerberos, но если где-то в статьях вы увидите Heimdal больших проблем возникнуть не должно - поскольку вы уже знаете механизм работы протокола. Также вы уже можете понять как windows active directory аутентифицирует клиентов в своих сервисах.

Итак, какой кейс мы будем делать? Давайте попробуем реализовать вход по ssh через kerberos тикеты.

У нас будет три сервера - kdc.krb.kis.im - на нем будет располагаться наш KDC, включающий в себя AS (authentication server) и TGS (ticket granting server). Второй хост - ssh.krb.kis.im - на нем запустим sshd с аутентификацией через kerberos. Ну и наш клиент - client.krb.kis.im, на котором мы будем получать kerberos тикеты и проходить аутентификацию на ssh.krb.kis.im.

Помимо этого домен Kerberos у нас будет KIS.IM - все пользователи и сервисы будут создаваться в этом домене.

В этот раз я решил не писать заметку в самое телеге, закинуть в виде pdf, а то телега бьет сообщения и получается не очень красиво. Приятного чтения!
Containerd - runtime для запуска контейнеров, который используется, например docker’ом. Также containerd предоставляет Container Runtime Interface, который использует Kubelet для запуска контейнеров. Часто встречается ситуация, когда на хосте установлен только containerd и было бы неплохо иметь возможность посмотреть что же запущено внутри и как оно работает.

Вообще, containerd представляет 3 утилиты для собственного управления:

- ctr - утилита входит в поставку containerd и позволяет получать отдебажить containerd
- netdctl - утилита с человеческим лицом для управления containerd - [https://github.com/containerd/nerdctl](https://github.com/containerd/nerdctl)
- crictl - утилита для дебага CRI совместимых runtime - [https://github.com/kubernetes-sigs/cri-tools/blob/master/docs/crictl.md](https://github.com/kubernetes-sigs/cri-tools/blob/master/docs/crictl.md)

Мы же сегодня немного посмотрим на ctr:

1. У containerd есть namespaces, по которым можно разделять образы, запущенные контейнеры и так далее, вывести список namespaces можно так:


# ctr ns list
NAME LABELS
default
k8s.io


Как видно, kubelet создает свой собственный namespace k8s.io, в котором запускаются все контейнеры.

2. Мы можем посмотреть на список образов контейнеров в том или ином namespace:


# ctr -n k8s.io images ls | head -2
REF TYPE DIGEST SIZE PLATFORMS LABELS
docker.io/library/alpine:latest application/vnd.docker.distribution.manifest.list.v2+json sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4 3.2 MiB linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x io.cri-containerd.image=managed


3. Так же мы можем скачать нужный нам образ:


# ctr -n default image pull docker.io/library/postgres:14
docker.io/library/postgres:14: resolved |++++++++++++++++++++++++++++++++++++++|
index-sha256:cfd6203fc331bdf87ddf8c0ae91d9a6a6eacd5f2037c8178b644f1bbc369fafe:

... skipped ...
elapsed: 5.7 s total: 101.6 (17.8 MiB/s)


Это часто бывает нужно для проверки возможности скачивания образов из приватных репозиториев - проверить что containerd принимает самоподписанный сертификат и так далее. К слову, аутентификацию в приватном registry можно настроить на уровне containerd и тогда не придется пихать всюду image pull secrets 🙂

4. Помимо этого вы можете подмонтировать образ контейнера в какую-нибудь директорию без запуска:


# ctr image mount docker.io/library/postgres:14 /mnt/
sha256:e242dad2cf72e07da948e090eb0f4cfd2a5e2820336ffc952afef4500a7ad117
/mnt/

[root@gis-s-app01 admin]# ls -la /mnt/
total 92
drwxr-xr-x. 1 root root 4096 Nov 30 21:10 .
dr-xr-xr-x. 18 root root 4096 Nov 27 21:06 ..
drwxr-xr-x. 2 root root 4096 Nov 14 03:00 bin
... skipped ...


5. Теперь самое главное! Контейнеры и задачи. Containerd разделяет эти две сущности. Суть в чем - контейнер (container) - это объект, который описывает окружение, что должно быть запущено и так далее. А вот задача (task) - это непосредственно процесс, который запускается в контейнере. Давайте посмотрим какие контейнеры у нас есть:
# ctr -n k8s.io container ls
CONTAINER IMAGE RUNTIME
093dd176a51cd2aa69ad7a052130dd8f73bc76a638bf3e4ee3a4b62e613e9e90 keycloak:latest io.containerd.runc.v2



6. А теперь взглянем какие задачи запущены в этом контейнере:


# ctr -n k8s.io tasks ps 093dd176a51cd2aa69ad7a052130dd8f73bc76a638bf3e4ee3a4b62e613e9e90
PID INFO
51028 -



Из вывода видно, что в данном контейнере у нас запущен процесс с PID = 51028 ну и дальше можно посмотреть что это за процесс.

7. Так же можно вывести список всех задач:


# ctr -n k8s.io tasks list
TASK PID STATUS
b6d79f43dde30d226af1c7b7bcf59188eed7661c0e6fd094a7c3d25d9ac0cf9b 50975 RUNNING
a59661db743f5a6deae2dd2292b0085853f94adc58f4a9d07d76ca9ba1ae433f 46053 RUNNING
f0840a9b8d70889a957cb082d4cf752cebef555d6d66da458944fdcd39013d9e 48139 RUNNING



Надо понимать - что у вас может быть такое, что контейнер есть, а процесса для него нет - это значит что он просто остановлен. Или самоубился 🙂

Ну и напоследок важная информация по поводу sandbox_image. Я думаю вы замечали, что kubernetes запускает какой-то непонятный pause контейнер, который крутится внутри пода:


# ctr -n k8s.io container ls | grep pause
330dd58e20efbeff2c2e2c5a688a4ac26ba8ca1c07f8c0e4806f602b2dafe94a k8s.gcr.io/pause:3.6 io.containerd.runc.v2



Pause контейнер выполняет важную задачу - он держит сетевой namespace для пода в случае если все ваши контейнеры внутри него умрут. Он запускается первым, чтобы containerd создал сетевой namespace, к которому впоследствии будут подключаться контейнеры из пода. То есть когда вы стартуете любой под в kubernetes (даже с одним контейнером), kubelet через CRI дергает containerd и запускает RunPodSandbox, который запускает sandbox image и настраивает сеть с помощью CNI плагинов. Если все контейнеры в поде умрут, то pause останется и соответственно сетевой namespace будет сохранен.

В общем когда вы разворачиваете kubernetes внутри приватной сети без доступа в интернет, то вам потребуется указать параметр sandbox_image в containerd config.toml, который будет смотреть на приватный registry:


# containerd config default | grep sandbox
sandbox_image = "k8s.gcr.io/pause:3.2"
Собственно на этом все - всем хорошей ночи! 🙂
Вообще очень странно, что я ничего не писал про wireguard до сегодняшнего дня, поскольку это решение одно из лучших для быстрой организации защищенных vpn туннелей (по моему, чисто субъективному мнению).

В двух словах. Wireguard работает поверх udp и заворачивает внутрь зашифрованные ip пакеты. В отличие от IPSEC, wireguard не устанавливает соединение, поэтому перемещение клиентов выглядит очень просто - нет никаких переподключений - просто отправляем udp датаграмму на сервер и все работает. Наконец-то никаких проблем с фаерволами и прочей фигней (как было с IPSec).

С точки зрения безопасности у wireguard все тоже хорошо. Поскольку wireguard’у не требуется поддерживать всякое старое, то в него не включали устаревшие методы шифрования. А исходники wireguard прошли аудит. В общем про криптографию можно почитать здесь - https://books.google.com/books?id=UKJfDwAAQBAJ&pg=PA3&redir_esc=y#v=onepage&q&f=false.

И по скорости wireguard опережает тот же openvpn, поскольку реализован на уровне ядра. Хотя справедливости ради стоит отметить, что в последнем релизе openvpn разработчики также добавили модуль ядра для повышения производительности.

Итак, если вы хотите настроить wireguard сервер на ubuntu, то нет ничего проще:


# Устанавливаем wireguard
apt-get install wireguard

# Генерируем приватный и публичный ключ для нашего сервера
cd /etc/wireguard && wg genkey | tee privatekey | wg pubkey > publickey

# Создаем конфигурацию wireguard
cat > wg0.conf <<EOF
# Наш интерфейс на котором слушаем входящие пакетики
[Interface]
# Адрес, который будет назначен нашему интерфейсу
Address = 10.8.0.1/24
# Сохраняем конфигурацию, если вносим изменения через wg утилиту
SaveConfig = true
# Слушаем на 51820 порту
ListenPort = 51820
# Наш приватный ключ - берем из ранее сгенерированного файла privatekey
PrivateKey = <private key>
EOF

# Добавляем wireguard в автозагрузка и поднимаем интерфейс
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0

# Проверяем статус интерфейса
wg show
interface: wg0
public key: <server public key>
private key: (hidden)
listening port: 51820


Для подключения клиента, к примеру на маке, устанавливаем wireguard тулзу из appstore. После запуска и создания туннеля, клиент сам сгенерирует свой приватный ключ и отобразит его публичную часть - это потребуется нам в будущем - когда мы будем добавлять клиента на сервере. А пока составляем конфигурацию для клиента:

jsx
# Интерфейс на клиенте
[Interface]
# Приватный ключ, который сгенерировала утилитка wireguard. Если хотите - можете сами сгенерировать ключи с помощью wg.
PrivateKey = <private key client>
# Наш адрес, который будет настроен на интерфейсе
Address = 10.8.0.5/24

[Peer]
# Публичный ключ нашего сервера из файла publickey
PublicKey = <public key>
# AllowedIPs решает две задачи:
# 1. Ограничивает доступ из туннеля только для апйпишников, указанных здесь - то есть если из туннеля пойдет трафик из подсети 10.9.0.0/24, то wireguard его отбросит.
# 2. Wireguard добавляет маршруты до этой сети через туннель - то есть если вы захотите обратиться к узлу 10.8.0.127, то система отправит пакеты в данный туннель. Через запятую можно указывать несколько сеток. А если указать 0.0.0.0/0, то весь трафик пойдет через защищенный канал.
#
AllowedIPs = 10.8.0.0/24
# Адрес и порт нашего wireguard сервера
Endpoint = 169.254.1.1:51820
# Как часто посылать keepalive пакеты. Поскольку wireguard работает по udp, то есть клиент находится за NAT, то через 30 секунд (зависит от настроек PAT / NAT), маршрутизатор оборвет сессию и сервер не сможет обратиться к клиенту, так что данные keepalive'ы нужны, чтобы поддерживать запись в таблице трансляции на маршрутизаторе.
PersistentKeepalive = 15


Отлично! Практически все готово. Теперь осталось добавить нашего клиента на сервер. Для этого на сервере выполняем:
jsx
# Добавляем клиента, указывая его публичный ключ и какие сети / айпишники находятся за ним. Логика такая же - все сети, указанные в allowed-ips будут маршрутизироваться сервером в данный туннель
wg set wg0 peer <client public key> allowed-ips 10.8.0.5/32


Теперь можно подключаться и проверять соединение!

jsx
wg show
interface: wg0
public key: <server public key>
private key: (hidden)
listening port: 51820

peer: <client public key>
endpoint: 169.254.99.99:28280
allowed ips: 10.8.0.5/32
latest handshake: 2 seconds ago
transfer: 180 B received, 92 B sent


Вообще для управления всеми этими конфигами можно использовать какой-нибудь web ui, например [https://www.firezone.dev/](https://www.firezone.dev/). Но вообще лучше погуглите - наверняка найдете что-нибудь интересное. Тем более тот же pritunl позволяет генерировать wireguard профили.

Теперь немного минусов:

1. К сожалению, вы не можете указать что-то типа disallowed-ips и исключить из маршрутизации какой-то айпишник. Чтобы это сделать необходимо добавить в allowed-ips все остальные, за исключением нужного. Для этого ребята даже написали allowed-ips калькулятор - https://www.procustodibus.com/blog/2021/03/wireguard-allowedips-calculator/. Можете пользоваться 🙂
2. Поскольку у wireguard нет сессий, то организация каких-либо аудит логов становится немного затруднительна - по факту нельзя сказать когда пользователь подключился - можно только сказать когда он начал отправлять данные. А так если для вашей системы безопасности требуются аудит логи, то можно посмотреть на https://www.procustodibus.com/blog/2021/03/wireguard-logs/, или на гошечке - https://github.com/nikaro/wirelogd.

Ну и конечно же надо затестировать какую-нибудь отказоустойчивую ферму из wireguard хостов. На openvpn с централизованным хранилищем пользователей было круто - поставили десяток openvpn серверов и юзеры коннектятся куда захотят. Упал сервер, убрали из dns и клиент переподключился без проблем.
В общем, всем wireguard, народ!
Что-то давненько не писал - работки привалило. Но на днях мы наконец-то разрулили одну историю - вытащили сайтик из черного списка. И я бы хотел высказаться про все эти списки дурацкие.

Предыстория. Есть у нас доменчик. И с некоторых сеток он перестал быть доступен - выдавалась заглушка cisco umbrella, типа сайт фишинговый и все дела. Посидели, почитали, изучили. В общем и целом выходило, что сайтик наш добавили в блеклист и теперь надо оттуда вылезать. Пока читал, наткнулся на какие-то форумы, где писали, что сайты проверяют по spfbl, а он проверяет по reverse dns name и если его нет, то привет пока - в список. Ну типа как почтовые сервачки проверяют так же и тут. Как это к вебу относится правда - хрен его знает.

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

По сути ситуация разрешилась хорошо, НО! В этой истории есть одно большое и сильное НО! Даже не одно.

Во-первых. Мы сами про ситуацию не знали - поскольку под раздачу попала только часть клиентов. И если бы нам про это не написали - то мы бы продолжали жить типа все ок. Могли бы уж по адресу контакта в whois кинуть уведомляшку типа ваш сайт был опознан так-то и так-то - давайте нам фидбек и вас разблочат.

Во-вторых. Ну ок, выявила ваша мега умная система какую-то проблему с сайтом - ну так киньте на апрув модераторам - пусть посмотрят и подтвердят что там к чему. Очевидно же, что роботы ошибаются периодически - можно перепроверить. И не надо говорить что каждый сайт не проверишь - надо только подозрительные на апрув закидывать. И систему калибровать надо нормально.

Во-третьих. Сама ситуация странная. Какая-то компания, создала какой-то свой список, как-то сама его менеджерит и сама решает кому куда можно ходить, а кому куда нельзя. Дальше осталось бы только деньги брать за исключение из списка и бизнес готов. Вон, из spfbl можно выйти платно - https://spfbl.net/en/delist/. Понятно, что там еще условия есть, но просто зацените - черный список и за деньги из него можно выйти. Ну такое себе.

Я в своей истории проходил черные списки только однажды - когда настраивал почтовые системы. Тогда это было мега популярно проверять отправителя по черным спискам. И знаете что? Туда попадали абсолютно все и вообще непонятно по каким причинам. То есть с включенной блокировкой по черным спискам почта не ходила от слова совсем. Ну и я сделал очень простой вывод на всю жизнь - нельзя блочить по черным спискам. Никогда и никого. Черт его знает как кто-то туда попал. Может айпишник старый меченным оказался, а сейчас там вполне валидный сервер располагается. Блочить можно только по совокупности факторов. Типа helo не прислал - вот тебе балл. Отправитель левый - вот тебе еще 5 баллов. В блеклистах есть - вот тебе еще 3 балла. Больше 7 баллов набрал? Идешь лесом.

В истории с их системой можно так же было поступить. Чекаем сайт, он набирает баллы - набрал меньше 50 - в whitelist, набрал от 50 до 70 - на ручную проверку модератором, набрал больше 70 - в бан с уведомлением. А если мы сразу набрали больше 70, то очевидна проблем каллибровки. Если вы вручную потом проверяете и исключаете из списка тут же - значит что-то не так с вашей системой распознования.

Короче раздолбал бы черные списки если б мог. Ах да, черный список в телефоне я регулярно пополняю, а то повадились звонить в одно и тоже время все кому ни лень.
Ох, забыл. В догонку сразу - хочу задачку вам задать, не относящуюся к предыдущему посту.

Итак, нужно придумать самый быстрый способ выкрутиться из ситуации.

Ситуация такая. У вас есть виртуалка с айпшником из РФ. И у Вас еще есть виртуалка зарубежная. Между ними связь кайф - все работает. С виртуалки из РФ вы делаете curl -D - https://packages.gitlab.com и получаете 403 - gitlab заблочил доступ из РФ.

Как быстрее всего получить доступ к гитлабу с виртуалке из РФ? Типа за минуту. Ответы кидайте на vasiliyozerov@gmail.com - самый крутой ответ запощу сюда и упомяну автора (при желании). А может еще чего подарю 🙂 Ну и составим топ ответов и сюда закинем.

P.S. Вторую виртуалку зарубежную использовать необязательно - если знаете способ как без нее, но мега быстро - закидывайте.
Так, в последний раз я задал задачку и ушел в неизвестность. Исправляюсь.

Топ вариант номер 1 по ответам - это socks5 proxy в ssh. Я вообще забыл про эту штуку, поскольку практически никогда ее не юзал (АХАХА). Но действительно в ssh есть socks5 и таким образом можно быстро создавать проксики. Очень удобно и очень круто - вы большие молодцы! Выглядит способ так:


ssh -D 127.0.0.1:9999 <ipaddr>
curl -x socks5://127.0.0.1:9999 …


Еще были варианты с использованием squid, wireguard, openvpn и любыми прокси / vpn решениями на удаленных узлах. Но эти варианты я не брал, поскольку, они требуют установки софта, что навряд ли уложится в 1 минуту. Хотя тот же wireguard ставится быстро 🙂

Мой вариант сводился к настройке iptables на второй машинке в таком виде:


iptables -t nat -I PREROUTING -p tcp -m tcp --dport 443 -j DNAT --to-destination 188.114.98.224:443
iptables -t nat -I POSTROUTING -d 188.114.98.224 -p tcp -m tcp --dport 443 -j MASQUERADE


То есть мы меняем назначение для трафика, направленного на 443 порт на packages.gitlab.com и обязательно вторым правилом делаем маскарадинг на наш адрес, чтобы гитлаб не начал отвечать на реальный айпишник. Ну и на исходной машинке добавляем в /etc/hosts айпишник нашей машинки для packages.gitlab.com.

Я видел предложения сделать также в некоторых вариантах (если вариант через iptables подразумевал именно это :). Этот путь конечно не такой универсальный, как с socks5 proxy, поскольку требует чтобы на нашем внешнем хосте 443 был не занят. И он не позволяет проксировать несколько адресов через наш внешний хост - только один. Хотим больше - надо ставить что-то умное типа nginx с сертификатами и proxy_pass, либо прокси сервер, либо wireguard, либо что-то еще.

В общем делаю простой вывод - вы победили - socks5 proxy намного приятнее и быстрее чем мои iptables правила. Поздравлямба!

Самый нестандартный ответ был по поводу использования google translate в качестве proxy. Я честно немного попробовал загрузить файл packages.gitlab.com/gitlab/gitlab-ee/packages/ubuntu/jammy/gitlab-ee_15.11.3-ee.0_amd64.deb через google translate, но у меня не получилось - валю на свои кривые ручки 🙂

Всем хорошей ночки!
Если вы, как и я, апгрейдите кубернетес через несколько версий, то в один прекрасный момент helm может сказать вам что-то типа:


Error: UPGRADE FAILED: unable to build kubernetes objects from current release manifest: resource mapping not found for name: "service" namespace: "" from "": no matches for kind "Ingress" in version "extensions/v1beta1"
ensure CRDs are installed first
helm.go:84: [debug] resource mapping not found for name: "service" namespace: "" from "": no matches for kind "Ingress" in version "extensions/v1beta1"


Что в переводе на русский означает, что в предыдущем релизе был ресурс Ingress в “extensions/v1beta1”, но теперь такой версии не существует и извините, я не могу нифига обновить.

Чтобы направить хельм на путь истинный, можно заюзать утилитку [https://github.com/helm/helm-mapkubeapis](https://github.com/helm/helm-mapkubeapis) - она ищет удаленные апишки в прошлом релизе и исправляет их на правильные. Возможно кому-то пригодится.
Если вы используете argocd, а конкретно applicationSet, то знайте - вы можете столкнуться с проблемой обновления параметров через сам argocd (app set).

Я использую примерно следующую структуру для своих проектов:

core - корневой репозиторий, в котором я описываю terraform манифесты и параметры бутстрапа кластеров кубера (типа service accounts, cert-manager, etc…).
При развертывании argocd я сразу же создаю корневой проект и добавляю в него root application )https://argo-cd.readthedocs.io/en/stable/operator-manual/cluster-bootstrapping/#app-of-apps-pattern), которое смотрит на этот же core репозиторий, только в директорию apps. Задача этого приложения - создать еще приложения, но уже для отдельных продуктовых команд.
К примеру, там я могу создать приложение auth-root-app для команды аутентификации или email-root-app для команды, которая занимается всем что связано с email’ами. Каждое из этих приложений уже будет смотреть на отдельный репозиторий с манифестами для команды. Для auth-root-app - это gitlab.com/company/auth-team/manifest, а для email-root-app - gitlab.com/company/email-team/manifests.

manifests - это уже репозитории с манифестами для каждой отдельной команды, где описываются их приложения и параметры для них. Здесь каждая команда вольна добавлять какие-то параметры, удалять деплойменты, использовать helm, kustomize, jsonnet и так далее.

Так вот. Если вы приложения для команд создаете с помощью ApplicationSet, то при обновлении параметров приложения команды вы можете столкнуться с такой ошибкой (из логов):

time="x" level=info msg="received unary call /application.ApplicationService/UpdateSpec" grpc.method=UpdateSpec grpc.request.content="%!v(PANIC=String method: reflect.Value.Bytes of non-byte slice)" grpc.service=application.ApplicationService grpc.start_time="x" span.kind=server system=grpc

Подробнее можно посмотреть здесь: https://github.com/argoproj/argo-cd/issues/12151. Вообще есть похожая known issue - https://github.com/argoproj/argo-cd/pull/13061#discussion_r1199279829, но я не уверен что она исправит мою проблему при обновлении.

Поэтому совет - используйте пока только App of apps, а сами приложеньки генерируйте с помощью kustomize. Ну ладно. Или с помощью helm 🙂
Ох, читал тут статейку про ExternalSecrets Operator - https://hackernoon.com/cooking-helm-with-the-external-secrets-operator-and-reloader и у меня есть что сказать по этому поводу!

Вообще вы можете использовать кучу разных вариантов, чтобы хранить секретные данные в вашем репозитории. Некоторые из них:

1. ExternalSecretsOperator - https://external-secrets.io/v0.8.2/. Логика такая. Ставим его в кубер, настраиваем подключение к внешнему источнику секретов (vault, lockbox, etc..). Далее ESO периодически синкает секреты из внешних источников и сохраняет их в Secret в кубере, который вы можете использовать.
2. SealedSecrets - https://github.com/bitnami-labs/sealed-secrets - решение от битнами. Суть такая. В кубере запускается sealed secrets, который отдает наружу только public key. С помощью kubeseal cli вы можете трансформировать любой Secret в зашифрованный SealedSecret и положить его в git репозиторий. После применения в кубе, SealedSecrets трансформирует SealedSecret обратно в Secret, который вы можете использовать.
3. SOPS - https://github.com/mozilla/sops - sops / ksops / helm secrets - все идет сюда. Суть такая - мы шифруем yaml’ики локально и закидываем в git. Дальше наша ci система с помощью приватного ключа расшифровывает их и передает kustomize / helm или кому-то другому.
4. Argo Vault Plugin - https://argocd-vault-plugin.readthedocs.io/ - суть примерно как у скрещенного sops и external secrets operator, но только если вы используете argocd (хотя можно его и локально запускать на самом деле). Суть такая. Вы внутри secret или других yaml файлов ставите placeholders: <password>, которые заменит vault plugin. Взять данные для подстановки vault plugin может из sops файла, из kms или еще откуда-то. В общем удобно 🙂

Из всех вариантов выше мне нравится sops, поскольку он позволяет хранить секреты рядом с приложением (манифестами), а так же позволяет их легко менять при необходимости. То есть вам не надо придумывать а как же закинуть секретную строку в hashicorp vault (с помощью “ClickOps-Way”), при этом не положив ее в гит.

С ним бы мог поспорить только kubeseal, но тот не позволяет редактировать секреты локально. То есть вы можете их только зашифровать. Расшифровывать нельзя. И это реально неудобно - вам каждый раз придется пересоздавать секрет. А если учесть что в секрете может быть несколько переменных, а вам надо поменять только одну - то привет пока приехали.
Хмельной Девопс
Ох, читал тут статейку про ExternalSecrets Operator - https://hackernoon.com/cooking-helm-with-the-external-secrets-operator-and-reloader и у меня есть что сказать по этому поводу! Вообще вы можете использовать кучу разных вариантов, чтобы хранить секретные…
Небольшое дополнение к секретами, да и вообще в целом к безопасности.

После этого поста мне написали в личку примерно такое:

"Чем ближе ты расшифровываешь секрет к месту его использования, теб безопаснее система" (с) Дмитрий

Полностью поддерживаю! В моем примере я в конце написал что мне нравится использовать sops и это действительно так, но с точки зрения безопасности самым правильным вариантом является использование SealedSecrets, поскольку он уменьшает поверхность атаки на нашу систему. С сопсом как минимум наша CI система владеет приватным ключом и имеет возможность получить расшифрованные данные. А если предположить что в качестве CI вы используете облачный сервис, то можно вообще считать что вы отдали внешнему сервису данные в открытом виде. И даже если сам сервис не имеет дурных намерений, то он сам может подвергнуться атаке. В общем возможнох вариантов масса.

Это нехитрое размышление приводит нас к очень простому выводу: В каждой ситуации необходимо рассматривать и обсуждать возможные риски. На каком-то проекте вы можете использовать sops, а на каком-то - железные токены, которые выдаются сотрудникам под расписку. Но вы должны самостоятельно продумать эти варианты и принять взешенное решение. Не стоит тут полагаться на Васяна и его мнение 🙂

Я со своей стороны могу сказать, что когда вы настраиваете любую систему вы всегда выбираете между безопасностью и удобством. Чем удобнее - тем менее безопасно. И наоборот. Поэтому я всегда придерживаюсь стандартных гигиенических правил из серии - все ресурсы храним внутри за vpn (gitlab'ы, куберы и тд), все сервисы общаются между собой по внутренней сети, желательно с каким-нибудь istio & mtls, вход на устройства по ключам ограниченному кругу лиц, разработчики не имеют доступа к системам - только к панелькам типа grafana (logs & metrics) и тд. Из вне доступны только те порты, которые используют пользователи - http & https. Для некоторых проектов правила могут быть намного жестче - например вообще отсутствие доступа из интернета. Но в каждой конкретной ситуации надо смотреть и оценивать риски - это главное.

Всем хороших выходных и отдельное спасибо Дмитрию за комментарий 🙂