Файлы и права доступа: chmod, chown и всё такое
Файлы и права доступа: chmod, chown и всё такое
Ты написал деплой-скрипт, запускаешь - Permission denied. Знакомо? Это права доступа. В Linux каждый файл имеет владельца и набор разрешений.
Как читать права
Права видно в первой колонке вывода 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------- (только владелец)
Числовая нотация:
| Число | Права | Значение |
|---|---|---|
| 7 | rwx | всё |
| 6 | rw- | чтение + запись |
| 5 | r-x | чтение + выполнение |
| 4 | r-- | только чтение |
| 0 | - | ничего |
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 - работа группой над общим каталогом
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 # выполнить от другого пользователя
В контейнере ровно та же проблема: процесс по умолчанию идёт от root, поэтому в Dockerfile создают отдельного пользователя и переключаются на него инструкцией USER - см. сборку образа по шагам.
Символические ссылки
ln -s /opt/myapp/current /opt/myapp/latest # симлинк
ls -la /opt/myapp/latest # покажет → current
Симлинки используются повсеместно: /usr/bin/php → конкретная версия (php8.2), конфиги nginx в sites-enabled → sites-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/ пойдёт в каталог-цель и вычистит настоящий релиз. Удаляй симлинки без слеша.
Разбор инцидента: 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/uploads | deploy теряет запись, деплой-скрипт начинает падать |
| Общая группа | chgrp -R webdata dir плюс chmod -R 2775 dir | нужен setgid (ведущая 2), чтобы новые файлы наследовали группу; настройка сложнее, зато переживает деплой |
| ACL | setfacl -Rm u:www-data:rwx dir | точечно, без смены владельца, но права не видны в ls -la (только + в конце) и о них забывают при переносе |
chmod 777 | chmod -R 777 dir | доступ получают все пользователи и все процессы сервера; в аудите это находка уровня «критично» |
В 90% случаев верный выбор - вторая строка: общая группа плюс setgid на каталог. Именно она не отваливается после следующего деплоя.
Полезные команды для файлов
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) - должен получить отказ.