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

Разбираем права доступа в Linux на реальном инциденте с Vault: как работают rwx, chmod, chown, права на каталоги, SUID, SGID и sticky bit.
Время чтения: 8 мин
месяц назад
Обновлено: месяц назад

Ночной алерт с сообщением: диск на одной из нод забит на 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 — для особых случаев.
Поделиться: