Права доступа в Linux: как один бит уронил Vault

Ночной алерт с сообщением: диск на одной из нод забит на 98%. Нода сама по себе не критична, но на ней крутится Vault — а Vault, который вот-вот встанет колом из-за нехватки места уже не терпит отлагательств до утра.
Захожу, df -h. Правда, / почти под завязку. du по верхам — и виной всему каталог с логами Vault, а в нём один файл-лог, разросшийся до неприличных размеров.
Первая мысль — logrotate.
Открываю конфиг: всё на месте, daily, rotate 14, compress — как в учебнике.
Запускаю руками:
logrotate -f /etc/logrotate.d/vaultЛог ротируется, архив создаётся, свежий файл открывается, место освобождается. Вроде все работает.
Вопрос на миллион: если конфиг правильный и руками всё ротируется — почему оно не отрабатывает само?
Дело оказалось не в logrotate, не в cron, не в диске и не в самом Vault.
Корень зла скрывался в одном бите прав на каталоге.
При ручной проверке от root, всё проходило нормально. Но logrotate работал уже от пользователя vault, а у него не было прав создать архив в каталоге. На экране это никак не проявлялось: заметной ошибки не было, алерт тоже не приходил. Ротация просто не выполнялась, а лог продолжал расти.
Права в Linux — тема хорошо известна любым админам: chmod 777, chmod +x, ну там rwx, чего разбираться. А на проде она всплывает, как упавший сервис и забитый под ноль диск.
Чтобы понять, как один бит в правах повлиял на создание архива, нужно научиться читать права так, как их читает ядро. Начнём с той самой строчки -rwxr-xr-- из ls -l.
Читаем строку прав как штрихкод
ls -l в первой колонке показывает что-то вроде:
-rwxr-xr-- 1 deploy deploy 812 авг 4 03:14 deploy.shДесять символов слева - это штрих код из четырёх блоков, и читается он строго по позициям. Позиция важнее символа: r всегда стоит на своём месте, w — на своём, x — на своём. Прочерк на любой позиции означает только то, что это права здесь нет.
Ничего не сдвигается.
- rwx r-x r--
│ │ │ │
│ │ │ └── others (все остальные)
│ │ └──────── group (группа)
│ └────────────── owner (владелец)
└─────────────────── тип объектаПервый символ — тип объекта, к правам он вообще не относится. - — обычный файл, d — каталог, l — симлинк. Есть ещё c, b, s, p для устройств и сокетов, но в 99% выводов вы увидите - d или l.
Дальше — три тройки rwx, и каждая тройка — отдельный класс доступа: что может владелец файла, что может группа, что могут все остальные. Внутри тройки порядок всегда r, w, x.
Так что -rwxr-xr-- расшифровывается: обычный файл; владелец deploy может читать, писать и запускать; группа deploy — читать и запускать, но не менять; все прочие — только читать.
Пока прямолинейно. Дальше — момент, который ломает интуицию и регулярно всплывает в дебаге даже у опытных инженеров.
Классы не складываются. Работает первый подходящий.
Кажется логичным, что права накапливаются: владелец плюс группа плюс остальные. Нет. Ядро проверяет по порядку: процесс запущен владельцем файла — применяются только биты владельца, на группу и остальных оно уже не смотрит. Не владелец, но в группе файла — работают только биты группы. Ни то ни другое — остаются права others.
Отсюда контринтуитивная ситуация:
-r--rwxr-- 1 deploy devs 812 авг 4 03:14 report.txtФайл принадлежит deploy, группа — devs. У владельца r--, у группы rwx. И deploy при этом состоит в группе devs.
И кажется - раз группе можно писать, то и владельцу можно. А вот и нет — он владелец, значит для него действует тройка владельца, r--. Записать в собственный файл не получится, хотя его же группа имеет полный доступ. Проверка остановилась на первом совпадении и до группы не дошла.
Побеждает конкретный класс, а не сумма. При дебаге первым делом проверьте, не является ли пользователь владельцем каталога, у которого прав как раз меньше, чем у группы.
Одни и те же rwx на файле и на каталоге значат разное
На файле всё интуитивно: r — прочитать содержимое, w — изменить, x — запустить как программу.
На каталоге те же буквы переопределяются. Полезно перестать думать про каталог как про папку с файлами и начать думать про него как про таблицу: имя файла → на какие данные оно указывает. Каталог физически не содержит файлы, он содержит записи «имя ↔ ссылка». И права на каталоге — это права на эту таблицу:
- r — прочитать список имён в каталоге. ls покажет, что тут лежит.
- w — менять саму таблицу: создавать, удалять, переименовывать записи.
- x — войти в каталог и обращаться к тому, что внутри, по имени. Без x нельзя ни cd внутрь, ни открыть лежащий там файл, даже зная его полное имя.
Выводы:
w без x на каталоге бесполезен. Чтобы что-то создать в каталоге, ядро должно сначала «войти» в него (x), а уже потом менять таблицу (w). Один w без x не даёт ничего — и потом остаётся ломать голову, почему «право на запись есть, а файл не создаётся»
x без r — рабочая и частая штука. В каталог можно зайти и открыть файл по точному имени, но ls выдаст Permission denied — список посмотреть нельзя. Так специально делают с каталогами вроде /var/lib/что-то: сервис ходит внутрь по известным путям, а «оглавление» посторонним не показывается.
Самое неинтуитивное: чтобы удалить файл, права нужны не на файле, а на каталоге, в котором он лежит.
Удаление — это вычёркивание записи из таблицы, а таблица — это каталог. Отсюда получается, что файл может принадлежать root и стоять как r--r--r-- (на запись недоступен вообще) — но при наличии w+x на каталоге он спокойно удаляется. И наоборот: свой файл в него писать можно, а удалить не плучится — потому что на родительский каталог прав нет.
Именно это и сломало ротацию Vault. logrotate из-под юзера vault пытался создать в каталоге новую запись — архив vault.log.1 рядом со свежим vault.log. А создание записи в каталоге — это w+x на самом каталоге. Не на логе, а именно в каталоге
chmod и chown: меняем права и владельца
Два инструмента, которые путают: chmod меняет права (rwx), chown меняет владельца и группу.
chmod — двумя способами, результат один. Символьная запись — для точечных правок: кому (u/g/o/a) что делаем (+/-/=) какие биты (rwx):
chmod u+x deploy.sh # добавить владельцу право на запуск
chmod go-w config.yml # отобрать у группы и остальных запись
chmod a+r README # дать чтение всемВ Числовой (восьмеричной) каждая тройка rwx — одна цифра: r=4, w=2, x=1, они складываются:
7 = rwx 6 = rw- 5 = r-x 4 = r-- 0 = ---Три цифры — три класса, в порядке owner-group-other:
chmod 755 deploy.sh # rwxr-xr-x
chmod 640 secret.env # rw-r-----chmod 744 file и chmod u=rwx,go=r file дают идентичный результат. Символьная запись удобна для правки одного бита без риска задеть остальные.
chown — кто владелец и в какой группе:
chown vault log.txt # сменить владельца на vault
chown vault:vault log.txt # владелец vault, группа vault
chgrp devs report.txt # сменить только группуФлаг -R делает это рекурсивно по всему дереву — и chmod -R, и chown -R.
Нюанс: chmod доступен владельцу файла (или root), а chown на практике почти всегда только root — обычный пользователь не может «подарить» свой файл другому.
Спецбиты: SUID, SGID и sticky
Иногда в chmod встречается четвёртая цифра: chmod 4755. Это спецбиты — ещё три бита поверх обычных девяти. Их редко трогают руками, но один из них разгадывает трюк, который происходит при каждой смене пароля.
SUID — выполнись с правами владельца файла. Загадка: /etc/shadow, где лежат хеши паролей, принадлежит root и обычному пользователю недоступен для записи — правильно, чужим туда лезть нельзя. Но команду passwd спокойно запускает любой юзер и меняет свой пароль — то есть пишет в этот самый shadow. Как?
-rwsr-xr-x 1 root root 68208 /usr/bin/passwdНа месте x у владельца стоит s. Это SUID (set user ID): когда файл запускают, процесс выполняется с правами владельца бинаря, а не того, кто его запустил. Владелец passwd — root. Значит passwd, запущенный любым юзером, на время работы становится root и получает право писать в shadow. Это и есть тот самый «магический бит».
SUID мощный и потому опасный: SUID-root бинарь с уязвимостью — прямой путь к повышению привилегий.
SGID — то же самое, но для группы. 2 в спецбитах, буква s на месте x у группы. На файле работает как SUID, только по группе. На каталоге у SGID полезное свойство: все новые файлы внутри автоматически наследуют группу каталога, а не первичную группу создателя. Удобно для общих командных папок — ставите на каталог группу devs и SGID, и что бы кто ни создал внутри, оно всегда :devs.
Sticky bit — удалять может только владелец. 1 в спецбитах, буква t на месте x у others. Ставится на каталоги. Классика — /tmp:
drwxrwxrwt 10 root root 4096 /tmpПрава rwxrwxrwx — писать может кто угодно. Но удаление, как мы уже разобрали, зависит от прав на каталог. Значит без защиты любой юзер мог бы стереть чужие temp-файлы. Sticky bit это чинит: в таком каталоге удалить или переименовать файл может только его владелец (и root). Все создают, но каждый — хозяин только своему.
Еще раз основные моменты инцидента:
1. конфиг logrotate.
В нём стояла директива su vault vault. Это правильная практика: она говорит logrotate ронять привилегии и делать ротацию из-под пользователя vault, а не из-под root. Логика здравая — незачем архиватору логов ходить рутом по сервисным каталогам.
2. сам каталог.
Логи Vault лежали в каталоге, принадлежащем root:root с правами 755:
drwxr-xr-x 2 root root 4096 /var/log/vaultДальше — та же проверка, что делает ядро, ровно по двум правилам описанным в начале
vault хочет создать в этом каталоге файл vault.log.1. Создание файла — это запись новой строки в таблицу-каталог, а значит нужен бит w на каталоге.
Есть ли у vault этот w? Идём по классам (правило «первый подходящий побеждает»):
- vault — владелец каталога? Нет, владелец root.
- vault — в группе каталога (root)? Нет.
- Значит vault попадает в класс others. А у others тут — r-x.
В r-x бита w нет. Всё. Создать архив нельзя. Permission denied на попытке создать файл — и ротация не состоялась. Один недостающий бит: w для vault на каталоге. Не на логе — лог как раз писался прекрасно, vault был его владельцем. Именно на каталоге.
Ручной запуск logrotate -f из-под root работал, потому что root игнорирует биты прав вообще — единственный аккаунт, которого модель rwx не ограничивает. Поэтому ручная ратация так предательски маскировало проблему: проверка каждый раз шла из-под того единственного пользователя, для которого прав всегда достаточно.
Починка в правах каталога, не файла. Исправим ее
chown vault:vault /var/log/vaultТеперь vault — владелец каталога, у него полный rwx, ротация из-под su vault vault спокойно создаёт архивы. Диск больше не забивается.
Вся авария — отсутствие одного бита w у сервисного пользователя на одном каталоге.
Четыре способа всё сломать
Если хотите проверить руками, а не только прочитать — заведите отдельный тестовый сервер под это, и обязательно сделайте снапшот перед тем, как гонять рекурсивные chmod -R и chown -R — это ровно тот случай, когда откат снапшотом быстрее, чем восстанавливать права по памяти.
chmod 777. Когда что-то не работает и лень разбираться — рука тянется к 777. Это не решение, а отключение проверки: доступ не починен, защита снята, дыра создана. Почти всегда правильный ответ в конкретном недостающем бите у конкретного класса (как в инциденте выше — один w через chown).
Рекурсивный chmod -R 755 по дереву с файлами. Каталогам x нужен, чтобы в них заходить, а обычным файлам тем же махом раздаётся право на запуск — и все конфиги вдруг становятся «исполняемыми». Для рекурсии есть заглавная X:
chmod -R u+rwX,go+rX dirОна ставит x только каталогам и тому, что уже было исполняемым.
chown -R не туда. Классика жанра — лишний пробел или внезапно пустая переменная: chown -R app: / вместо chown -R app: /opt/app. За секунды владелец переписывается на пол-системы, sshd и sudo обижаются, и дальше уже интересный вечер. Рекурсивные операции по системным путям — только с двойной проверкой пути (и со свежим снапшотом под рукой).
Слишком «щедрые» права под /etc. Некоторые демоны нарочно отказываются работать, если права слишком открытые. sshd не пустит по ключу, если ~/.ssh или authorized_keys доступны на запись группе. sudo откажется запускаться, если у /etc/sudoers права шире 0440 — и тогда root уже не получить, чтобы это же и починить. Здесь «больше прав» = «хуже», ровно наоборот интуиции.
Итого
Для администратора права в Linux определяют, сможет ли сервис запуститься. Наша ночь с Vault закончилась забитым под ноль диском по причине отсутствующего бита w на одном каталоге.
Если собрать вместе, то все уместится в четыре пункта:
- три класса — owner, group, other, и побеждает первый подходящий, а не сумма;
- три бита — r, w, x, которые на файле и на каталоге значат разное (и удаление зависит от каталога, а не от файла);
- chmod меняет права, chown — владельца; символьная и числовая запись — одно и то же;
- три спецбита — SUID, SGID, sticky — для особых случаев.