LinuxSkill - Сводки с прода и Шпаргалки
10.7K subscribers
70 photos
102 videos
12 files
533 links
Следим за новостями Linux, DevOps и ИБ, чтобы быть готовым к любым факапам.
Бонусом — плотные шпаргалки и чеклисты для ежедневной работы в терминале.

📩 По всем вопросам: @chorapov

Зеркало в MAX: https://max.ru/LinuxSkill

РКН https://vk.cc/cMUwm4
Download Telegram
Найти на сервере скрытых рутов и не поверить проверке, которая их не видит.

Найти лишние учётки с нулевым UID


getent passwd | awk -F: '($3 == 0) {print $1}'


Нюанс: в подборках пишут awk -F: '($3 == "0")' — со строковым сравнением. Оно пропускает UID, записанный как 00 или 0000. Проверил: завёл пользователя с -u 00, id показал uid=0(root), то есть для ядра это полноценный рут, а строковая проверка его не увидела. Без кавычек awk сравнивает числа и ловит все варианты.

getent вместо cat /etc/passwd нужен, чтобы захватить пользователей из LDAP и SSSD.

Убедиться, что root не пускают по SSH


sshd -T | grep -i permitrootlogin


Нюанс, и он главный. grep по /etc/ssh/sshd_config показывает то, что написано, а не то, что действует. Собрал стенд: в основном конфиге PermitRootLogin no, а в /etc/ssh/sshd_config.d/99-cloud.conf — yes. Строка Include стоит выше, drop-in побеждает:


grep -> PermitRootLogin no
sshd -T -> permitrootlogin yes


Именно так выглядит типовой облачный образ. sshd -T печатает итоговую конфигурацию после всех включений.

Проверить sudoers перед сохранением


visudo -c -f /etc/sudoers.d/devops


visudo без аргументов знают все, а вот -c -f работает с любым файлом и годится для CI или проверки после Ansible. На кривом правиле показывает строку и место ошибки, на нормальном — parsed OK. Заодно sudo -l покажет, что реально разрешено текущему пользователю.

Найти SUID-бинарники


find / -xdev -perm /6000 -type f -ls 2>/dev/null


Нюанс: -perm -4000 -user root из чек-листов пропускает SGID. На моей машине разница 9 файлов против 12 — мимо проверки прошли chage, expiry, ssh-agent. Фильтр -user root тоже лишний: чужой SUID опаснее рутового, а он под этот фильтр не попадёт. И -xdev обязателен, иначе find захлебнётся на /proc.

Про sudo -i вместо su спорить не буду, но помнить стоит другое: жёсткие правила в sudoers обходятся через less, find, awk и десяток других утилит — у всех есть побег в шелл. Разрешать по одной команде безопаснее, чем кажется, только если это не интерпретатор.

А вы root на серверах отключаете совсем или держите для аварийной консоли?

#Linux #Security #SSH #sudo #DevOps
👍4
🎥 Вебинар: Где Linux хранит настройки и логи: разбираем файловую структуру на практике.

На открытом уроке разберем структуру каталогов Linux и узнаем, где искать конфигурационные файлы, системные журналы и другие важные данные. Рассмотрим назначение основных директорий и вспомним, как права доступа влияют на работу с файлами.
Данный открытый урок проходит в рамках курса «Администратор Linux. Базовый уровень».

О чем поговорим:
— Как устроена файловая структура Linux
— Для чего предназначены основные системные каталоги
— Что такое конфигурационный файл и где обычно хранятся настройки сервисов
— Где Linux хранит системные логи и как найти нужный журнал
— Как права доступа влияют на чтение и изменение файлов

🧠 Вебинар приурочен к старту курса «Администратор Linux. Базовый уровень». Вы освоите Bash, TCP/IP, Nginx, Apache, MySQL, Docker, Git, Prometheus, Grafana и ELK. На живых занятиях с практикующими экспертами научитесь настраивать веб-серверы, работать с сетью, контейнерами, мониторингом и фильтрацией трафика.

👉 Для участия зарегистрируйтесь https://otus.pw/e3sf/

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
👍2👎1
Смонтировать сетевую шару и не подвесить процессы, когда сервер отвалится.

Убрать пароль из fstab


mount -t cifs -o credentials=/etc/cifs-creds \
//10.0.0.10/share /mnt/samba


Файл /etc/cifs-creds из трёх строк: username=, password=, domain=. Нюанс: chmod 600 на нём — целиком на вашей совести, mount права не проверяет и с файлом 644 отработает молча. Заодно проверьте, что каталог с ним не уезжает в бэкап открытым текстом.

Отцепить зависшую шару


umount -l /mnt/nfs


Проверил на tmpfs: после -l каталог пуст и монтирования в mount больше нет, но процесс, державший файл открытым, спокойно дочитал его по старому дескриптору. Нюанс: -l убирает точку из дерева, а не освобождает ресурсы — пока живы процессы, ФС остаётся. Ищите их через lsof +D до того, как отцепите, иначе искать будет негде.

-f имеет смысл только для NFS и только вместе с недоступным сервером, для cifs он почти ничего не даёт.

Посмотреть, что отдаёт сервер


showmount -e 10.0.0.10


Нюанс: showmount говорит по протоколу MOUNT, которого в NFSv4 нет вообще. На сервере с отключёнными v2 и v3 получите clnt_create: RPC: Program not registered — и это не значит, что сервер мёртв. Для v4 просто монтируйте корень экспорта: mount -t nfs4 10.0.0.10:/ /mnt.

Монтировать по требованию, а не при загрузке


//10.0.0.10/share /mnt/samba cifs
noauto,x-systemd.automount,_netdev,
credentials=/etc/creds 0 0


Нюанс: _netdev нужен наравне с x-systemd.automount. Без него systemd не знает, что монтирование сетевое, и может дёрнуть его до поднятия интерфейсов. Плюс x-systemd.idle-timeout= отцепит шару после простоя — на нестабильном канале это лучше, чем висящая точка.

Не залипнуть на медленном NFS


mount -t nfs -o soft,timeo=50,retrans=2 \
10.0.0.10:/data /mnt/nfs


timeo в децисекундах, так что 50 — это 5 секунд. По умолчанию для TCP стоит 600, то есть минута, да ещё с линейным ростом после каждой переотправки.

Нюанс: man про soft пишет прямо — silent data corruption. Приложение получит I/O error, и если оно его не обработает, запись потеряется тихо. Для баз и любой записи оставляйте hard. И не тащите из старых шпаргалок intr — опция игнорируется с ядра 2.6.25.

А вы сетевые шары монтируете через fstab или отдаёте systemd с automount?

#Linux #NFS #Samba #systemd #DevOps
👍5
Не оставить пароль в bash_history и не удивиться, что он всё равно там оказался.

Скрыть команду от истории


mysql -u root -p'super_secret'


Ведущий пробел, и строка не попадёт в ~/.bash_history. Работает, если в HISTCONTROL есть ignorespace. Нюанс: в Ubuntu это уже настроено, но по-разному. В /etc/skel/.bashrc стоит ignoreboth, а в root-сессии у меня было ignoredups:ignorespace. Проверьте своё:


echo "$HISTCONTROL"


Если пусто — трюк не сработает вообще, а вы будете уверены в обратном.

Исправить опечатку в предыдущей команде


systemctl status nginx-confg
^confg^config


Заменяет и сразу выполняет. Нюанс: только первое вхождение. Проверил на echo aaa bbb aaa — после ^aaa^XXX получилось XXX bbb aaa. Для всех вхождений другая конструкция:


!!:gs/aaa/XXX/


Открыть длинную команду в редакторе


fc


Вытаскивает последнюю команду в $EDITOR, после сохранения bash её выполняет. Нюанс: порядок поиска редактора — FCEDIT, потом EDITOR, и только потом vi по умолчанию. На голом сервере без vi получите bash: vi: command not found и никакого редактирования. Заранее пропишите export FCEDIT=nano или что вам ближе.

Оживить терминал после вывода бинарника


reset


clear только прокручивает экран, а reset переинициализирует терминал целиком. Если и это не помогает — stty sane возвращает настройки ввода, когда не видно набираемых символов.

Отвязать процесс от закрывающейся сессии


bg
disown -h %1


Нюанс: -h и без него — разные вещи. С -h задача остаётся в jobs, но не получит SIGHUP при закрытии терминала. Без флага задача исчезает из таблицы совсем. Проверил оба варианта, разница именно в этом.

И про главное ограничение трюка с пробелом: он прячет команду от файла истории, но не от системы. Пока процесс жив, его аргументы видны в ps -eo args любому пользователю. Пароль в командной строке остаётся паролем в командной строке — читайте его из файла или переменной окружения.

А чем вы правите длинные пайплайны — fc или сразу пишете в файл?

#Linux #Bash #Terminal #Security #DevOps
👍4
Собрать дерево каталогов одной строкой и не получить папку с фигурными скобками в имени.

Создать вложенную структуру


mkdir -p project/src/{configs,utils,tests}


Нюанс: раскрытие скобок — фича bash, а не mkdir. В sh строка отработает буквально, создав каталог с именем {configs,utils,tests} — проверил, ошибки при этом никакой. То же будет от пробела после запятой и от одного элемента без запятой: {only} останется как есть.

Если скрипт начинается с #!/bin/sh, скобки не использовать.

Вернуться в предыдущий каталог


cd -


Нюанс: cd - печатает путь в stdout. В скрипте это мусор в выводе, а $(cd -) вообще бесполезен — подстановка уходит в подоболочку, и рабочий каталог самого скрипта не меняется. Для скриптов тише так:


cd "$OLDPWD"


Ходить по каталогам стеком


pushd /var/log >/dev/null
popd >/dev/null


Нюанс: лишний popd возвращает directory stack empty и код 1. Под set -e скрипт на этом падает — а такое легко получить, если между pushd и popd стоит выход по ошибке. Перенаправление в /dev/null тоже нужно: обе команды печатают содержимое стека.

Пройти сквозь симлинк


cd -P /var/run


Проверил на своём симлинке: обычный cd оставляет PWD равным /tmp/dd/link, с -P он становится /tmp/dd/real. Нюанс не в самом переходе, а в том, что будет дальше. Из link/sub команда cd .. вернёт в link, а cd -P .. — в real. Скрипт, который ходит относительными путями через симлинк, легко уедет не туда. Заодно pwd -P показывает физический путь, когда pwd показывает логический.

Снести пустую цепочку каталогов


rmdir -p a/b/c


Поднимается снизу вверх, пока каталоги пустые. Нюанс: наткнувшись на непустой, останавливается с Directory not empty — но то, что успел удалить ниже, уже удалено. Возврата нет, так что для глубоких цепочек лучше сначала посмотреть find a -type d -empty.

А вы по дереву каталогов ходите pushd или всё-таки алиасами и cd -?

#Linux #Bash #Terminal #Scripting #DevOps
👍5
🎥 Вебинар: «Типовые задачи с RAID-массивами: создание, эксплуатация, перенос данных и восстановление»

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

О чем поговорим:
— Как создавать и эксплуатировать RAID-массивы
— Как правильно менять вышедшие из строя диски
— Как собрать массив из оставшихся рабочих дисков
— Как перенести данные на другой сервер
— Как восстанавливать RAID после сбоя и оценивать вероятность успешного восстановления

🧠 Вебинар приурочен к старту курса «Администратор Linux. Продвинутый уровень». Программа постоянно обновляется с учётом современных требований рынка. Занятия проводят практикующие эксперты, которые ежедневно работают с производственной инфраструктурой. Вы изучите востребованные инструменты: Zabbix, Prometheus, Docker, Nginx, PostgreSQL, ELK, Ansible, SELinux, Bash и многие другие.

👉 Для участия зарегистрируйтесь https://otus.pw/ZrYD/

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
👎1
Собрать архив с исключениями и не обнаружить потом, что исключения не сработали.

Исключить каталог из архива


tar --exclude='data/logs' -czf arch.tgz data


Нюанс: --exclude обязан стоять до источника. Поставил после — tar честно предупредил --exclude 'data/logs' has no effect и всё равно упаковал логи. Второе: шаблон сравнивается с тем, как путь записан внутри архива. Упаковываете data, значит исключать надо data/logs, а не /tmp/ta/data/logs — с абсолютным путём совпадения не будет.

Достать один файл из архива


tar -tf arch.tar | grep app.conf
tar -xf arch.tar tmp/ta/data/conf/app.conf


Нюанс: при упаковке по абсолютному пути tar срезает ведущий слеш и пишет Removing leading '/' from member names. Поэтому распаковать по тому же абсолютному пути не выйдет — получите Not found in archive. Сначала посмотрите список через -t, потом копируйте путь оттуда.

Дописать или удалить файл в архиве


tar -rf arch.tar newfile.txt
tar --delete -f arch.tar oldfile.txt


Нюанс: только для несжатых архивов. На .tgz обе команды падают с Cannot update compressed archives и Error is not recoverable: exiting now. Чтобы поправить сжатый архив, его придётся распаковать целиком.

Сжать быстрее и плотнее gzip


tar --zstd -cf arch.tar.zst data


На моих тестовых данных zstd дал 199 байт против 225 у gzip. Нюанс: если пакета zstd в системе нет, tar создаст файл нулевого размера и завершится с кодом 2. Файл при этом лежит на месте и выглядит как архив — в скрипте бэкапа такое заметят не сразу. Проверяйте код возврата, а не наличие файла.

Не перезаписать свежие файлы при распаковке


tar -xf arch.tar --keep-newer-files


Работает: файл, который новее архивного, остаётся нетронутым, в выводе Current 'data/logs/a.log' is newer or same age. Нюанс: код возврата при этом 2, как при ошибке. Под set -e скрипт упадёт, хотя всё прошло по плану.

Про сравнение архива с диском: tar -df arch.tar показывает Mod time differs и Size differs по каждому расхождению. Удобно проверить бэкап, не распаковывая.

А вы чем сжимаете бэкапы — gzip по привычке или уже переехали на zstd?

#Linux #tar #Backup #Bash #DevOps
👍6
Добавить пользователя в группу и не выкинуть его при этом из sudo

Добавить во вторичную группу


usermod -aG developers lisa


Нюанс, ради которого стоит перечитать свои скрипты: без -a флаг -G не добавляет, а заменяет весь список. Было sudo,developers,ops — после usermod -G developers lisa осталось только developers. Ни ошибки, ни предупреждения, код возврата 0. Человек узнает об этом, когда в следующий раз наберёт sudo.

Удалить из группы


gpasswd -d bob developers


Печатает Removing user bob from group developers. В отличие от usermod -G, трогает ровно одну группу.

Сменить GID существующей группы


groupmod -g 2000 developers
find / -xdev -nogroup 2>/dev/null
chgrp -R developers /data


Нюанс: groupmod меняет запись в /etc/group, но файлы на диске остаются со старым числовым GID. У меня после смены ls -ld /data показал вместо имени голое 1004 — каталог стал ничьим. Ищите такие через find -nogroup и чините chgrp -R, иначе доступ к данным потеряется молча.

Создать группу в скрипте


groupadd -f developers
groupadd -r pgsql


Нюанс: обычный groupadd на существующей группе возвращает код 9 и роняет скрипт под set -e. Флаг -f делает операцию идемпотентной — то, что нужно для повторных прогонов Ansible или cloud-init. А -r создаёт системную группу: у меня она получила GID 996, обычная — 2002.

Удалить группу


groupdel testers


Нюанс: если группа у кого-то основная, команда откажет — cannot remove the primary group of user 'bob', код 8. Вторичную удалит без вопросов, и у пользователей она просто исчезнет из списка.

Про проверку: id -nG lisa показывает актуальный список из базы, а groups в чужой открытой сессии — то, что было на момент входа. Пользователь не увидит новых групп, пока не перелогинится или не выполнит newgrp.

А вы группы раскатываете Ansible-модулями или скриптом с groupadd -f?

#Linux #Security #Ansible #DevOps #Bash

---

Что нашлось при проверке

Главную ловушку воспроизвёл. Завёл lisa в sudo, developers, ops. После usermod -G developers lisa осталась одна developers, код возврата 0, ни слова в выводе. Исходник называет это в «честном минусе», но в самом пункте 7 стоит только скобка «обязательно -a» — я вынес разбор в тело, потому что это главное содержание всей темы.

Находка сверх исходника: `groupmod -g` оставляет файлы без группы. Сменил GID у developers с 1004 на 2000 — каталог /data стал показывать 1004 вместо имени. Формально данные не потеряны, но группа к ним больше не имеет отношения. Исходник даёт команду без единой оговорки. Добавил find -nogroup и chgrp -R как лечение.

`groupadd -f` для идемпотентности. Обычный вызов на существующей группе — код 9. В скриптах инициализации, о которых говорит сам исходник, это ровно та причина, по которой люди начинают городить проверки через grep /etc/group. Флага -f в исходнике нет.

`groupdel` на основной группе. cannot remove the primary group of user 'bob', код 8. Проверил обе ветки — вторичная удаляется молча.

`groupadd -r` даёт GID из системного диапазона. У меня 996 против 2002 у обычной. Исходник пишет «GID до 1000» — подтвердилось.

Из пятнадцати пунктов оставил шесть. Убрал groups, id, chgrp, find -group, newgrp, groupmod -n, gpasswd -A как отдельные блоки — по ним нечего добавить сверх справки. Мысль про groups против id вплёл в текст: она объясняет, почему пользователь «не видит» новую группу.

Метрики: объём 2027, подводка 67 символов, форма — инфинитив цели. Строк кода длиннее 60 нет, восклицательных нет, хештегов 5.
👍2
🎥 Вебинар: «Сможет ли ИИ починить Linux-сервер: где заканчиваются подсказки и начинается инженерная диагностика»

Разберем реальный инцидент с Linux-сервером и попробуем найти причину проблемы с помощью ИИ. Посмотрим, какие рекомендации действительно помогают инженеру, какой информации не хватает модели и почему некоторые команды опасно выполнять без понимания состояния системы.

О чем поговорим:
- Разберем реальный инцидент с Linux-сервером и попробуем диагностировать его с помощью ИИ.
- Проверим, какие рекомендации AI можно использовать, а какие требуют дополнительной проверки.
- Сравним готовые команды с последовательной инженерной диагностикой и поиском первопричины.
- Покажем, как безопасно применять ИИ для анализа логов, формирования гипотез и подготовки плана действий.
- Разберем формат обучения, практические задания, поддержку преподавателей и итоговый проект курса.

🧠 Вебинар приурочен к старту курса «Администратор Linux. Продвинутый уровень». Программа постоянно обновляется с учётом современных требований рынка. Занятия проводят практикующие эксперты, которые ежедневно работают с производственной инфраструктурой. Вы изучите востребованные инструменты: Zabbix, Prometheus, Docker, Nginx, PostgreSQL, ELK, Ansible, SELinux, Bash и многие другие.

👉 Для участия зарегистрируйтесь https://otus.pw/6rPu/

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
👎1
Закрыть периметр правилами iptables и не остаться при этом за дверью вместе с атакующим.

Посмотреть, что слушает наружу


ss -tulpn


Даёт порт, состояние и процесс: 0.0.0.0:8099 users:(("python3",pid=3545,fd=3)). Первое, с чего начинается любой аудит — половина находок обычно в том, что сервис слушает 0.0.0.0 вместо 127.0.0.1.

Найти, кто держит больше всего соединений


ss -Hntu | awk '{print $5}' |
sed 's/:[^:]*$//' | sort | uniq -c | sort -rn


Нюанс: в шпаргалках этот однострочник идёт без -H и с cut -d: -f1. Оба места ломаются. Без -H в подсчёт попадает строка заголовка, и в топе появляется несуществующий адрес Local. А cut по первому двоеточию режет IPv6: адрес [2001:db8::2]:54321 превращается в [2001. Отсечение через sed берёт только порт с конца.

Закрыть вход по умолчанию


iptables -A INPUT -m conntrack \
--ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -P INPUT DROP


Нюанс: в подборках порядок обратный — сначала -F, потом -P INPUT DROP, а разрешающие правила ниже. Между этими двумя строками сессия уже мертва, и до правил вы не дойдёте. Политика ставится последней, всегда.

Заодно -m state там устаревший, актуален -m conntrack --ctstate.

Ограничить SSH подсетью и числом сессий


iptables -A INPUT -p tcp --dport 22 \
! -s 10.0.0.0/24 -j DROP
iptables -A INPUT -p tcp --dport 22 \
-m connlimit --connlimit-above 3 -j REJECT


Оба правила встали. Полезная деталь: iptables -S показывает, что модуль сам дописал --connlimit-mask 32 --connlimit-saddr — лимит считается по одному адресу, а не по подсети. Если нужен другой охват, маску задавайте явно.

Отправить подсеть в никуда без файрвола


ip route add blackhole 192.168.10.0/24


Нюанс: это не тихий дроп. Локальные попытки получают отлуп сразу — ping вернул connect: Invalid argument, curl завершился с кодом 7. Для исходящего трафика это плюс, ошибка приходит мгновенно вместо таймаута, но маскировки не ждите.

И общее: всё это живёт до перезагрузки. netfilter-persistent для правил, ip route — в конфигурацию сети, иначе следующий ребут откроет всё обратно.

А вы вредоносные IP баните iptables напрямую или отдаёте это CrowdSec и fail2ban?

#Linux #Security #iptables #Networking #DevOps
👍3
Разобрать строку штатным <regex.h> и не получить молчаливое «нет совпадения».

Проверял на glibc, gcc 13.3, Ubuntu 24.04. Все фрагменты ниже собраны в одну программу и скомпилированы с -Wall -Wextra без единого предупреждения.


Скомпилировать шаблон

regex_t re;
int rc = regcomp(&re, "^([a-z]+)=([0-9]+)$",
REG_EXTENDED);


REG_EXTENDED включает ERE: скобки и + работают без обратных слешей. Без флага получите BRE, где группа пишется как \(...\).


Понять, почему не скомпилировалось

char err[256];
if (rc != 0) {
regerror(rc, &re, err, sizeof err);
fprintf(stderr, "regcomp: %s\n", err);
}


Буфер обязан быть массивом. На незакрытой скобке [a-z получите Unmatched [, [^, [:, [., or [=. Нюанс: regfree вызывайте только после успешного regcomp, для неудачной компиляции освобождать нечего.


Сопоставить и достать группы

regmatch_t m[3];
const char *s = "app=100";
if (regexec(&re, s, 3, m, 0) == 0)
printf("%.*s\n", (int)(m[1].rm_eo - m[1].rm_so),
s + m[1].rm_so);


m[0] — всё совпадение, m[1] и дальше — группы по порядку. На app=100 вышло app в первой группе и 100 во второй. Нюанс: смещения в байтах, не в символах. В строке ключ=42 вторая группа начинается с байта 9, хотя символов перед ней пять. И если компилировали с REG_NOSUB, массив m не заполняется вовсе.


Не забыть освободить

regfree(&re);


Замерил: 100 тысяч компиляций в цикле без regfree раздули процесс до 1.4 ГБ, с ним — 1.8 МБ. В демоне, который компилирует шаблон на каждый запрос, это вопрос часов.


Где POSIX молчит вместо ошибки

Самое неприятное не в том, чего API не умеет, а в том, как он об этом сообщает:

\d+     на "abc 123" -> No match
<.*?> на "<a><b>" -> <a><b>
(?<=x)y -> Invalid preceding regular expression


Просмотры и именованные группы падают на компиляции — это честно. А вот \d компилируется и тихо ничего не находит, вместо него нужен [0-9]. И .*? не ошибка, а просто жадный .*: вернул всю строку вместо <a>. Привычки из PCRE здесь ломают логику молча.

А вы в C-утилитах держитесь <regex.h> или сразу тянете PCRE2?

#Linux #C #POSIX #regex #DevOps