Файлы и права доступа: chmod, chown и всё такое

Файлы и права доступа: chmod, chown и всё такое

Ты написал деплой-скрипт, запускаешь - Permission denied. Знакомо? Это права доступа. В Linux каждый файл имеет владельца и набор разрешений.

Как читать права

9 бит прав доступа: owner, group, other по три бита rwx

Права видно в первой колонке вывода ls -la - той же команды, которой листаешь каталоги:

ls -la
# -rw-r--r-- 1 user group 1234 Apr 22 10:00 config.yml
# drwxr-xr-x 2 user group 4096 Apr 22 10:00 scripts/

Разберём -rw-r--r--:

- rw- r-- r--
│  │    │    │
│  │    │    └── другие (other): только чтение
│  │    └─────── группа (group): только чтение
│  └──────────── владелец (user): чтение + запись
└─────────────── тип: - файл, d каталог, l ссылка

Три права: r (read), w (write), x (execute).

Права на каталог значат не то же, что на файл

Самая частая путаница: rwx у каталога читают так же, как у файла. Смысл битов другой:

БитУ файлаУ каталога
rпрочитать содержимоеполучить список имён (ls)
wизменить содержимоесоздать, удалить, переименовать файл внутри
xзапустить как программувойти внутрь, обратиться к файлу по имени

Отсюда два неочевидных следствия.

Без x каталог бесполезен, даже если есть r.

mkdir -p /tmp/lab && echo secret > /tmp/lab/data.txt
chmod 444 /tmp/lab             # r--r--r--, бит x убрали
ls /tmp/lab                    # data.txt - имена видны
cat /tmp/lab/data.txt          # Permission denied - войти внутрь нельзя

Обратный случай тоже рабочий: chmod 711 на каталог даёт открыть /tmp/lab/data.txt, если знаешь имя, но ls вернёт отказ. Заходить можно, подглядывать нельзя.

Удаление разрешает каталог, а не файл. Право удалить файл - это бит w у каталога, в котором файл лежит. Свой файл с правами 444 ты удалишь (rm только переспросит), а чужой файл 666 в каталоге, куда тебе нет записи, - не удалишь.

Sticky bit: почему /tmp не превращается в свалку

/tmp доступен на запись всем, иначе программы не смогли бы создавать временные файлы. По правилу выше это значит, что любой пользователь мог бы удалять чужие файлы в /tmp. Спасает sticky bit:

ls -ld /tmp
# drwxrwxrwt 2 root root 4096 Aug 16 10:00 /tmp
#          ^ t вместо x - это sticky bit
chmod +t /var/shared           # поставить
chmod 1777 /var/shared         # то же в восьмеричной: ведущая 1

Со sticky bit удалить файл может только владелец файла, владелец каталога или root. Поднимаешь общий каталог для нескольких сервисов - ставь 1777, а не 777, иначе один сервис подчистит файлы другого.

chmod - меняем права

chmod +x deploy.sh          # добавить право на выполнение
chmod 755 deploy.sh         # rwxr-xr-x (стандарт для скриптов)
chmod 644 config.yml        # rw-r--r-- (стандарт для конфигов)
chmod 600 .env              # rw------- (только владелец)

Числовая нотация:

ЧислоПраваЗначение
7rwxвсё
6rw-чтение + запись
5r-xчтение + выполнение
4r--только чтение
0-ничего
`chmod 777` даёт полный доступ всем. Это как оставить ключи в двери. Для скриптов - `755`, для конфигов - `644`, для секретов - `600`. Те же восьмеричные числа встречаются и в коде: `os.WriteFile(path, data, 0644)` создаёт файл ровно с правами `rw-r--r--`, см. урок про [работу с файлами в Go](../go/22-files.md).

chmod 600 .env защищает секреты только от соседей по серверу. Чтобы файл не попал в образ и не утёк в реестр, его подсовывают контейнеру снаружи - через env_file и volumes, как в уроке про переменные окружения Docker.

umask: откуда берутся 644 и 755

Ты не выставлял права новому файлу, а он всё равно получился rw-r--r--. Это работа umask - маски, которая вычитает биты при создании файла.

umask                          # 0022 - типичное значение
umask -S                       # u=rwx,g=rx,o=rx - то же символьно

Система запрашивает 666 для файлов и 777 для каталогов, а umask убирает лишнее:

файл:    666 (rw-rw-rw-)  без 022  =  644 (rw-r--r--)
каталог: 777 (rwxrwxrwx)  без 022  =  755 (rwxr-xr-x)

Заодно это ответ на «почему созданный файл не исполняемый»: ядро вообще не предлагает бит x для обычных файлов, максимум 666. Поэтому после echo ... > deploy.sh всегда нужен chmod +x - без него bash-скрипт остаётся просто текстом.

umask 077                      # всё создаётся как 600/700 - «только я»
umask 002                      # 664/775 - работа группой над общим каталогом
Поменял `umask` - уже созданные файлы не изменятся. И наоборот: `umask` в твоей SSH-сессии не влияет на файлы, которые создаёт демон. Для сервиса маску задают директивой `UMask=0027` в unit-файле, см. [урок про процессы и systemd](./04-processes.md).

777 не «даёт права», а снимает защиту

chmod -R 777 выглядит универсальным лекарством и почти всегда делает хуже. Три конкретные причины.

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

chmod 777 ~/.ssh/id_ed25519
ssh git@gitlab.com
# WARNING: UNPROTECTED PRIVATE KEY FILE!
# Permissions 0777 for '/home/user/.ssh/id_ed25519' are too open.
# This private key will be ignored.

Так же ведут себя visudo с /etc/sudoers и PostgreSQL с каталогом данных: он просто не запустится, если PGDATA доступен группе или всем.

777 делает файл исполняемым. Бит x на конфиге, логе или загруженной картинке не нужен никому, кроме атакующего.

777 не лечит причину. Если Permission denied из-за неверного владельца, 777 замаскирует симптом, а следующий файл приложение создаст по своему umask - и ошибка вернётся завтра. Правильный порядок: посмотреть, от какого пользователя работает процесс (ps aux | grep backend), и править владельца или группу.

chown - меняем владельца

chown user:group file.txt           # сменить владельца и группу
chown -R www-data:www-data /var/www # рекурсивно для каталога
chown root:root /etc/nginx/nginx.conf

Типичная ситуация: nginx работает от пользователя www-data, а файлы принадлежат root. Решение - chown.

Кто вообще имеет право менять права

chmod и chown подчиняются разным правилам, и это регулярно ломает деплой-скрипты:

ОперацияКто может
chmod на файлвладелец файла и root
chown - смена владельцатолько root
chgrp - смена группывладелец, но лишь на группу, в которой сам состоит; root - на любую

«Отдать» свой файл другому пользователю нельзя даже добровольно: иначе можно было бы подсунуть человеку гигабайтный файл и съесть его дисковую квоту. Отсюда классический отказ в скрипте, который запускается не от root:

chown www-data:www-data /var/app/uploads
# chown: changing ownership of '/var/app/uploads': Operation not permitted

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

Отдельный случай - bind mount в Docker. В контейнере appuser может иметь UID 1000, а на хосте под UID 1000 сидит другой пользователь: ядро сверяет числовые UID, имена ему не важны. Поэтому файл, созданный контейнером в примонтированном каталоге, на хосте выглядит «чужим», и наоборот - контейнер не может писать в каталог хоста. Разбор монтирования - в уроке про переменные окружения, volumes и сети.

Пользователи и sudo

whoami                    # кто я
id                        # подробно: uid, gid, группы
sudo command              # выполнить от root
sudo -u postgres psql     # выполнить от другого пользователя
`sudo su` и работа от root - привычка, которая рано или поздно стоит сервера. Используй `sudo` для конкретных команд, работай от обычного пользователя.

В контейнере ровно та же проблема: процесс по умолчанию идёт от root, поэтому в Dockerfile создают отдельного пользователя и переключаются на него инструкцией USER - см. сборку образа по шагам.

Символические ссылки

ln -s /opt/myapp/current /opt/myapp/latest   # симлинк
ls -la /opt/myapp/latest                      # покажет → current

Симлинки используются повсеместно: /usr/bin/php → конкретная версия (php8.2), конфиги nginx в sites-enabledsites-available.

Права у самого симлинка всегда lrwxrwxrwx, и это не дыра в безопасности: при обращении ядро проверяет права цели, а не ссылки. Из этого три практических правила:

chmod 600 /opt/myapp/latest    # изменит права ЦЕЛИ, а не ссылки
chown -h appuser link          # -h - поменять владельца именно ссылки
readlink -f link               # куда ведёт после раскрытия всех ссылок
find /opt/myapp -xtype l       # найти все битые («висячие») симлинки

Второй подвох - относительный путь против абсолютного. ln -s ../releases/v2 current переживёт переезд всего каталога, а ln -s /opt/myapp/releases/v2 current сломается, если проект перенесли в /srv. Битая ссылка при этом выглядит в ls нормально, и только cat по ней вернёт No such file or directory.

Третий подвох - завершающий слеш. rm current удалит ссылку, а rm -r current/ пойдёт в каталог-цель и вычистит настоящий релиз. Удаляй симлинки без слеша.

Схема «каталог `releases/` плюс симлинк `current`» - классика деплоя. Переключение делают через `ln -sfn new_release current`: замена симлинка атомарна, не бывает момента, когда `current` не существует. Флаг `-n` обязателен - без него, если `current` уже указывает на каталог, команда создаст ссылку **внутри** него и получится `current/new_release`.

Разбор инцидента: nginx отдаёт 403 на существующий файл

Файл на месте, права 644, владелец верный - а nginx возвращает 403 Forbidden. Причина почти всегда в промежуточном каталоге: чтобы дойти до файла, процессу нужен бит x на каждом каталоге пути. Проверяется одной командой:

namei -l /home/deploy/app/public/index.html
# f: /home/deploy/app/public/index.html
# drwxr-xr-x root   root   /
# drwxr-xr-x root   root   home
# drwx------ deploy deploy deploy      <- здесь www-data останавливается
# drwxr-xr-x deploy deploy app
# drwxr-xr-x deploy deploy public
# -rw-r--r-- deploy deploy index.html

Домашний каталог 700 - нормальная настройка по умолчанию, но www-data через него не пройдёт. Правильное лечение - chmod 711 /home/deploy (войти можно, листинг нельзя) или перенести приложение в /opt, где такой проблемы нет. Неправильное - chmod 755 /home/deploy, после чего домашний каталог со всеми черновиками читает любой пользователь сервера.

Дать сервису доступ к каталогу: четыре способа и их цена

Задача: демону под www-data нужно писать в /var/app/uploads, каталог принадлежит deploy.

СпособКомандаЦена
Сменить владельцаchown -R www-data /var/app/uploadsdeploy теряет запись, деплой-скрипт начинает падать
Общая группаchgrp -R webdata dir плюс chmod -R 2775 dirнужен setgid (ведущая 2), чтобы новые файлы наследовали группу; настройка сложнее, зато переживает деплой
ACLsetfacl -Rm u:www-data:rwx dirточечно, без смены владельца, но права не видны в ls -la (только + в конце) и о них забывают при переносе
chmod 777chmod -R 777 dirдоступ получают все пользователи и все процессы сервера; в аудите это находка уровня «критично»

В 90% случаев верный выбор - вторая строка: общая группа плюс setgid на каталог. Именно она не отваливается после следующего деплоя.

Буква `s` в правах (`-rwsr-xr-x`) означает setuid: программа стартует с правами владельца файла, а не того, кто её запустил. Так работают `sudo` и `passwd`. Своим бинарникам setuid не ставят никогда: любая уязвимость в таком файле сразу даёт root. Найти все setuid-файлы в системе: `find / -perm -4000 -type f 2>/dev/null`.

Полезные команды для файлов

file document.pdf          # определить тип файла
du -sh /var/log/           # размер каталога
df -h                      # свободное место на дисках
find / -name "*.log" -size +100M   # логи больше 100MB
which nginx                # где лежит бинарник

Мини-задание

  • Создай скрипт hello.sh с содержимым #!/bin/bash и echo "Hello"
  • Попробуй запустить ./hello.sh - получи Permission denied
  • Дай права: chmod +x hello.sh и запусти снова
  • Проверь права через ls -la hello.sh
  • Убери бит x у каталога (chmod 444 /tmp/lab) и убедись, что ls работает, а cat внутри - нет
  • Выполни umask и посчитай на бумаге, какие права получат новый файл и новый каталог
  • Посмотри ls -ld /tmp и найди букву t в конце прав
  • Прогони namei -l по пути до любого файла в своём домашнем каталоге

Итог

  • Каждый файл имеет владельца, группу и три набора прав (user/group/other).
  • chmod меняет права, chown меняет владельца. Числовая нотация - быстрее.
  • 600 для секретов, 644 для конфигов, 755 для скриптов. 777 - не используй.

Типичная ошибка

Лечить Permission denied через chmod 777 или sudo. Это как открыть все двери, потому что не нашёл нужный ключ. Разберись, какому пользователю нужен доступ, и дай минимально необходимые права.

Мини-практика

Создай каталог /tmp/permissions-lab. Внутри создай три файла: public.txt (644), secret.env (600), deploy.sh (755). Проверь через ls -la, что права выставлены верно. Попробуй прочитать secret.env от другого пользователя (через sudo -u nobody cat secret.env) - должен получить отказ.

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