Bash Days | Linux | DevOps
23.1K subscribers
124 photos
22 videos
586 links
Авторский канал от действующего девопса

Самобытно про разработку, devops, linux, скрипты, тестирование, сисадминство, техдирство, пиэмство и за айтишную жизу.

Автор: Роман Шубин
Реклама: @maxgrue

Курс: @tormozilla_bot

РКН: https://two.su/bashdays
Download Telegram
Приветcвую вас, комрады!

🔤🔤🔥🔤🔤🔤🔤🔤🔤🔤

Есть довольно редкоземельная но полезная утилитка, обзывается она - Crane

Примеры использования:

Копирование образа между реестрами (без необходимости их пулить себе).

Бывало у вас такое, что нужно перенести образ из одного реестра в другой?

С crane это делается на раз-два:

crane cp gcr.io/shlyapa-project/executor:v1.7.0-debug myharbor.ru/shlyapa-executor:v1.7.0-debug


Эта команда скопирует образ executor:v1.7.0-debug из реестра gcr.io в ваш собственный реджистри myharbor.ru.

Просмотр содержимого образа:

Иногда хочется заглянуть внутрь образа и посмотреть, что там внутри.

С crane это проще простого:
crane export executor - | tar -tvf - | less


Здесь мы экспортируем файловую систему образа executor и просматриваем список файлов с помощью команды tar.

Извлечение конкретного файла из образа:

Нужно достать нужный файл из образа? Легко!
crane export executor - | tar -Oxf - etc/passwd


Эта команда извлечет файл passwd из директории etc образа executor и выведет его содержимое.

Сравнение конфигураций двух версий образа:


Хотите узнать, чем отличаются конфигурации двух версий одного образа? Пожалуйста:
diff <(crane config front:1.32 | jq) <(crane config front:1.33 | jq)


Здесь мы сравниваем конфигурации версий 1.32 и 1.33 образа front с помощью утилиты diff.

Получение размера образа:

Интересно, сколько весит ваш образ? Вот как это узнать:

crane manifest gcr.io/buildpacks/builder:v1 | jq '.config.size + ([.layers[].size] | add)'


Эта команда выводит суммарный размер конфигурации и всех слоев образа.

Изменение меток и аннотаций образа:

Вы можете добавлять или изменять метки и аннотации в существующем образе без необходимости его повторной сборки:
crane mutate myharbor.ru/shlyapa-project/bear_ass_image:tag --annotation "org.opencontainers.image.description=New description" --label "version=2.0"


Эта команда добавит или обновит аннотацию и метку в указанном образе.

Также можно доложить tarbar (архивы .tar .tar.gz .tgz), добавить тэг (удобнее чем в дефолтном докер клиенте), задать другой entrypoint и т.д.

И это ещё не всё)

Но это чем я пользую практически регулярно. + это 1 бинарь (на любимой многими GOшечке) что позволяет спокойно его использовать и в системах автосборки и упростить взаимодействие с контейнерам.

tags: #utilites #devops

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
145
Еще немного про отладку ssh ключей, но уже в контексте интеграции со всякими гитлабами/гитхабами.

Чтобы работать с приватными репами ты добавляешь в настройки гитлаба/гитхаба свою публичную часть ключа.

Хорошо если такой ключ один, запутаться особо негде.

Но что если добавлено 20-100 ключей?

Порой возникает ситуация, когда какой-нибудь раннер отказывается клонировать репу и начинает орать на доступы.


Ты проверяешь, видишь в табличке «Заголовок» — gitlab-runner-1 и вроде всё хорошо, ключ прописан и в раннере его приватная часть есть. Чо за хуйня?

Тут нам и пригодится команда:

ssh-keygen -l -E md5 -f ~/.ssh/id_rsa


Выполняем ее для ключа с которым подключаемся (работает и для приватной и публичной части одинаково).

И по итогу видим фингерпринт, отпечаток:

2048 MD5:03:62:23:ca:ce:1b:8c:ad:60:1f:66:16:05:43:d8:a7 shuba@server (RSA)


Сравниваем этот отпечаток с тем что прописан в гитлабе/гитхабе и видим что он НЕ совпадает.

А это значит что твой раннер использует не тот приватный ключ для подключения.


Фиксим и радуемся. Как фиксить? Прописать актуальную публичную часть ключа в гитлаб/гитхаб. Дело закрыто.

tags: #git #devops #ssh #linuxfactory

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
46
Еще один частый вопрос с LF — а нужно ли менять дефолтный ssh (22) порт на какой-то другой?

Нет! Ну а нахуя? Сократить в логах количество записей про неудачные попытки подломить твой девственный сервак?

В логи ты ходишь — практически никогда! Тем более там logrotate работает и место диске сильно не забьется.

Предположим ты поменял 22 на 2222. Количество записей в логах «сократилось» и теперь ты ебешься с указанием порта.

ssh -p2222 user@server


Охуеть как удобно! Ты скажешь - дак можно же в ~/.ssh/config все это прописать и всё вернуть на свои места.

Host bashdays.ru
HostName bashdays.ru
User user
IdentityFile ~/.ssh/shuba
Port 2222


Справедливо. Но зачем жертвовать своим удобством?

У тебя на серверах уже отключен вход по паролю, только ключи, рут тоже запрещен.

Пусть эти писька-боты долбятся и дальше в жопы. Накрайняк воткни fail2ban и устрой им веселую жизу.

Да и боты сейчас умные, если они видят закрытый 22 порт, они сделают nmap -Pn, по итогу получат твой 2222 и продолжат долбиться.

Как не прячься, все равно тебя найдут и насрут в логи.

Получается все манипуляции с портом это бесполезные телодвижения в ущерб удобства.

Соблюдай базовые правила безопасности и всё с твоими серверами будет хорошо.

- отключи вход по паролю
- перейди на ssh ключи
- запрети вход для рута


За всю свою практику я встретил только одну компанию, где меняли 22 порт на какой-то другой, но никакой смысловой нагрузки в этом не было. Так и так эту компанию подломили и спиздили базу данных.

Да, еще был компания, где 22 порт открывался через port knocking. То есть нужно было предварительно стукнуться на 3 определенных порта и если последовательность была соблюдена, то открывался 22 порт.

Ну тоже такое себе решение в ущерб удобства. Любят люди из пушки по воробьям стрелять.

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

Такие дела, изучай!

tags: #devops #ssh #linuxfactory #security

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
187
Здрасти мои хорошие!

Рассмотрим ситуацию — злодеи устроили нагрузочное тестирование и натравили на твой ламповый стартап какой-нибудь «яндекс-танк».

😲 Подключить DDosGuard или Кратор!

Эт я пеню, ценник на такие услуги — моё почтение, да и по функционалу там очень даже всё урезано. А cloudflare по регламенту компании запрещен. Замкнутый круг.

Ну так что делать то?

Да блядь nginx затюнить и отбить всю эту хуйню-муйню. Понятно дело оно особо при дидосах не спасет, но со сканерами и «танками» заебись справится.

В nginx есть параметр limit_req_zone, он то нам и пригодится. Этот параметр ограничивает количество одновременных запросов с одного айпишника.

Поехали настраивать.

Создаем файл /etc/nginx/conf.d/assholes.conf

geo $limited {  
default 1;
192.168.1.1 0; # Этот IP не лимитируется
10.0.0.0/24 0; # 10.0.0.0/24 тоже без ограничений
}

map $limited $limit {
1 $binary_remote_addr;
0 "";
}

limit_req_zone $limit zone=bashdays:20m rate=200r/s;


limit_req_zone - ключ для отслеживания запросов.

$binary_remote_addr - системная переменная nginx, внутри хранит ip адрес клиента.

zone=bashdays:20m - произвольное имя зоны, 20m это объём памяти в мегабайтах для хранения данных. В этой зоне хранятся данные о количестве запросов от каждого уникального клиента.

rate=200r/s - ограничение равное 200 запросов в секунды с одного ip клиента.

Для рейта есть такая табличка:

Сценарий                    rate      burst
-------------------------------------------
API с высокой нагрузкой 100r/s 200
Средний веб-сайт 50r/s 100
Анти-DDoS защита 10r/s 20
CDN или кеширующий прокси 500r/s 1000


тестируем так:

ab -n 1000 -c 50 http://bashdays.ru/
wrk -t4 -c100 -d10s http://bashdays.ru/


И смотрим нет ли ошибок 503 Слишком дохуя запросов и корректно ли отрабатывает лимит.

🅰️🅰️
Сука! А как рассчитать эту 20m памяти? Щаа…

Максимальное количество клиентов = размер зоны в байтах / размерзаписинаклиента


Пардон, хуйню сморозил, давай на котиках:

Пример расчёта для 20m (20 мегабайт = 20 × 1024 × 1024 = 20 971 520 байт):

20 971 520 / 128 = 163 840


То есть, зона 20m может хранить лимиты примерно для 163 тысяч уникальных клиентов одновременно.

А откуда взялось 128?

Как я написал выше nginx хранит каждого уникального клиента (ключ $binary_remote_addr или другое значение) в зоне, используя примерно 128 байт на запись.

Есть такая табличка:

< 20 000 = 4m
~80 000 = 10m
~160 000 = 20m
500 000+ = 64m


Да, еще можно настроить burst для резких пиков. burst позволяет временно превышать rate, прежде чем включится жёсткий лимит.

Например:

limit_req zone=bashdays burst=400 nodelay;


Клиенту разрешается до 400 мгновенных запросов, а затем он попадает в ограничение 200r/s.

nodelay означает, что первые 400 запросов проходят сразу, а потом начинается строгий лимит.

Теперь по мапингам и гео хуйне.

В первом блоке про гео:

1. Все пользователи получат $limited = 1 (по умолчанию).
2. Те, кто с 192.168.1.1 или из 10.0.0.0/24, получат $limited = 0 и не будут ограничены.

Во втором блоке про мапинг:

1. Если $limited = 1, то $limit = $binary_remote_addr (IP-адрес клиента).
2. Если $limited = 0, то $limit = "", и клиент не попадает в limit_req_zone.

Короче делаем что-то вроде белого списка и кто в него не входит — идёт нахуй! Надеюсь понятно объяснил.

Теперь чтобы вся эта поебота заработала, нужно прописать в нужный локейшен:

location / {  
limit_req zone=bashdays burst=10 nodelay;
try_files $uri $uri/ /index.php?$args;
}


Про burst и nodelay выше уже рассказывал.


Ну а чтобы отдавать нужный статус при достижении лимита, делаем:

location / {  
limit_req_status 429;
limit_req zone=bashdays burst=10 nodelay;
try_files $uri $uri/ /index.php?$args;
}


Возвращаем 429 Слишком дохуя запросов, вместо стандартного 503.

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

➡️ Ну и сыпь в комменты свои варианты!

tags: #nginx #devops #security

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
84
Очередные грабли на которые в LF многие наступили.

Есть nginx с такой конфигурацией:

server {
listen 80;
server_name _;

location /app {
proxy_pass http://localhost:5000;
}
}


И есть приложение:

from flask import
Flask app = Flask(__name__)

@app.route('/')

def hello():
return 'Привет, Linux Factory!'

if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)


Плюс к нему Dockerfile:

FROM python:3.8-slim
WORKDIR /app COPY .
/app RUN pip install -r requirements.txt
EXPOSE 5000
CMD ["python", "app.py"]


Собираем и запускаем контейнер:

docker build -t bashdays .
docker run -d -p 5000:5000 bashdays


Идем в браузере на http://bashdays.ru/app и получаем хуй с маслом:

Not Found. The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.


Важно!
Уже по этой ошибке можно сделать какие-то выводы, эт не стандартная ошибка от nginx, а значит запрос отлично доходит до приложения. Но где-то внутри приложения теряется. Тренируй насмотренность, у nginx ошибки обычно по центру выводятся, а тут align вправо.

Еще как вариант, можешь остановить контейнер и еще раз в браузере тыкнуться, если получишь 502 Плохой шлюх, то значит что nginx нормально всё у тебя передает запросы на Flask. Но где-то внутри Flask происходит какое-то гавнище.

А что случилось? Всёж верно? Локейшен в nginx прописан, прокси-пасс тоже присутствует, где ответ от приложения?

Все дело в слэшах!

 location /app {
proxy_pass http://localhost:5000/;
}


В конце порта 5000 ставим слэш и проверяем. Хуяк и оно начинает работать!

Что вообще происходит?

Когда мы делаем локейшен без последнего слэша в прокси-пассе, nginx проксирует запросы на приложение без изменения пути.

То есть приложение app.py получает роут /app а его там естественно нет. А есть просто корень /.

А когда мы добавляем слэш, nginx добавляет путь после /app к целевому урлу.

Короче опять заумно расписал, давай снова на котиках!

➡️ Без слэша запрос /app проксируется на http://localhost:5000/app, что не совпадает с маршрутом, настроенным в Flask.

➡️ С слэшем запрос /app проксируется на http://localhost:5000, и приложение Flask обслуживает его как корневой путь /.

В общем с конечным слэшем nginx «удаляет» часть пути /app и отправляет запрос на корень приложения Flask.

Вот и вся наука. Грабли прям жизненные и порой доставляют проблемы с многочасовым дебагом.

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

Вечерком еще чтива подвезу, случайно набрал интеграций, теперь отдуваюсь.

tags: #nginx #devops #linuxfactory

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
115
Очередной распространенный вопрос - а схуяли нужно гитлаб изучать?

Почему не github / pornhub / gitea?

Ответ простой — Бесплатная версия GitLab (Community Edition) с открытым кодом даёт возможность изучить платформу без затрат. Для бизнеса это ещё и свобода от привязки к вендору.


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

Например, в вакансиях этот пункт про гитлаб будет в 99% случаев. Раньше еще можно было встретить Bitbucket, но эти письки съебали с РФ и поэтому все дружно послали atlassian в пешее эротическое.

Что не так с гитлабом?

Всё с ним так, единственный минус — он дохуя прожорлив, если у тебя слабенький сервачок то будет больно. Ну и ежечасные обновления бесят, заебывает эта всплывашка — ай, я снова дырявый, подлатай меня сучка!

Сюда еще можно добавить — бесконечная документация, нет четких инструкций по бест-практикам как писать пайплайны. Одну задачу можно решить несколькими способами, а способы порой бывают достаточно костыльные и упоротые.

Кто-то вообще умудряется превратить пайплайны в ООП. Это отдельный костяк отбитых инженеров, приверженцы бас-фактора.

На самом деле неважно где ты натаскаешься писать пайплайны, тут главное иметь представления как они работают.

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

Грубо говоря — нарисовал в голове блок схему, напрограммировал. Благо сейчас полно книжек — Java для детей, Golang - для бабушек и т.п. Читаешь книжку за пару дней, знакомишься с синтаксисом и пошел кодить. Функции, циклы, условия они везде одинаковые.


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

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

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

Такие дела!

tags: #рабочиебудни #devops

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
75
Вчера многие обратили внимание на конструкцию в nginx:

server_name _;


Что это блядь за «чёрточка» вместо сервер_нейма?

Если техническим языком — это подстановочное имя хоста.

Пусть на сервере прописано так:

server {
listen 80;
server_name _;
return 444;
}

server {
listen 80;
server_name bashdays.ru;
return 200;
}

server {
listen 80;
server_name linuxfactory.ru;
return 200;
}


Если в браузере откроем bashdays.ru или linuxfactory.ru, то получим статус 200.

А как получить 444, тот самый с «чёрточкой»?

Просто! Например, зайти на сервер по его IP адресу. Nginx не сможет сопоставить доменные имена, которые прописаны в конфиге и отдаст статус 444.

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

Обычно эту «чёрточку» прописывают в файле defaults, чтобы тем, кто попал на сервер по IP адресу увидел, то что ты захочешь, а не рандомный сайт который у тебя висит на сервере.

Короче если запрос приходит с именем хоста, которое не совпадает ни с одним из явно прописанных server_name, то он будет обрабатываться этим серверным блоком.

А в сочетании с return 444 (No Response) используется для закрытия нежелательных запросов или для обработки некорректных запросов.

Если «чёрточка» не используется, то вместо неё можно просто прописать default_server в директиве listen, и блок будет работать аналогично.

server {
listen 80 default_server;
return 444;
}


Символ _ не имеет специального значения в самом Nginx. Это просто удобное соглашение, принятое сообществом.


Вот так вот и живём, изучай!

tags: #nginx #devops #linuxfactory

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
179
Сегодня будем запирать врата ада.

Ситуация: Есть физический сервер, на нём торчит само собой Linux, к нему подключен монитор и кнопки. Короче можно физически подойти к этой машине и ввести логин/пароль.

Ну или при желании понажимать ALT+F1, F2, F3 (чтобы переключать tty).

Надеюсь ты понял о чем я тебе говорю. Ну дак вот!

Задача: Нужно запретить физический вход по логину/паролю, но оставить вход по ssh с ключами.

Как настраивать все эти ключи можешь почитать в серии постов по тэгу: #linuxfactory

Давай теперь отключим физический доступ к tty.

Заходим на сервер и смотрим юниты

systemctl list-units | grep getty


В ответ получаем список активных tty

getty@tty1.service
getty@tty2.service
getty@tty3.service
getty@tty4.service


getty (сокращение от get tty) — это программа в Linux и Unix-подобных системах, которая отвечает за управление терминалами и логин-процессом для пользователей, подключающихся к системе через консоль.


Теперь запускаем команды:

systemctl disable getty@tty{1..10}.service
systemctl stop getty@tty{1..10}.service


Всё! Песда! Терминал становится черным, интерактивность потеряна.

Пробуем понажимать ALT+F1, F2, F3 — хуй там плавал!

Перезагружаем сервер и НИЧЕГО! Черная дыра! Пробуем подключиться к серверу по ssh — ХА! Работает сучка!

При отключении getty для терминалов tty1–tty10, это не повлияет на SSH-доступ, так как SSH-сервер работает независимо от getty.


Теперь даже имея физический доступ к серверу, ты нихуя с ним сделать не сможешь. Максимум загрузиться в Recovery mode и починить:

sudo mount /dev/sdXn /mnt
sudo chroot /mnt
sudo systemctl enable getty@tty{1..10}.service
sudo systemctl start getty@tty{1..10}.service


Вот такие приколюхи! Как и где это применять решай сам. Развлекайся.

Про Magic SysRq в Linux писал тут.

Чем терминал отличается от консоли писал тут


tags: #linux #devops

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
1097
Использовать ssh ключи очень удобно, при условии если у тебя в наличии 2-5-10 серверов

А как жить в ситуации когда у тебя 100500 серверов и еще 100500 пользователей?

Этож нужно за всем этим следить, обновлять и отзывать неактуальны (проёбанные) ключи. Всё скатывается в ужасную рутину.

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

Но чтобы не морочиться со всей этой хуйней, у тебя всё это есть и установлено из коробки. Вариант не совсем простой, легко запутаться, но если разобраться то качнешь свой скилл.

Центр сертификации (CA).

Работает он так:

1. Ты настраиваешь CA которому доверяют все твои 100500 серверов.
2. Сертификат подписывает ключи пользователей с указанием сроков действия и ограничений.
3. Если сотрудник покидает команду, ты просто отзываешь сертификат, а не удаляешь ключи с каждого сервера.

Давай замутим:

Берем какуюнить линукс машину и делаем из него CA.

ssh-keygen -t rsa -b 4096 -f ~/.ssh/ssh_ca -C "SSH Certificate Authority"


Описывать за что отвечают параметры не буду, всё это уже разжевали, читай посты по тегу #linuxfactory


По итогу получаем 2 ключа, ssh_ca (приватный) и ssh_ca.pub (публичный).

Дальше чтобы сервера могли доверять сертификатам, подписанным нашим CA, добавляем публичный ключ CA (ssh_ca.pub) на все 100500 серверов.

Тут уже сам автоматику организуй, либо баш скриптом, либо ансибл ролью либо еще как-то. Попробуй ради интереса изобрести своё решение.


Покажу в рамках одного сервера:

scp ~/.ssh/ssh_ca.pub user@bashdays:/etc/ssh/


Теперь заходим на этот самый сервер куда ты скопировал публичную часть ключа.

Открываем конфиг /etc/ssh/sshd_config

И пишем в него:

TrustedUserCAKeys /etc/ssh/ssh_ca.pub


Не забываем хуйнуть:

sudo systemctl restart ssh


Теперь сервер будет доверять сертификатам подписанным CA.

Ну а чтобы пользователь мог подключаться по сертификату, подписываем его публичный ключ с помощью CA.

Для этого копируем на CA машину свой локальный публичный ключ. Тот что у тебя на машине лежит и который ты прописывал ранее в authorized_keys на удаленных серверах.

Подписываем публичный ключ и превращаем его в сертификат.

ssh-keygen -s ~/.ssh/ssh_ca -I "user_cert" -n user -V +1w /tmp/user_key.pub


В ответ получаем нечто такое:

Signed user key /tmp/user_key-cert.pub: id "user_cert" serial 0 for username valid from 2025-02-16T12:02:00 to 2025-02-23T12:02:59


И в папке /tmp появляется файл user_key-cert.pub

➡️ Важно. При подписании ключа, указывай валидного юзера под которым будешь подключаться, иначе нарвешься на ошибку:

 error: Certificate invalid: name is not a listed principal


Забираем себе на локальную машину подписанный ключ и пробуем подключиться:

ssh -i ~/.ssh/id_rsa -o CertificateFile=~/.ssh/user_key-cert.pub root@bashdays.com


Вуаля, я залетаю на сервер с подписанным ssh ключом, а на удаленном сервере вижу в логах:

2025-02-16  sshd[958704]: Accepted publickey for root from 11.11.11.11 port 35528 ssh2: RSA-CERT SHA256:Q4SKZ5cRycm79w0SyvRhAQR8 ID user_cert (serial 0) CA RSA SHA256:PtNBUw/+4/gGz4rc/ybu/uNHngcI


Если что-то не получается или не даёт зайти, пиздуешь на сервер к которому подключается и смотришь логи var/logs/auth.log. В этом файле тебе очень информативно подскажут что погуглить.


И да, на удаленном сервере можно снести файл ~/.ssh/authorized_keys от тебе больше не пригодится. Потому что сервак начинает доверять всем ключам, которые подписаны через CA.

Ну а теперь давай отзовем сертификат с ключа

На сервере к которому ты настраивал подключение:

touch /etc/ssh/revoked_certs


В файле /etc/ssh/sshd_config добавляем:

RevokedKeys /etc/ssh/revoked_certs


Добавляем серт в этот файл с отзывами:

ssh-keygen -k -f /etc/ssh/revoked_certs -z 1 user_key-cert.pub


Всё, теперь если попробуем подключиться к серверу, нас пошлют нахуй.

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

Завтра про Vault Hashicorp расскажу, в нем все проще делается

tags: #linux #devops #linuxfactory

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
2118
Здрасти, сегодня продолжаем издеваться над ssh.

Давай прикрутим 2FA.

Идем на сервер к которому хотим подключmся по ssh и ставим пакеты:

sudo apt update
sudo apt install libpam-google-authenticator


Запускаем конфигуратор:

google-authenticator


Я запускаю под рутом, но если тебе нужно настроить 2FA для другого юзера, для начала переключись на этого пользователя и только потом запускай конфигуратор.

После запуска конфигуратора, получишь по ебалу:

Do you want authentication tokens to be time-based (y/n)


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

Можно еще всякие ключики использовать:

google-authenticator -t -f -d -w 3 -e 5 -r 3 -R 60


Если интересно: google-authenticator --help

Всю эту инфу куданить себе скопируй чтоб не проебаться.

Дальше сканируем этот QR код, либо берем секретный ключ который он тебе выплюнул и вставляем в vaultwarden в TOTP или на телефоне в апку добавляем.

Тот самый секретный ключ:

Your new secret key is: MAIY4KDCXKWHPDCI


В общем нужно получить шестизначный код, вернуться в терминал (где ты запускал конфигуратор) и вставить его. Логика аналогична подключению 2FA в любых сервисах.

После этого оно выплюнет тебе рекавери коды, ну и спросит:

Do you want me to update your "/root/.google_authenticator" file?

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks

и другие


На всё соглашаешься, но если хочешь, можешь прочитать и тонко затюнить под свои задачи.

В файл /root/.google_authenticator сохранится секретный ключ и коды восстановления. Этот файл не трогаем, без него тоже нихуя не заработает.

Теперь открываем файл /etc/pam.d/sshd

И добавляем в него строчку:

# @include common-auth
auth required pam_google_authenticator.so


По необходимости комментим common-auth если ничего не работает. Есть вариант не комментить, но тогда нужно правильно настроить common-auth, но у нас сегодня не про это.

Закомментировав этот модуль ты отключаешь стандартные механизмы аутентификации и даешь зеленый свет на использование pam_google_authenticator.

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

Дальше добавляем в конфиг: /etc/ssh/sshd_config

UsePAM yes
PasswordAuthentication no
ChallengeResponseAuthentication yes
PubkeyAuthentication yes
AuthenticationMethods publickey,keyboard-interactive


Вот и всё, настройка 2FA завершена.

Рестартим:

sudo systemctl restart ssh


И пробуем подключиться по ssh к этому серверу:

ssh root@bashdays.ru

(root@bashdays.ru) Verification code:


Ха! А что вводить? Это и есть 2FA, сюда вводим одноразовый код который выплюнул тебе vaultwarden либо аппка на телефоне.

Всё! Залетаем спокойно на сервер. Без кода и ключа хуй ты теперь чо сделаешь.

Как использовать резервные коды?

Да также при запросе в Verification code. НО из файла /root/.google_authenticator они будут отлетать. Тут тоже аккуратнее.

Также можешь включать 2FA для конкретных пользователей, в конфиге /etc/ssh/sshd_config

Match User <имя юзера>
AuthenticationMethods publickey,keyboard-interactive


Настроек там жопой кушай, я тебе лишь концепт показал как эту хуйню можно быстренько настроить.

А как работать с 2FA и QR кодами из консоли, можешь почитать тут.


Главное не спеши и делай всё вдумчиво, чтобы не проебать доступ к серверу. Ну а если все проебал, да и хуй с ним, ебани кофейка и мультики посмотри.

tags: #linux #devops #linuxfactory

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
67
Ребята с LF порой интересуются — а нахуя в nginx нужна поддержка lua? Можно какой-то пример, но только не Hello World?

Конечно можно!

Понадобилось мне как-то реализовать продвинутый basic auth, чтобы nginx сходил в mysql базу и сопоставил введенные учетные данные.

А как блядь это сделать?

Тут-то и пригодится Lua!

Для начала создаем базу (nginx_auth) и табличку users. Ну и в ней колонки username и password. В username храним имя пользователя, в password захэшированный пароль:

SHA1('$UpperPa$$word')


Эту команду выполняешь в mysql, в ответ он выплюнет тебе хэш, его и нужно будет заебенить в колонку password.

Дальше конфигуряем nginx:

    location / {
content_by_lua_file /etc/nginx/lua/auth.lua;
}

location @authenticated {
root /var/www/bashdays/htdocs/site;
index index.html;
}


В nginx само собой добавляем модуль Lua, Как его собрать можешь в гугле посмотреть, ну либо взять какойнить openresty.


Ну и сам скрипт кидаем в /etc/nginx/lua/auth.lua;

local mysql = require "resty.mysql"
local sha1 = require "resty.sha1"
local resty_string = require "resty.string"
local db, err = mysql:new()

-- Установка таймаута
if not db then
ngx.log(ngx.ERR, "failed to create mysql object: ", err)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end

db:set_timeout(1000)

-- Подключение к базе данных
local res, err = db:connect{
host = "db",
port = 3306,
database = "nginx_auth",
user = "nginx_user",
password = "password",
charset = "utf8",
}

if not res then
ngx.log(ngx.ERR, "failed to connect to MySQL: ", err)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end

-- Извлекаем данные из заголовков Authorization
local auth = ngx.var.http_authorization

if not auth or not auth:find("Basic ") then
ngx.header["WWW-Authenticate"] = 'Basic realm="Restricted Area"'
return ngx.exit(ngx.HTTP_UNAUTHORIZED)
end

-- Парсим Basic Auth заголовок
local encoded = auth:sub(7) -- Убираем "Basic " из начала
local decoded = ngx.decode_base64(encoded)

if not decoded then
ngx.header["WWW-Authenticate"] = 'Basic realm="Restricted Area"'
return ngx.exit(ngx.HTTP_UNAUTHORIZED)
end

-- Разделяем строку на имя пользователя и пароль
local username, password = decoded:match("([^:]+):(.+)")

if not username or not password then
ngx.header["WWW-Authenticate"] = 'Basic realm="Restricted Area"'
return ngx.exit(ngx.HTTP_UNAUTHORIZED)
end

-- Хешируем пароль
local sha1_obj = sha1:new()
sha1_obj:update(password)
local password_hash = resty_string.to_hex(sha1_obj:final())

-- Проверяем пользователя и пароль в базе данных
local sql = string.format("SELECT * FROM users WHERE username = '%s' AND password = '%s'", username, password_hash)
local res, err = db:query(sql)

if not res or #res == 0 then
ngx.header["WWW-Authenticate"] = 'Basic realm="Restricted Area"'
return ngx.exit(ngx.HTTP_UNAUTHORIZED)
end

-- Передаем управление Nginx для загрузки страницы
ngx.exec("@authenticated")


Всё! Релоадим nginx и получаем прокаченный Basic Auth.

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


Теперь nginx лезет в базу и при успешном исходе кидает пользователя в локейшен @authenticated.

Да, скрипт работает на mariadb. Для всяких перкон и т.п. возможно нужно будет переделать SELECT.

Вот такие пироги, вот тебе и заебачее применение Lua!

➡️ Кстати, если увидел вектор атаки, поделись в комментах.

tags: #linux #devops #lua #nginx

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
45
Такс, как и обещал притаранил тебе рабочий пайплайн для авто деплоя mkdocs через gitea.

Чтобы всё это заработало, в проекте тебе нужно создать структуру:

.gitea/workflows/deploy.yml


Файл можешь обозвать как угодно.

name: Build and Deploy Bashdays Blog

on:
push:
branches:
- main

jobs:
build:
runs-on: super-runner
container:
image: catthehacker/ubuntu:act-22.04

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Install dependencies and build
run: |
pip install -r requirements.txt
mkdocs build

- name: Deploy to Server
uses: https://gitea.com/aquelle1/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
ARGS: "-rlgoDzvc -i --delete"
SOURCE: "site/"
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
TARGET: ${{ secrets.REMOTE_TARGET }}


Давай пробежимся:

Триггер on: push: запускается если что-то запушено в ветку main.

Почему в main а не в master? Читай тут.


Следом запускается джоба build на раннере super-runner, не забываем зарегать себе раннер, без него нихуя не получится.

Что такое раннеры и как с ними работать, можешь загуглить, там по сути ничего сложного. Поэтому этот этап пропущу. Чуть позже отдельным постом запилю про это.


Дальше используем адаптированный контейнер с убунтой 22.04 специально адаптированный под тестирование GitHub Actions через act.

Почему GitHub Actions? Потому что gitea унаследовало синтаксис и пайплайны в теории могут переносится между системами.

Пиздеть не буду, не переносил, но если у тебя есть опыт, то было бы интересно увидеть его в комментариях.

Потом идут шаги speps, клонируется репозиторий с проектом в контейнер, устанавливаются необходимые зависимости через «пипку», ну и билдится финальная статика.

После сборки идет секция с деплоем, я использую этот экшен. Основан он на rsync поверх ssh.

Забиваем нужные ключи и переменные, которые требуются для работы этого экшена. Переменные определяешь в настройках проекта в gitea в секции secrets.

SSH_PRIVATE_KEY = Приватный ssh ключ с которым раннер пойдет на твой сервер и подключиться по ssh к нему. Соответственно публичная часть ключа должна быть прописана у пользователя на сервере в файле ~/.ssh/authorized_keys.

Как работать с ssh ключами, читай по тегу #linuxfactory


ARGS = Аргументы для rsync, рекурсивное копирование, сохранение прав доступа, удаление файлов которые не в локальной версии.

SOURCE = Какой каталог со статикой отправляем. В Mkdocs статика генерится по умолчанию в папку site.

REMOTE_HOST & REMOTE_USER = Айпишник или домен продакшена на который деплоим + имя пользователя под которым подключаемся к серверу.

TARGET = В какую папку на проде скинуть содержимое папки site.

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

Вот и всё. Теперь при пуше в main ветку, автоматически запустится пайплайн и на прод выкатятся изменения.

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

Короче хорошего тебе вечера, теперь уже до завтра!

tags: #devops #cicd

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
51
Привет, один из наших подписчиков попросил анонсировать его пепяку. Она уже пролетала в gitgate, но ничего страшного, пусть и тут полежит. Тем более штука маст-хэвная.

TENV Ёпта!

Если кратко, TENV это консольный менеджер для управления версиями Terraform, Terragrunt, OpenTofu, Atmos.

TENV написан на гошке, не требует дополнительных зависимостей и может быть запущен под любой операционкой. Удобно чё.

Ты скажешь — блядь, да есть же например tfenv и asdf, зачем очередной велосипед?

Во-первых, автора TENV зовут Александр, а не «Аннадурай Сатьямурти Чидамбарампиллаи». А это уже о чем-то говорит!

Ну и, во-вторых, все эти утилиты не поддерживают OpenTofu, Terragrunt и т.п. К тому же, требуют много консольных костылей/зависимостей и хуёва работают НЕ на Linux.

asdf не поддерживает автоматическое переключение версий на базе спецификации версии Terraform / OpenTofu внутри проекта с помощью HCL файлов. В целом, asdf скорее переключалка по запросу, а tenv более заточен под OpenTofu/Terraform проекты.


Установка:

LATEST_VERSION=$(curl --silent https://api.github.com/repos/tofuutils/tenv/releases/latest | jq -r .tag_name)
curl -O -L "https://github.com/tofuutils/tenv/releases/latest/download/tenv_${LATEST_VERSION}_amd64.deb"
sudo dpkg -i "tenv_${LATEST_VERSION}_amd64.deb"


Сейчас лично потыкал, всё работает как часики. После установки запускаем tenv, выбираем из списка (стрелочками и на пробел) что установить и жмем ENTER.

НО, так как хашикорпы — письки (This content is not currently available in your region), в РФ надо трафик прогнать через одно место, чтобы вытащить бинари терраформа.

Вот тут я бы пожелал автору добавить киллер-фичу, сделать чтобы tenv обращался к таким заблокированным серверам не самостоятельно, а например через какую-то прокладку. Тогда сука цены бы не было!

Александр, добавь для нас такую возможность, буду прям лично твоей штукой пользоваться, достаточно удобно!


В общем рекомендую потыкать и поддержать проект звездами. Годнота!

➡️ Страница проекта и документация

tags: #utilites #devops

🔔 @bashdays➡️ @gitgate
Please open Telegram to view this post
VIEW IN TELEGRAM
62
Proxmox post install in 2025 (часть 1)

Давай разберемся, что нужно сделать после установки proxmox.

Как вариант сразу накатить эту штуку Proxmox VE Post Install, но процесс не прозрачный.


Наш путь — это всё сделать ручками, чтобы было понимание и контроль над процессом.

Для начала пиздуем сюда:

/etc/apt/sources.list


И добавляем дополнительные репозитории:

deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription
deb http://security.debian.org/debian-security bookworm-security main contrib


Затем отключаем интерпрайзный репозиторий в файле:

/etc/apt/sources.list.d/pve-enterprise.list


В нем комментируем одну строчку:

# deb https://enterprise.proxmox.com/debian/pve bookworm pve-enterprise


Следом отключаем платный ceph, идем в файл:

/etc/apt/sources.list.d/ceph.list


Комментируем и добавляем халявную репу:

# deb https://enterprise.proxmox.com/debian/ceph-quincy bookworm enterprise
deb http://download.proxmox.com/debian/ceph-quincy bookworm no-subscription


После этого выполняем:

apt update && apt upgrade
reboot


Всё раздуплится и обновится из бесплатных репозиториев. После этого перезагружаем сервер, потому что прилетело достаточно много важного и серьезного.

Дальше выполняем в терминале proxmox:

sed -Ezi.bak "s/(Ext.Msg.show\(\{\s+title: gettext\('No valid sub)/void\(\{ \/\/\1/g" /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js


Эта команда отключит No valid subsciption, сообщение о том, что у нас отсутствует подписка на интерпрайз. Довольно нозящая и заёбистая тема.

Чтобы изменения вступили в силу, скинь кэш в браузере. Всё, больше это информационное окошко тебя не побеспокоит.


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

Этот момент рассмотрим во второй части (завтра в 14:13 МСК залетит, пост в отложке), там похитрее всё.

🛠 #proxmox #devops

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
97
Proxmox post install in 2025 (часть 2)

Такс, следующим этапом после установки proxmox необходимо настроить проброс реальных устройств в виртуальные машины.

Проброс устройств подразумевает — устройства которые подключены к материнской плате на хостовой машине. Например, видюха, модемы и т.п. То есть виртуальные машины смогут их использовать.


Если устройство проброшено в виртуальную машину, то ни хостовая машина ни другие виртуальные машины использовать это устройство не смогут. Короче устройство будет изолировано.

У меня таким боком прокинут рутокен в виндовую виртуалку, чтобы в ЭДО доки подписывать с любого устройства и из любой жопы мира.


Открываем на редактирование файл: /etc/default/grub

И правим GRUB_CMDLINE_LINUX_DEFAULT, должно получиться так:

Если у тебя Intel:

GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on pt=on"


Если у тебя AMD

GRUB_CMDLINE_LINUX_DEFAULT="quiet amd_iommu=on pt=on"


iommu=on — включает IOMMU (Input-Output Memory Management Unit) для процессоров. Этот механизм позволяет изолировать устройства ввода-вывода, повышая безопасность и управление памятью.


pt=on — включает поддержку PCI passthrough для виртуализации, позволяя передавать устройства (например, видеокарты или другие PCI-устройства) непосредственно в виртуальные машины.


Сохраняем изменения и обновляем загрузчик:

update-grub
reboot


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

Загружаем модули:

Открываем на редактирование: /etc/modules

И прописываем:

vfio
vfio_iommu_type1
vfio_pci


С версии ядра 6.2 модуль vfio_virqfd больше нахуй не нужен.

Сохраняем и запускаем:

update-initramfs -u -k all
reboot


Здесь обязательно нужен ребут, без ребута нихуя НЕ заработает!

Проверяем:

dmesg | grep -e DMAR -e IOMMU or dmesg | grep -e DMAR -e IOMMU


И ищем строчку: Interrupt remapping enable

Если нашел, то поздравляю, у тебя всё получилось! Если же нет, то еще раз внимательно выполни все шаги и не забудь про ребут.

На закуску

Чтобы изолировать GPU карточку от хостовой системы, нужно еще с бубном побегать.

lspci -nn


Узнаём ID карточки, а дальше подставляем этот ID в эту команду:

echo "options vfio-pci ids=10de:____,10de:____ disable_vga=1" > /etc/modprobe.d/vfio.conf


И пиздярим блеклисты:

echo "blacklist radeon" >> /etc/modprobe.d/blacklist.conf 
echo "blacklist nouveau" >> /etc/modprobe.d/blacklist.conf
echo "blacklist nvidia" >> /etc/modprobe.d/blacklist.conf
echo "blacklist nvidiafb" >> /etc/modprobe.d/blacklist.conf
echo "blacklist nvidia_drm" >> /etc/modprobe.d/blacklist.conf
echo "blacklist i915" >> /etc/modprobe.d/blacklist.conf


Ну и само собой отправляем proxmox в ребут.

Проброс HDD

ls -n /dev/disk/by-id/


Узнаём ID диска и выполняем команду:

/sbin/qm set [VM-ID] -virtio2 /dev/disk/by-id/[DISK-ID]


Вот и вся наука. Всё лично протестировано и работает как часы, включая проброс видюхи.

Если есть чего добавить, велком в комменты.

🛠 #proxmox #devops

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
49
Бомбардиро крокодило!

Я выспался, можно продолжать радовать вас всякой «нужной» хуйней.

Давно про nip.io ничего не слышал, но всё чаще встречаю его у ребят с Linux Factory.

Суть этого сервиса — автоматически разрешать поддомены в IP адреса без необходимости поднимать свой DNS сервер.

Сейчас как мы делаем, локально редактируем файл /etc/hosts, прописываем в него какие-то свои локальные айпишники на вымышленные домены.

Например:

192.168.0.10 nginx.local
192.168.0.11 grafana.local


И если перейти по адресу nginx.local попадаешь на виртуалку с айпишником 192.168.0.10. Это всё очевидно.

Но опять же как оказалось про манипуляции с hosts файлом многие не знали.


А ведь это база. С помощью которой ты к примеру поднимаешь новый продакшен. Но что бы протестировать его, тебе нужно подкинуть на новый продакшен домен со старого продакшена. А тебе такое сделать не дадут.

Вот и прописываешь себе в hosts этот домен, указываешь айпишник нового прода, идешь тестишь, если все ок, то меняешь A запись.

Вернемся к nip.io, суть там такая же, прописываешь айпишники и нужные домены и по итогу получаешь такое:

nginx.192.168.0.10.nip.io → 192.168.0.10
app.192.168.0.11.nip.io → 192.168.0.11
test.192.168.0.12.nip.io → 192.168.0.12


Теперь открыв nginx.192.168.0.10.nip.io ты попадешь на 192.168.0.10 без редактирования своего hosts файла.

Давай потыкаем

У меня на IP 192.168.10.6 в локалке висит малина со всякими открытыми портами.

Пингуем:

ping 192.168.10.6.nip.io


В ответ я получаю такую картинку:

Server: 8.8.8.8
Address: 8.8.8.8#53

Non-authoritative answer:
Name: 192.168.10.6.nip.io
Address: 192.168.10.6


Отлично, пинг прошел, вернулся локальный айпишник. То есть мы пинганули какой-то глобальный домен, а он привел нас на локальный сервис.

Теперь я направляюсь в браузер и открываю url:

http://192.168.10.6


Мне открывается стартовая страница nginx. Тут всё понятно и логично. А теперь давай сделаем так.

http://nginx.192.168.10.6.nip.io


Хуяк и видим предупреждение:

Для сайта 192.168.10.6.nip.io не поддерживается защищенное подключение


Ну клёвое же. Запрос прошел через глобальный домен и перенаправил запрос в мою локальную сеть. Без необходимости что-то прописывать в hosts файле.

Просто жмем «продолжить» и попадаем снова на стартовую страницу nginx.

Еще вариант с портами:

http://sync.192.168.10.6.nip.io:8384


На порту 8384 у меня висит syncthing и всё отлично открывается. То есть можно пробрасывать запросы через домен прям на порты или в докер контейнеры.

А можно прям кучу поддоменов плодить:

http://a.b.c.d.192.168.10.6.nip.io/


И всё будет работать.

А еще можно на такие домены получить SSL сертификаты, но у тебя должен быть белый айпишник.

sudo certbot certonly --standalone -d nginx.204.0.115.50.nip.io


Хуяк и у тебя готовый SSL для домена nginx.204.0.115.50.nip.io. Ну а если не хочется возиться с certbot можно воспользоваться алтернативами, например acme.sh или caddy.

Минусы nip.io:

1. Не поддерживает wildcards, но в большинстве случаев они избыточны. Инструмент заточен больше на удобное тестирование при разработке.

2. SSL работает только с публичными IP. Для 127.0.0.1.nip.io или 192.168.x.x.nip.io Let's Encrypt не выдаст сертификат.

У nip.io есть альтернативы: xip.io или sslip.io

В общем я показал, а тебе уже самому решать где это применить или не применить.


Хорошо тебе провести эти выходные. Ну а я пошел фиксить баги.
Увидимся!

🛠 #devops #networks

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
62
У Hashicorp Vault есть прикольная фишка со встроенным ssh враппером и подписными ключами.

То есть ты не напрямую подключаешься к серверу:

ssh root@server


А подключаешься к серверу через сервер с Vault, получается что-то вроде бастиона. Vault подписывает ключи и запускает тебя в нужное место.

Настраивается эта штука так:

На сервере с Vault выполняем:

vault secrets enable -path=ssh ssh
vault write ssh/config/ca generate_signing_key=true


Создаём под это дело отдельную роль:

vault write ssh/roles/devops -<<"EOH"
{
"allow_user_certificates": true,
"allowed_users": "*",
"allowed_extensions": "permit-pty,permit-port-forwarding,permit-agent-forwarding,permit-user-rc,permit-X11-forwarding",
"default_extensions": [
{
"permit-pty": ""
}
],
"key_type": "ca",
"default_user": "root",
"ttl": "30m0s"
}
EOH


На сервере к которому хотим подключаться, делаем:

curl -o /etc/ssh/trusted-user-ca-keys.pem https://vault.bashdays.ru:8200/v1/ssh/public_key


Добавляем в конфиг /etc/ssh/sshd_config строчку:

TrustedUserCAKeys /etc/ssh/trusted-user-ca-keys.pem


И перезапускаем: systemctl restart sshd

Переходим на свою локальную машину, с которой будем подключаться:

export VAULT_TOKEN="token"

vault write -address='https://vault.bashdays.ru:8200' ssh/sign/devops public_key=@$HOME/.ssh/id_rsa.pub


Этой командой подписываем свой публичный ключ через vault сервер. В ответ получаем кучу всякой хуйни типа: serial_number и signed_key.

Сохраняем на локальную машину подписанный ключ:

export VAULT_TOKEN="token"

vault write -address='https://vault.bashdays.ru:8200' --field=signed_key ssh/sign/devops public_key=@$HOME/.ssh/id_rsa.pub > ~/.ssh/id_rsa_signed_key.pub


Смотрим параметры получившегося ключа:

ssh-keygen -Lf /Users/user/.ssh/id_rsa_signed_key.pub`


Опять же получишь портянку информации, можно посмотреть валидность и время жизни этого ключа.

Теперь подключаемся:

ssh -i ~/.ssh/id_rsa_signed_key.pub -i ~/.ssh/id_rsa root@server


Ну и вишенка
, подключаемся через враппер:

vault ssh -address='https://vault.bashdays.ru:8200' -private-key-path=~/.ssh/id_rsa -public-key-path=~/.ssh/id_rsa.pub -mode=ca -role=devops -user-known-hosts-file=/dev/null root@server


В этом случае подписывать ключ не обязательно.

Доступ к серверам контролирует Vault. Можно быстро накручивать необходимые права, забирать и отзывать ключи. Ну и там много чего еще можно сделать.

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


Что-то вроде центра сертификации, писал как-то отдельный пост на эту тему.

Нашел еще видос у пиндоса на эту тему, можешь визуально глянуть и проникнутся.

Такие дела!

🛠 #linux #devops

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
156
Как оказалось многие испытывают неистовые жопные боли при настройке angie со встроенной поддержкой SSL сертификатов от Lets Encrypt.

Сегодня покажу как избавиться от боли и на лету получать SSL для angie. Без всяких там certbot и acme.sh. Довольно удобно реализовано.

Для чистоты эксперимента я взял чистый, прерываемый сервер, без nginx’ов и т.п. на базе Ubuntu 24.04.


angie это аналог nginx, но на стероидах, у меня он крутится на уроках для LinuxFactory и в блоге. Подкупил он меня нативной поддержкой LUA и кучей модулей включая авто-получение SSL сертов.

Устанавливаем angie:

sudo curl -o /etc/apt/trusted.gpg.d/angie-signing.gpg https://angie.software/keys/angie-signing.gpg

echo "deb https://download.angie.software/angie/$(. /etc/os-release && echo "$ID/$VERSION_ID $VERSION_CODENAME") main" | sudo tee /etc/apt/sources.list.d/angie.list > /dev/null

sudo apt-get update
sudo apt-get install -y angie


Начиная с Angie 1.3.0, модуль ACME (http_acme_module) включён по умолчанию в основной пакет angie.

Открываем айпишник сервера в браузере и убеждаемся что все работает. Если всё заебись, получишь стартовую страницу angie. Дефолтная страница практически один в один повторяет дефолтную страницу от nginx.

Дальше у меня есть домен two.su который живет в Cloudfalre. В настройках DNS я прописываю A запись и указываю айпишник нового сервера. Весь трафик идет напрямую, без фильтрации самого Cloudfalre.


Конфигуряем angie на домен two.su.

Создаем файл two.su.conf в /etc/angie/httpd.d/ с таким содержимым.

    server {
listen 443 ssl;
server_name two.su www.two.su;

acme letsencrypt;
ssl_certificate $cert_letsencrypt;
ssl_certificate_key $cert_key_letsencrypt;

location / {
root /var/www/html;
}
}


А в файле /etc/angie/angie.conf в секцию http добавляем:

    acme_client letsencrypt https://acme-v02.api.letsencrypt.org/directory;
resolver 127.0.0.53;

map $acme_cert_letsencrypt $cert_letsencrypt {
'' /etc/angie/ssl-self-signed/cert.pem;
default $acme_cert_letsencrypt;
map $acme_cert_letsencrypt $cert_key_letsencrypt {
'' /etc/angie/ssl-self-signed/key.pem;
default $acme_cert_key_letsencrypt;
}


Если angie у тебя работает в докере, то в resolver пропиши: 127.0.0.11.

Проверяем: angie -t и в ответ получаем:

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


Полученный сертификат и соответствующий ключ будут доступны в конфигурации через переменные $acme_cert_<имя> и $acme_cert_key_<имя>.

Перезапускаем: systemctl reload angie

Если получил ошибку, закинь сертификаты заглушки в /etc/angie

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 3650 -nodes -subj "/C=RU/O=Bashdays/CN=LinuxFactory"


У меня никаких ошибок не возникло.

В папке: /var/lib/angie/acme/letsencrypt появились ключи и серты.

Захожу в браузере на two.su и вижу зеленый замочек. Что и требовалось доказать. SSL сертификат был автоматически получен и в дальнейшем будет автоматически продляться. Без всяких кронов, клиентов, API ключей и хуйни.

Единый сертификат будет получен для всех доменных имён, которые были перечислены в директиве server_name. А директива acme указывает, какой ACME-клиент должен использоваться.


Меня вся эта кухня полностью устраивает. Понятно дело есть Nginx Proxy Manager, но я привык работать без гуёвых штук и мордочек.

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

По крайней мере меня так учили и я с этим полностью согласен.

🛠 #linux #devops #angie #ssl

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
369
Распространенный случай в пайплайнах, как ребята пишут в LF:

deploy:
stage: deploy
script:
- ssh ${USER}@${HOST} "docker pull"
- ssh ${USER}@${HOST} "docker down"
- ssh ${USER}@${HOST} "docker up -d"
- ssh ${USER}@${HOST} "....."


🔥 Как и обещал. С сегодняшнего дня в Linux Factory действуют летние скидки. Кто ждал, велком.


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

На сервере может быть установлены лимиты в ssh_configMaxSessions, а плюсом еще работает Fail2ban или нечто подобное.

И по итогу пайплайн будет вечно делать хуйню, внезапно падать и т.п.

Что делать?

Передать команды в рамках одной сессии.

Например, так:

script:
- |
ssh ${USER}@${HOST} << EOF
docker pull ...
docker down ...
docker up -d ...
...
EOF


В YAML, конструкция | перед блоком многострочного текста указывает, как обрабатывать переносы строк. | означает: сохраняй все переносы строк, как они написаны.


Либо пропихать так:

script:
- ssh ${USER}@${HOST} "docker pull ...; docker down ...; ..."


Но предпочтительнее первый вариант, он консистентный и более читаемый.

Учись сразу делать нормально и учитывать такие моменты.

Ну и с праздником тебя и твоих ребятишек!

🛠 #devops #linuxfactory #cicd

@bashdays / @linuxfactory / @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
781
Довольно частый вопрос с LFКак жить с gitlab раннерами?

Да просто там всё. Если у тебя основная инфра в кубе, то в кубе и крутим.

Ну а если ламповые сервера, без всей этой кубовой хуйни, то вариантов не много.

Заводим себе отдельный сервер/сервера, чисто под раннеры. Заводим с учетом на то, что на этих серверах будут собираться докер образы. Со временем место будет забиваться.

Здесь важно раздуплить себе какой-нибудь гарбаж коллектор, либо башник в крону накидать.

Часто раннеры пихают прям на продакшен сервера, где крутятся все бизнес процессы. В этом случае продакшен превращается в помойку, нагрузка постоянно скачет при сборках/деплое и т.п.

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


Подняли сервер, нарегали нужных раннеров. В идеале это процесс автоматизировать, ансибл-хуянсибл ну или чё там у тебя есть.

Раннеры можно зарегать на один токен:

Сервер 1

gitlab-runner register \
--url https://gitlab.com/ \
--registration-token ABCDEFG123456 \
--executor docker \
--description "runner-1"


Сервер 2

gitlab-runner register \
--url https://gitlab.com/ \
--registration-token ABCDEFG123456 \
--executor docker \
--description "runner-2"


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

Раннер 1

--description "runner-fast"
--tag-list "docker"


Раннер 2

--description "runner-slow"
--tag-list "docker"


В .gitlab-ci.yml у нас так:

build:
script: echo "Building"
tags:
- docker


Задание пойдет на тот раннер, который раньше освободится. Если оба свободны — гитлаб рандомно сделает выбор, приоритетности нет.

Если важна приоритетность, можно поиграть в юните с параметром GITLAB_RUNNER_PRIORITY.

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


А как деплоить?

Тоже всё просто, ssh/rsync в помощь, раннер подключается к нужному серверу, например по ssh и выполняет все необходимые команды. Затаскивает новый докер образ, стопорит старый, запускает новый.

Как-то так:

deploy:
stage: deploy
script:
- |
ssh $SSH_USER@$SSH_HOST << EOF
docker pull $IMAGE_NAME
docker stop my_app || true
docker rm my_app || true
docker run -d -p 5000:5000 --name my_app $IMAGE_NAME
EOF


Подключаемся под юзером из переменной $SSH_USER к хосту из переменной $SSH_HOST. Ну и запускаем команды.

Важно!

Если у тебя 100500 раннеров — ты с ними заебешься, поэтому на моменте регистрации, сделай себе табличку, пропиши в них теги раннеров и на каких серверах они у тебя живут. Опять же это можно сделать через ансибл-роль, либо скриптом генерить. Тут уже сам наколдуешь.

Основное рассказал, если что-то еще вспомню, накидаю. Если у тебя есть мысли, пиши в комментарии, будем обсуждать.

🛠 #devops #linuxfactory #cicd

@bashdays @linuxfactory @blog
Please open Telegram to view this post
VIEW IN TELEGRAM
454