Все уроки
Linux и серверыНачальный11 мин чтенияВ моём стеке

Пользователи, группы и права доступа в Linux

Кто может читать, писать и запускать: rwx, chmod, chown, sudo и баги, которые из них растут.

  • Users & groups
  • chmod / chown
  • sudo
  • umask
Шаг 1/5
ls -l показывает табличку на двери файла: -rw-r-----. Владелец alice читает и пишет, группа devs только читает, остальные — ничего.

За 30 секунд

У каждого файла в Linux есть владелец, группа и три набора прав: чтение, запись и выполнение. Каждый процесс работает от имени какого-то пользователя. Большинство ошибок «permission denied» на серверах возникает из-за несовпадения пользователя, под которым работает приложение, и владельца нужных ему файлов. Разберитесь в этой модели — и вы сможете безопасно чинить такие ошибки, а не хвататься за chmod 777.

Почему я это использую

  • Для каждого сайта или приложения я завожу отдельного системного пользователя, чтобы взломанный сайт не мог читать или менять другой.
  • Ошибки прав доступа — одни из самых частых проблем в продакшене, которые я чиню; один из недавних случаев — каталог кэша, в который приложение не могло писать.
  • Принцип минимальных привилегий входит в мой базовый набор безопасности для каждого сервера: люди входят под своими учётками, используют sudo по необходимости, и никто не работает под root.

Как это работает

01Пользователи, группы и root

Пользователь — это учётная запись с числовым идентификатором (UID). Пользователи — это люди, но также и сервисы: nginx в Ubuntu обычно работает от www-data (в RHEL — от nginx), PostgreSQL — от postgres. Группа — это именованный набор пользователей, который позволяет выдать нескольким пользователям одинаковый доступ. root (UID 0) может всё и обходит обычные проверки прав, поэтому повседневно работать под root не стоит. Учётные записи перечислены в /etc/passwd, хэши паролей — в /etc/shadow (читать может только root), группы — в /etc/group. Команда id показывает, кто вы и в каких группах состоите.

bash
whoami                       # current user
id                           # uid, gid and all groups
id www-data                  # who is the web server user?
getent passwd your_user      # account details
groups your_user             # group membership
sudo adduser your_user       # create a user (Debian/Ubuntu, interactive)
sudo useradd -m -s /bin/bash your_user   # portable, non-interactive
sudo usermod -aG www-data your_user      # ADD to a group (-a matters!)

02Как читать rwx и восьмеричные права

Выполните ls -l, и вы увидите что-то вроде -rw-r--r-- 1 deploy www-data. Первый символ — тип (- файл, d каталог, l ссылка). Затем идут три тройки: права владельца, права группы и права всех остальных. r — чтение, w — запись, x — выполнение. Для каталога смысл смещается: r позволяет выводить список имён, w — создавать и удалять файлы внутри, а x — входить в каталог и обращаться к файлам по пути. Каждая тройка — это ещё и число: r = 4, w = 2, x = 1, которые складываются. Так, 644 — это rw-r--r-- (владелец пишет, остальные читают), 755 — rwxr-xr-x, 600 — доступ только владельцу, а 640 позволяет группе читать.

  • 644: обычные файлы. 755: каталоги и скрипты. 600: секреты вроде SSH-ключей и файлов .env.
  • 640 / 750: у владельца полный доступ, у группы только чтение, у остальных ничего; хорошо подходит для конфигов приложения, общих с группой веб-сервера.
  • 777 означает, что писать может кто угодно в системе. Это прячет реальную проблему и открывает дыру в безопасности.
  • Специальные биты: setgid на каталоге (2775) заставляет новые файлы наследовать группу каталога; sticky bit (1777, как на /tmp) не даёт пользователям удалять чужие файлы.

03chmod, chown и исправление владельцев

chmod меняет права, chown — владельца и группу. Обе команды принимают -R для рекурсивных изменений, и это мощно и опасно: неосторожный chmod -R не на том каталоге может сломать SSH или всю систему. Используйте символьную запись для небольших точных изменений (u+x добавляет владельцу право на выполнение, g-w убирает у группы право на запись) и восьмеричную, когда нужен точный результат. Частый приём — выставлять файлам и каталогам разные права через find, потому что каталогам нужен x, а обычным файлам он, как правило, не нужен.

bash
chmod 600 ~/.ssh/id_ed25519          # private key: owner only
chmod u+x deploy.sh                   # make a script executable
chmod g-w config.php                  # remove group write
sudo chown deploy:www-data -R /var/www/app
# Directories 755, files 644, in one tree
find /var/www/app -type d -exec chmod 755 {} +
find /var/www/app -type f -exec chmod 644 {} +
# Inspect a path permission by permission
namei -l /var/www/app/storage/logs/app.log

04sudo и /etc/sudoers

sudo выполняет одну команду от имени root (или другого пользователя), предварительно проверив, что вам это разрешено. Каждый вызов логируется, что для подотчётности гораздо лучше, чем общий пароль root. В Ubuntu участники группы sudo получают полные права sudo; в RHEL эта группа называется wheel. Правила лежат в /etc/sudoers и в файлах в /etc/sudoers.d. Никогда не редактируйте их обычным редактором: используйте visudo, который проверяет синтаксис перед сохранением. Сломанный файл sudoers может лишить sudo всех сразу. Можно также выдавать узкие права, например разрешить пользователю deploy перезапускать один сервис без пароля — и больше ничего. В свежих релизах Ubuntu поставляется sudo-rs — переписанная с безопасной работой с памятью версия с теми же командами и конфигом.

bash
sudo usermod -aG sudo your_user     # full sudo (wheel on RHEL)
sudo -l                             # what am I allowed to run?
sudo -u postgres psql               # run as another user
sudo visudo -f /etc/sudoers.d/deploy
# Content of /etc/sudoers.d/deploy:
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp.service

05umask, сервисные пользователи и ACL

umask определяет права новых файлов по умолчанию, убирая биты. Распространённое значение 022 даёт новым файлам 644, а новым каталогам 755; umask 002 даёт 664 и 775, что подходит командам, работающим через общую группу. Для приложений создавайте отдельного системного пользователя без login shell и делайте его владельцем только того, во что приложение должно писать. ACL (списки контроля доступа) добавляют дополнительные правила поверх модели владелец/группа/остальные, например дают ещё одному пользователю право записи в один каталог. Они удобны, но не видны при беглом ls -l (ищите + в конце строки прав), поэтому используйте их умеренно и документируйте.

bash
umask                                   # show current mask, e.g. 0022
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp
sudo chown -R myapp:myapp /var/lib/myapp
# ACL: let www-data write to one directory, now and for new files
sudo setfacl -R -m u:www-data:rwX /var/www/app/storage
sudo setfacl -R -d -m u:www-data:rwX /var/www/app/storage
getfacl /var/www/app/storage

06Классический баг: веб-сервер не может писать

Типичная ситуация: PHP- или Node-приложение работает локально, а в продакшене пишет в лог «Permission denied», когда пытается записать кэш, логи или загрузки. Причина почти всегда в том, что код задеплоил один пользователь (deploy или root), а приложение работает от другого (www-data или пользователя пула PHP-FPM). Решение — не 777. Вместо этого выясните, от какого пользователя работает процесс, оставьте код во владении пользователя deploy и доступным приложению только для чтения, а право записи дайте только тем каталогам, которым оно действительно нужно, например storage и bootstrap/cache в Laravel. Бит setgid сохраняет за новыми файлами правильную группу. И помните: деплой или запуск artisan-команд под root может создать файлы кэша во владении root, которые приложение потом не сможет перезаписать.

bash
ps -o user,pid,cmd -C php-fpm8.3     # which user runs PHP? (adjust version)
ps -o user,pid,cmd -C node
cd /var/www/app
sudo chown -R deploy:www-data .
sudo chmod -R 750 .
sudo chmod -R 2775 storage bootstrap/cache     # group write + setgid
sudo find storage bootstrap/cache -type f -exec chmod 664 {} +
sudo -u www-data php artisan cache:clear       # run as the app user, not root

Когда применять

  • Настройка любого нового сервера: личные пользователи с sudo и отдельный системный пользователь для каждого приложения.
  • Отладка ошибок «Permission denied» или «EACCES» в логах.
  • Выдача CI- или deploy-пользователю узких прав, например на перезапуск одного сервиса.
  • Защита секретов: файлы .env, приватные ключи и учётные данные БД должны иметь права 600 или 640.

Когда не стоит

  • Не используйте chmod 777 или chown -R root как быстрое решение: это прячет реальную причину и ослабляет безопасность.
  • Не стройте всё на ACL: если правило выражается через модель владелец/группа, оставьте его простым и видимым.
  • Не делите один логин (особенно root) между несколькими людьми: вы теряете подотчётность.

Альтернативы

  • ACL (setfacl)

    Дополнительные правила для отдельных пользователей или групп, когда модели владелец/группа/остальные не хватает.

  • SELinux / AppArmor

    Мандатный контроль доступа поверх обычных прав. SELinux включён по умолчанию в RHEL, AppArmor — в Ubuntu.

  • Контейнеры

    Docker дополнительно изолирует приложения, но пользователя внутри контейнера и на примонтированных томах всё равно нужно настраивать.

  • Песочница systemd

    Опции вроде DynamicUser, ProtectSystem и ReadWritePaths ограничивают сервис без ручного создания пользователей.

Лучшие практики

  1. Входите под именным пользователем и используйте sudo; отключите прямой вход root по SSH.
  2. Запускайте каждое приложение под собственным системным пользователем без login shell, владеющим только тем, во что нужно писать.
  3. Используйте 600 для приватных ключей и env-файлов, 644 / 755 для обычных файлов и каталогов.
  4. Всегда добавляйте группы через usermod -aG (с -a): без -a вы замените все группы пользователя.
  5. Редактируйте правила sudo только через visudo, желательно отдельными файлами в /etc/sudoers.d.
  6. Запускайте команды приложения (очистку кэша, миграции) от пользователя приложения, например sudo -u www-data.
  7. Учтите, что изменения групп вступают в силу только при следующем входе (или через newgrp).

Частые ошибки

  • chmod -R 777 на корне сайта, после чего любой процесс на машине может переписать ваш код.
  • Запуск artisan, npm или скрипта деплоя под root, после которого остаются файлы root, недоступные приложению.
  • usermod -G вместо -aG, который молча удаляет пользователя из группы sudo.
  • SSH отказывается принимать ключ, потому что ~/.ssh или authorized_keys доступны на запись группе или всем.
  • Прямая правка /etc/sudoers и сохранение синтаксической ошибки, ломающей sudo.

Глубже

07Как ядро на самом деле проверяет доступ

При каждом open ядро сравнивает effective UID процесса и его группы (основную и дополнительные) с владельцем и группой файла. Проверка выбирает ровно один класс по порядку: если вы владелец, учитываются только биты владельца; иначе, если совпадает одна из ваших групп, только биты группы; иначе — биты остальных. Классы не складываются, поэтому файл с правами 0077 запрещает доступ собственному владельцу, хотя все остальные могут его читать. Чтобы добраться до файла, нужно также право x (поиск) на каждом каталоге пути, что namei -l показывает с первого взгляда. Удаление или переименование файла зависит от права записи на каталог, а не на файл. Root обходит большинство этих проверок не магией, а через capabilities, такие как CAP_DAC_OVERRIDE, а работающий процесс сохраняет список групп, который был у него при входе, поэтому новое членство в группе требует новой сессии.

bash
cat /proc/self/status | grep -E '^(Uid|Gid|Groups)'   # real/effective IDs
chmod 077 test.txt && cat test.txt      # owner denied: only owner bits apply
namei -l /srv/app/shared/.env           # x needed on every directory
sudo -u www-data test -r /srv/app/shared/.env && echo readable
newgrp www-data                          # pick up a new group without relogin

08setuid, setgid и capabilities

Программа с setuid работает с привилегиями своего владельца, а не вызывающего; passwd и sudo имеют setuid root, чтобы обычные пользователи могли контролируемо менять защищённые файлы. Каждый setuid-root бинарник — это поверхность атаки, поэтому проводите их аудит и никогда не ставьте этот бит на скрипты или собственные утилиты. Linux capabilities делят власть root примерно на сорок частей, так что программа может получить только одну: cap_net_bind_service позволяет процессу без root слушать порты ниже 1024, cap_net_raw позволяет ping отправлять raw-пакеты. Файловые capabilities задаются через setcap, а systemd может выдать их сервису через AmbientCapabilities, отбросив всё остальное через CapabilityBoundingSet. Файловые системы также можно монтировать с nosuid, nodev и noexec — дешёвый шаг харденинга для каталогов загрузок и временных файлов, где никогда не должно быть исполняемых программ.

bash
find / -xdev -perm -4000 -type f 2>/dev/null   # setuid binaries
getcap -r /usr/bin 2>/dev/null                 # files with capabilities
sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/myserver
# In a systemd unit, instead of setcap:
#   AmbientCapabilities=CAP_NET_BIND_SERVICE
#   CapabilityBoundingSet=CAP_NET_BIND_SERVICE
findmnt -o TARGET,OPTIONS /tmp                  # check nosuid,noexec

09Когда права верные, а доступ всё равно запрещён

Если ls -l выглядит идеально, а приложение всё равно получает EACCES, «нет» говорит что-то поверх классической модели. Обычный подозреваемый — mandatory access control: AppArmor в Ubuntu ограничивает программы путями из их профиля, а SELinux в RHEL помечает каждый файл и процесс метками и запрещает всё, что не разрешено политикой, независимо от владельца и прав. В SELinux файл, перемещённый (а не скопированный) в web root, сохраняет старую метку — классическая причина ошибок 403; restorecon это исправляет. Другие скрытые слои: атрибут immutable, установленный через chattr +i, который блокирует даже root, пока его не снимут; монтирование read-only или noexec; маски ACL, ограничивающие записи групп; и в контейнерах — user namespaces, отображающие root контейнера на непривилегированный UID хоста. Проверьте всё это, прежде чем трогать права.

bash
sudo aa-status                        # AppArmor profiles in enforce mode
sudo journalctl -k | grep -i apparmor  # AppArmor denials
ls -Z /var/www/html                    # SELinux labels (RHEL family)
sudo ausearch -m avc -ts recent        # SELinux denials
sudo restorecon -Rv /var/www/html      # reset labels to policy defaults
lsattr /etc/resolv.conf                # 'i' = immutable
getfacl /srv/app/storage | grep mask   # ACL mask limiting entries

10UID в контейнерах и томах

Ядро хранит числа, а не имена. Файл, принадлежащий UID 1000, принадлежит тому, у кого UID 1000 в текущей базе пользователей, а она различается на хосте и в образе контейнера. Поэтому каталоги, подключённые через bind mount, часто показывают странных владельцев или становятся недоступными для записи: процесс контейнера работает как UID 33 (www-data в образах Debian) или 1000 (node в официальных образах Node), а файлы на хосте принадлежат вашему deploy-пользователю. Решения, от самого простого: запускать контейнер с UID и GID хоста (user: "1000:1000" в Compose), сделать chown тома на UID контейнера или использовать именованные тома, которые инициализирует Docker. Rootless Docker и Podman добавляют user namespaces, так что root контейнера отображается на высокий непривилегированный UID хоста; это повышает безопасность, но сдвигает числа, которые вы видите на диске. DynamicUser в systemd в том же духе выделяет временный UID на каждый сервис.

bash
docker compose exec app id                  # UID inside the container
stat -c '%u:%g %n' ./storage                # numeric owner on the host
# docker-compose.yml
#   services:
#     app:
#       user: "1000:1000"                   # match the host deploy user
cat /etc/subuid                             # ranges for rootless containers

Реальные ситуации

01

Подрядчику нужен доступ только к одному сайту

Проблема

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

Как решить

Создайте для него личную учётную запись с собственным SSH-ключом и без пароля. Каждый сайт уже работает под своим пользователем, поэтому добавьте подрядчика только в группу этого сайта и убедитесь, что каталоги других сайтов имеют права 750 или строже, чтобы «остальные» не получали ничего. Если ему нужно перезапускать сервис сайта, добавьте через visudo одно правило в /etc/sudoers.d/, разрешающее ровно эту команду systemctl. Доступ на чтение логов сайта дайте через групповое владение или ACL, а не через членство в группе adm, которое открывает все логи. Задайте срок действия учётной записи через chage -E, просматривайте логи sudo в auth.log и удалите учётную запись, когда работа закончится.

02

SSH игнорирует правильный ключ

Проблема

Публичный ключ пользователя есть в authorized_keys, но SSH всё равно спрашивает пароль или падает с «Permission denied (publickey)». На другом сервере этот ключ работает.

Как решить

Смотрите на сторону сервера, а не клиента. sshd со StrictModes (по умолчанию) отвергает ключи, если домашний каталог, ~/.ssh или authorized_keys доступны на запись группе или остальным либо принадлежат кому-то другому. Запустите namei -l /home/user/.ssh/authorized_keys и исправьте через chmod go-w ~, chmod 700 ~/.ssh, chmod 600 ~/.ssh/authorized_keys и chown -R user:user ~/.ssh. Проверьте sudo journalctl -u ssh (sshd в RHEL) на строки «bad ownership or modes». В системах с SELinux восстановленному домашнему каталогу может понадобиться restorecon -Rv ~/.ssh. Также убедитесь, что строка не перенесена и что тип ключа поддерживается версией OpenSSH на сервере.

03

Файлы, созданные контейнером, принадлежат root

Проблема

После запуска composer install или npm ci внутри контейнера с проектом, подключённым через bind mount, deploy-пользователь на хосте больше не может обновлять или удалять vendor и node_modules.

Как решить

Процесс контейнера работал от root, поэтому созданные им файлы на хосте принадлежат UID 0. Один раз исправьте существующие файлы через sudo chown -R deploy:deploy vendor node_modules. Затем не дайте этому повториться: запускайте команды сборки с --user "$(id -u):$(id -g)" или задайте user: в Compose-файле, либо соберите образ с пользователем без root, чей UID совпадает с хостом. Для долгоживущих контейнеров приложения предпочитайте именованные тома или копируйте код в образ при сборке, а не подключайте дерево исходников через bind mount в продакшене. Не исправляйте это через chmod 777 на точке монтирования — это лишь превращает проблему в уязвимость.

Шпаргалка

Владелец, группа и права пути
stat -c '%U:%G %a %n' /srv/app/.env
Проверить доступ от имени другого пользователя
sudo -u www-data test -w /srv/app/storage && echo ok
Найти файлы, доступные на запись всем
find /srv -xdev -type f -perm -0002
Найти файлы без действительного владельца
find / -xdev \( -nouser -o -nogroup \)
Список setuid-бинарников
find / -xdev -perm -4000 -type f
Заблокировать учётную запись и сделать её просроченной
sudo usermod -L -e 1 contractor
Задать дату окончания учётной записи
sudo chage -E 2026-12-31 contractor
История использования sudo (Ubuntu)
sudo grep 'sudo:' /var/log/auth.log | tail
Разрешить слушать порт 80 без root
sudo setcap 'cap_net_bind_service=+ep' /path/to/binary
Сделать файл immutable / mutable
sudo chattr +i file / sudo chattr -i file

В моих реальных проектах

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

Вопросы и ответы

Q1Что означает chmod 755, и когда использовать 644 или 600?

Каждая цифра — это сумма чтения (4), записи (2) и выполнения (1) для владельца, группы и всех остальных. 755 — это rwxr-xr-x: владелец может всё, остальные могут читать и выполнять. Это подходит для каталогов, потому что x на каталоге позволяет в него войти, и для исполняемых скриптов. 644 — это rw-r--r--, обычный режим для файлов: владелец редактирует, остальные читают. 600 — это rw-------, читать и писать может только владелец, и именно это нужно приватным SSH-ключам, файлам .env и другим секретам. SSH вообще отказывается использовать ключи, доступные для чтения другим, так что режим не просто рекомендуется, а принудительно проверяется.

Q2Laravel-приложение выдаёт «Permission denied» при записи в storage. Как это исправить?

Сначала я выясняю, какой пользователь пишет: пользователь пула PHP-FPM, часто www-data, или отдельный пользователь сайта. Затем проверяю владельцев через ls -l и namei -l по всему пути. Обычно код был задеплоен или artisan-команда была запущена под root или пользователем deploy, поэтому файлы принадлежат кому-то другому. Я делаю владельцем пользователя deploy, а группой — группу веб-сервера, даю storage и bootstrap/cache право записи для группы с битом setgid (2775), чтобы новые файлы сохраняли группу, и в дальнейшем запускаю artisan-команды от пользователя приложения. 777 я никогда не использую: это просто позволило бы любому процессу менять приложение.

Q3Почему sudo, а не вход под root?

sudo даёт каждому человеку собственную учётную запись и логирует каждую привилегированную команду вместе с тем, кто её запустил, так что у вас есть журнал аудита. Он ограничивает время, на которое вы получаете права root, одной командой, что уменьшает случайный ущерб, и позволяет выдавать узкие права, например разрешить одному пользователю перезапускать один сервис. Отключение входа root по SSH ещё и убирает то самое имя учётки, которое каждый атакующий пробует первым. С root все делят один пароль, невозможно понять, кто что сделал, а одна опечатка в долгой сессии под root может навредить всей системе.

Q4Что такое umask?

umask — это маска битов прав, которые убираются у вновь создаваемых файлов и каталогов. Программы обычно запрашивают 666 для файлов и 777 для каталогов, а umask вычитает из этого. При распространённом значении 022 новые файлы получают 644, а каталоги 755: писать может только владелец. При 002 получается 664 и 775, то есть писать может и группа, что подходит команде или пользователю deploy и веб-серверу, разделяющим одну группу. При 077 всё новое становится приватным. Задать umask можно для shell, для пользователя в профильных файлах или для сервиса через UMask= в юните systemd.

Q5Почему каждое приложение должно работать под своим пользователем?

Потому что права — главная стена между процессами на одном сервере. Если все сайты работают от www-data, уязвимость в одном сайте позволяет атакующему прочитать конфиги и пароли от БД всех остальных сайтов и изменить их код. С отдельным пользователем на приложение каждое может читать только свои файлы и писать только в свои каталоги storage, так что взлом остаётся локализованным. Это также упрощает понимание, кому принадлежат потребление ресурсов и логи, и позволяет задавать лимиты systemd для каждого сервиса. У сервисного пользователя не должно быть ни login shell, ни прав sudo.

Q6Что такое ACL и когда их использовать?

Списки контроля доступа расширяют базовую модель владелец, группа и остальные дополнительными записями, например дают ещё одному пользователю право записи в один каталог без смены его группы. Управляют ими через setfacl, смотрят через getfacl, а ACL по умолчанию заставляют новые файлы наследовать правила. Они полезны, когда двум разным сервисам нужен разный доступ к одним и тем же файлам, или для общих каталогов загрузок. Минус — заметность: ls -l показывает только знак плюс, и при отладке их легко пропустить. Сначала я использую обычных владельца и группу и берусь за ACL только тогда, когда эта модель не может выразить нужное правило.

Вопросы уровня senior

S1Как спроектировать права на файлы PHP-приложения, чтобы взлом нанёс минимальный ущерб?

Разделите того, кто деплоит, и того, кто исполняет. Код принадлежит deploy-пользователю и доступен пользователю пула PHP-FPM только на чтение, поэтому атакующий, получивший выполнение кода через приложение, не сможет переписать приложение или спрятать в нём бэкдор. Для runtime-пользователя доступны на запись только каталоги, куда приложение обязано писать, — storage, cache и uploads, — а каталог загрузок отдаётся nginx без выполнения PHP и в идеале смонтирован с noexec. Секреты лежат в env-файле с правами 640, читаемом только runtime-группой. У каждого сайта свой пользователь пула, так что сайты не могут читать друг друга. Если возможно, добавьте ограничения systemd или AppArmor. Цена — трение: деплои и команды artisan нужно запускать от правильного пользователя, а это требует дисциплины в скриптах.

S2Безопасно ли давать deploy-пользователю sudo без пароля для systemctl?

Всё зависит от того, насколько узкое правило. Правило вида deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp.service с полным списком аргументов разрешает только это одно действие. Правило, разрешающее /usr/bin/systemctl с любыми аргументами, фактически даёт root: пользователь может редактировать юниты, запускать произвольные сервисы или открыть пейджер, из которого запускается shell. Wildcard опасны по той же причине, как и команды, запускающие редакторы или принимающие пути к файлам, например tee, cp или tar. Лучшие варианты — маленький скрипт-обёртка без аргументов, принадлежащий root, или правила polkit, разрешающие управлять одним конкретным юнитом. Что бы вы ни выбрали, ограничьте deploy-ключ, логируйте использование sudo и пересматривайте правила так, будто deploy-ключ однажды утечёт.

S3Приложение получает «Permission denied», но ls -l показывает правильные владельца и права. Что ещё проверить?

Сначала подтвердите идентичность процесса, а не пользователя, которым вы его считаете: ps -o user,group,supgrp -p PID, потому что членство в группах фиксируется при старте процесса, и группа, добавленная позже, не активна до рестарта. Во-вторых, пройдите путь через namei -l: каждому родительскому каталогу нужно право на выполнение. Затем посмотрите выше дискреционных прав: отказы AppArmor или SELinux в журнале ядра или audit-логе, записи и маски ACL через getfacl, атрибут immutable через lsattr и опции монтирования через findmnt, например read-only или noexec. Для сервисов systemd проверьте опции песочницы вроде ProtectSystem, ProtectHome или ReadOnlyPaths через systemctl cat. В контейнерах сравните числовые UID внутри и снаружи — имена могут ввести в заблуждение.

Глоссарий

UID / GID
Числовые идентификаторы пользователя и группы. Ядро хранит и сравнивает именно числа; имена — лишь таблица соответствия.
Effective UID
Идентичность, которую ядро использует для проверки прав. При запуске setuid-программы она отличается от реального UID.
Дополнительные группы
Группы, в которые процесс входит помимо основной; фиксируются при старте сессии.
Бит setuid
Бит прав, из-за которого программа запускается от имени владельца файла, часто root, а не пользователя, который её запустил.
Capability
Отдельная часть привилегий root, например право слушать низкие порты, которую можно выдать процессу отдельно.
MAC
Mandatory access control: общесистемная политика (SELinux, AppArmor), которая может запретить доступ, даже если права файла его разрешают.
Маска ACL
Запись ACL, ограничивающая максимальные права именованных пользователей и групп. chmod на групповые биты меняет её.
User namespace
Механизм ядра, отображающий UID внутри контейнера на другие, обычно непривилегированные, UID хоста.
StrictModes
Настройка sshd, включённая по умолчанию: отвергает ключи, если у домашнего каталога или .ssh небезопасные владелец или права.

Есть идея продукта?Давайте создадим его.

Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.