Новинка Виртуальный VDS сервер в Нидерландах от 490р
Выберите продукт

Linux sudoers.d и visudo: безопасный sudo для deploy user

Показываем, как выдать deploy user только нужные sudo-права для выкладки приложения: создать правило в sudoers.d, проверить его через visudo, ограничить NOPASSWD и избежать лишнего root-доступа на Linux-сервере.
Linux sudoers.d и visudo: безопасный sudo для deploy user

На сервере для деплоя почти всегда появляется отдельная учётная запись: deploy, ci, gitlab-runner, github-runner или похожая. Ей нужно залить релиз, переключить symlink, перезапустить сервис, перечитать конфигурацию Nginx, иногда выполнить миграции. Самый быстрый путь — добавить пользователя в группу sudo или wheel и забыть. Самый правильный путь — дать только те команды, которые реально нужны, и хранить это правило отдельно в /etc/sudoers.d.

В этой статье разберём практическую настройку sudoers для deploy user: как создать пользователя, как работать с sudoers.d, почему редактировать нужно через visudo, где уместен NOPASSWD, а где он превращает деплой в почти root-доступ. Примеры подойдут для Debian, Ubuntu, AlmaLinux, Rocky Linux, CentOS Stream и других RHEL-based систем, если в них используется обычный пакет sudo.

Главная идея: sudo для деплоя должен быть не удобной заменой root, а узким шлюзом к нескольким проверенным операциям.

Такая настройка особенно важна на production-серверах и VDS, где один технический пользователь часто обслуживает приложение, веб-сервер, очереди и CI/CD. Чем уже его права, тем меньше последствия ошибки в pipeline или утечки SSH-ключа.

Что такое sudoers и зачем нужен каталог sudoers.d

sudo решает, можно ли пользователю выполнить команду от имени другого пользователя, по правилам из конфигурации sudoers. Основной файл обычно находится по пути /etc/sudoers. В нём задаются глобальные параметры, группы администраторов и подключение каталога /etc/sudoers.d.

Редактировать всё в одном большом файле неудобно: правила смешиваются, сложнее понять, кто и зачем получил доступ, выше риск сломать синтаксис. Поэтому для отдельных ролей обычно создают отдельные файлы: например, /etc/sudoers.d/90-deploy, /etc/sudoers.d/80-backup, /etc/sudoers.d/70-monitoring.

У такого подхода есть несколько плюсов. Во-первых, правило для deploy user можно удалить или отключить одним файлом. Во-вторых, его легко положить под управление Ansible, Salt, Puppet или другого инструмента конфигурации. В-третьих, при аудите проще ответить на вопрос: какие привилегии есть у конкретной технической учётной записи.

Есть важная деталь: для файлов в /etc/sudoers.d лучше использовать имена без точки и без временных суффиксов редакторов. Надёжный вариант — 90-deploy, а не deploy.conf и не deploy.sudoers. На многих системах sudo игнорирует файлы с точкой или заканчивающиеся на ~, чтобы случайно не загрузить резервные копии редактора.

Почему нельзя править sudoers обычным редактором

Файл sudoers чувствителен к синтаксису. Одна лишняя запятая, неправильный alias или незакрытое правило — и вы можете получить ситуацию, когда sudo перестанет работать для всех. На удалённом сервере это особенно неприятно: если root-вход по SSH отключён, а активной root-сессии нет, исправление превратится в задачу через rescue-режим или консоль провайдера.

visudo защищает от такой ошибки. Он открывает файл в редакторе, блокирует одновременное редактирование и перед сохранением проверяет синтаксис. Для отдельного файла в sudoers.d используется ключ -f.

sudo visudo -f /etc/sudoers.d/90-deploy

Проверить всю конфигурацию можно отдельной командой:

sudo visudo -c

Если sudo уже сломан, эти команды нужно выполнять из root-сессии без префикса sudo. Хорошая привычка перед любыми изменениями — открыть вторую SSH-сессию и держать её активной до проверки. Так у вас останется рабочий канал для отката.

Проверка синтаксиса sudoers через visudo перед применением правил

Проверяем sudo и создаём deploy user

На большинстве серверных образов sudo уже установлен. Но на минимальных шаблонах Linux это не всегда так. Сначала проверим наличие утилит:

command -v sudo
command -v visudo
sudo -V | head -n 1

Debian и Ubuntu

Если sudo не установлен, зайдите под root или используйте консоль сервера и установите пакет:

apt update
apt install sudo

AlmaLinux, Rocky Linux, CentOS Stream, Oracle Linux

В RHEL-based дистрибутивах пакет обычно тоже есть в базовой системе, но на минимальном образе его можно поставить так:

dnf install sudo

Теперь создадим отдельного пользователя для деплоя. Команды ниже одинаково подходят для Debian, Ubuntu, AlmaLinux, Rocky Linux и большинства близких систем, потому что useradd, passwd и install есть в базовой системе.

sudo useradd --create-home --shell /bin/bash deploy
sudo passwd --lock deploy
sudo install -d -o deploy -g deploy -m 0700 /home/deploy/.ssh
sudo install -o deploy -g deploy -m 0600 /tmp/deploy_authorized_keys /home/deploy/.ssh/authorized_keys

Здесь предполагается, что публичный SSH-ключ заранее лежит в файле /tmp/deploy_authorized_keys. Блокировка пароля через passwd --lock запрещает парольный вход, но не мешает SSH-аутентификации по ключу. Это нормальная практика для технических пользователей: доступ должен быть через ключи, а не через общий пароль, который неизбежно окажется в чате или CI-переменной.

Проверить пользователя можно так:

id deploy
sudo -l -U deploy

Пока правил нет, sudo -l -U deploy либо покажет отсутствие привилегий, либо выведет только общие defaults. Это хороший исходный уровень: deploy user существует, но ещё ничего не может делать от root.

Модель least privilege для деплоя

Принцип least privilege означает: пользователь получает минимальный набор прав, достаточный для работы, и ничего сверх этого. Для деплоя это особенно важно, потому что CI/CD часто запускается автоматически: по push в репозиторий, по тегу, по webhook или по расписанию. Если pipeline скомпрометирован, злоумышленник унаследует ровно те права, которые вы дали deploy user.

Типовые задачи деплоя лучше разделить на две группы. Первая — операции с файлами приложения: загрузить новый релиз, распаковать архив, обновить зависимости, переключить символическую ссылку, очистить кеш в каталоге проекта. Для этого обычно не нужен sudo. Правильнее настроить владельца, группу, ACL или setgid-бит на директориях приложения.

Вторая группа — операции с системными сервисами: systemctl reload nginx, systemctl restart myapp.service, запуск безопасного скрипта, который делает ровно одну административную задачу. Вот для этой части sudo может быть оправдан.

Плохое правило выглядит так:

deploy ALL=(ALL) NOPASSWD: ALL

Оно удобно, но по смыслу почти не отличается от выдачи root-доступа. Если deploy user может выполнить любую команду через sudo без пароля, компрометация его SSH-ключа или CI-токена становится компрометацией всего сервера.

Лучше идти от списка конкретных действий. Например: деплой может проверить конфигурацию Nginx, перечитать Nginx и перезапустить один application service. Он не должен уметь устанавливать пакеты, менять пользователей, редактировать системные unit-файлы, читать приватные ключи TLS или открывать произвольный root shell.

Базовое правило в sudoers.d для deploy user

Перед тем как писать правило, узнайте реальные пути к командам. В sudoers лучше указывать абсолютные пути, потому что команда без пути зависит от окружения и PATH.

command -v systemctl
command -v nginx

На современных Debian, Ubuntu, AlmaLinux и Rocky Linux systemctl обычно находится в /usr/bin/systemctl, а nginx — в /usr/sbin/nginx. Но проверять всё равно стоит: серверы бывают с нестандартными пакетами и кастомными путями.

Откроем файл правила:

sudo visudo -f /etc/sudoers.d/90-deploy

Пример минимального правила:

Defaults:deploy env_reset
Defaults:deploy secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Cmnd_Alias DEPLOY_NGINX = /usr/sbin/nginx -t, /usr/bin/systemctl reload nginx.service
Cmnd_Alias DEPLOY_APP = /usr/bin/systemctl restart myapp.service

deploy ALL=(root) NOPASSWD: DEPLOY_NGINX, DEPLOY_APP

Разберём по строкам. Defaults:deploy env_reset сбрасывает окружение для конкретного пользователя. secure_path задаёт безопасный путь поиска программ при выполнении sudo-команд. Cmnd_Alias объединяет разрешённые команды в читаемые группы. Последняя строка разрешает пользователю deploy запускать эти команды от имени root без пароля.

После сохранения обязательно проверьте синтаксис:

sudo visudo -c

И посмотрите итоговые права пользователя:

sudo -l -U deploy

Тестировать лучше из отдельной SSH-сессии под deploy:

sudo /usr/sbin/nginx -t
sudo /usr/bin/systemctl reload nginx.service
sudo /usr/bin/systemctl restart myapp.service

Если команда не совпадает с правилом по пути или аргументам, sudo её не разрешит. Это не баг, а нужная нам защита. Например, если разрешено /usr/bin/systemctl restart myapp.service, то /usr/bin/systemctl restart sshd.service не должен проходить.

NOPASSWD: когда можно, а когда лучше не надо

NOPASSWD часто используют для CI/CD, потому что pipeline не умеет вводить интерактивный пароль. Сам по себе NOPASSWD не является злом: проблема начинается, когда он применяется к слишком широкому набору команд.

Допустимый вариант: NOPASSWD на один-два безопасных systemctl действия или на root-owned wrapper script, который нельзя изменять пользователю deploy. Опасный вариант: NOPASSWD на ALL, на пакетный менеджер, на редактор, на shell, на интерпретаторы вроде Python, Perl, Ruby, PHP, Node.js, на tar, rsync, find, vim, less и другие инструменты, через которые можно выполнить произвольную команду.

Особенно аккуратно с wildcard. В sudoers символ * может оказаться шире, чем кажется. Правило с произвольными аргументами для команды почти всегда требует отдельного анализа. Если нужно сложное поведение, лучше вынести его в небольшой скрипт, принадлежащий root, и разрешить через sudo только запуск этого скрипта без аргументов или с жёстко проверяемыми аргументами.

Wrapper script вместо сложных sudoers-правил

Представим, что деплой должен выполнить несколько действий: проверить Nginx, перечитать его и перезапустить application service. Можно перечислить все команды в sudoers, но иногда удобнее сделать один административный скрипт. Главное условие: deploy user не должен иметь права изменять этот скрипт или каталог, где он лежит.

Создадим файл /usr/local/sbin/deploy-reload-myapp от root:

sudo install -o root -g root -m 0755 /dev/null /usr/local/sbin/deploy-reload-myapp
sudoedit /usr/local/sbin/deploy-reload-myapp

Содержимое скрипта:

#!/bin/sh
set -eu
/usr/sbin/nginx -t
/usr/bin/systemctl reload nginx.service
/usr/bin/systemctl restart myapp.service

Проверим владельца и права:

sudo chown root:root /usr/local/sbin/deploy-reload-myapp
sudo chmod 0755 /usr/local/sbin/deploy-reload-myapp
ls -l /usr/local/sbin/deploy-reload-myapp

Теперь правило sudoers становится короче:

Defaults:deploy env_reset
Defaults:deploy secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

deploy ALL=(root) NOPASSWD: /usr/local/sbin/deploy-reload-myapp

Плюс такого подхода — в sudoers нет сложной логики. Минус — нужно внимательно следить за правами на скрипт и все файлы, которые он использует. Если deploy user может изменить скрипт, unit-файл сервиса или исполняемый файл приложения, который запускается как root, ограничение теряет смысл.

Безопасный wrapper script для деплоя и перезапуска сервиса

Особенности systemctl и сервисов приложения

systemctl restart myapp.service безопасен только настолько, насколько безопасен сам unit. Если unit запускает приложение от пользователя www-data, nginx, apache или отдельного app, это одна история. Если сервис запускается от root и исполняемый файл лежит в каталоге, доступном deploy user на запись, это уже путь к повышению привилегий.

Для web-приложений лучше, чтобы systemd service запускался от отдельного непривилегированного пользователя. Деплой обновляет файлы приложения, а сервис при рестарте читает их уже с нужными правами. Если приложению нужен bind к порту ниже 1024, лучше решать это через reverse proxy, capability для конкретного бинарника или socket activation, а не через запуск всего приложения от root.

Не давайте deploy user право на systemctl daemon-reload, если он может менять unit-файлы. Комбинация изменения unit и daemon-reload часто эквивалентна root-доступу. То же касается systemctl edit, systemctl enable, systemctl start для произвольных сервисов и любых правил с широким wildcard по имени сервиса. Если приложение упирается в ограничения systemd по памяти или CPU, лучше настраивать их отдельно: пригодится разбор про лимиты systemd для сервисов.

Debian, Ubuntu и RHEL-based: что отличается

Синтаксис sudoers одинаковый во всех этих дистрибутивах, но отличаются группы администраторов, имена сервисов и иногда SELinux-контекст на RHEL-based системах.

В Debian и Ubuntu полные административные права обычно выдаются через группу sudo:

getent group sudo

В AlmaLinux, Rocky Linux, CentOS Stream и Oracle Linux чаще используется группа wheel:

getent group wheel

Для нашего сценария добавлять deploy user в эти группы не нужно. Наоборот, если пользователь уже там, это стоит пересмотреть:

id deploy
sudo gpasswd -d deploy sudo
sudo gpasswd -d deploy wheel

Команды удаления из группы могут завершиться сообщением, что пользователь не является членом группы или что такой группы нет. Это нормально. Важно состояние после проверки id deploy: deploy user не должен быть глобальным администратором, если его задача — только деплой.

Имена PHP-FPM сервисов тоже отличаются. В Debian и Ubuntu сервис часто называется по версии, например php8.3-fpm.service. В RHEL-based системах из стандартных репозиториев чаще встречается php-fpm.service. Поэтому перед созданием правила проверьте:

systemctl list-unit-files | grep fpm
systemctl status nginx.service --no-pager

Не копируйте sudoers-правило вслепую между серверами. Лучше параметризовать имена сервисов в Ansible или хотя бы держать отдельный файл правила для каждой роли сервера.

Права на файлы проекта без sudo

Частая причина избыточного sudo — неправильно настроенные права на каталог приложения. Если deploy user вынужден делать sudo chown, sudo chmod или sudo rsync при каждом релизе, стоит сначала пересмотреть модель владения файлами.

Простой вариант: код приложения принадлежит пользователю deploy, а веб-сервер или PHP-FPM имеют только чтение там, где это возможно. Каталоги для runtime-записи — uploads, cache, sessions, storage — выделяются отдельно и имеют права для пользователя процесса приложения.

Для командной работы удобно использовать общую группу, например webapp, и setgid-бит на каталогах:

sudo groupadd --system webapp
sudo usermod -aG webapp deploy
sudo usermod -aG webapp www-data
sudo chgrp -R webapp /var/www/myapp
sudo chmod 2775 /var/www/myapp

На AlmaLinux и Rocky Linux вместо www-data часто используется пользователь nginx или apache, в зависимости от стека:

sudo usermod -aG webapp nginx
sudo usermod -aG webapp apache

Не обязательно выполнять обе команды: выберите фактического пользователя вашего веб-сервера или PHP-FPM пула. Его можно посмотреть в конфигурации сервиса, PHP-FPM pool или через процессы:

ps -eo user,group,comm | grep php-fpm
ps -eo user,group,comm | grep nginx

Если файловая модель настроена правильно, sudo остаётся только для узких системных операций. Это проще поддерживать и намного безопаснее.

Аудит: как понять, что deploy user реально может

После настройки полезно не только проверить синтаксис, но и посмотреть эффективные права пользователя:

sudo -l -U deploy

Из-под самого пользователя:

sudo -l

Если в выводе внезапно видите ALL, пакетный менеджер, интерпретаторы, редакторы или shell, это повод остановиться и пересмотреть правило. Для deploy user нормальный вывод должен быть скучным: несколько конкретных команд с абсолютными путями.

Логи sudo обычно попадают в journald или системный auth log. На Debian и Ubuntu часто полезны такие команды:

sudo journalctl -t sudo
sudo grep sudo /var/log/auth.log

На AlmaLinux, Rocky Linux, CentOS Stream и Oracle Linux:

sudo journalctl -t sudo
sudo grep sudo /var/log/secure

По логам можно увидеть, кто запускал команду, от чьего имени, из какого TTY или с какой сессии. Для CI/CD дополнительно полезно логировать идентификатор pipeline на уровне самого deploy-скрипта, но не через расширение sudo до произвольных команд.

Типовые ошибки в sudoers.d

Первая ошибка — файл с неправильными правами. Файлы sudoers должны принадлежать root и обычно иметь режим 0440. Если права слишком широкие, sudo может отказаться читать файл.

sudo chown root:root /etc/sudoers.d/90-deploy
sudo chmod 0440 /etc/sudoers.d/90-deploy
sudo visudo -c

Вторая ошибка — правило не применяется из-за имени файла. Используйте имя без точки: 90-deploy. Если файл называется deploy.conf, переименуйте и проверьте конфигурацию.

sudo mv /etc/sudoers.d/deploy.conf /etc/sudoers.d/90-deploy
sudo visudo -c

Третья ошибка — путь к команде не совпадает. В sudoers указан /bin/systemctl, а на сервере команда находится в /usr/bin/systemctl. Или разрешён systemctl restart myapp, а CI запускает systemctl restart myapp.service. Для sudoers это разные команды.

Четвёртая ошибка — разрешить команду, которая сама открывает интерактивный pager или shell. Например, systemctl status без --no-pager может быть нежелателен в интерактивной сессии. Если статус действительно нужен, лучше разрешать точную форму:

deploy ALL=(root) NOPASSWD: /usr/bin/systemctl --no-pager status myapp.service

Пятая ошибка — пытаться решить sudoers-правилом проблемы файловых прав. Если deploy не может записать релиз в /var/www/myapp, не спешите давать ему sudo rsync. Чаще правильнее изменить владельца, группу, ACL или структуру каталогов.

Безопасный откат, если sudo сломался

Перед изменениями держите активную root-сессию или консоль сервера. Если после сохранения visudo -c показывает ошибку, не закрывайте терминал. Откройте проблемный файл снова:

visudo -f /etc/sudoers.d/90-deploy

Если нужно быстро отключить правило, можно переименовать файл так, чтобы sudo его не читал, а затем проверить конфигурацию:

mv /etc/sudoers.d/90-deploy /etc/sudoers.d/90-deploy.disabled
visudo -c

После исправления верните файл на место:

mv /etc/sudoers.d/90-deploy.disabled /etc/sudoers.d/90-deploy
chmod 0440 /etc/sudoers.d/90-deploy
visudo -c

Если доступа через sudo уже нет и root-сессии тоже нет, остаётся консоль управления сервером или rescue-режим. Поэтому простое правило эксплуатации: любые изменения sudoers сначала проверяются в отдельной сессии, а текущая рабочая сессия не закрывается до успешного теста.

Чеклист для production-сервера

  • Deploy user создан отдельно и входит только в нужные прикладные группы.
  • Парольный вход для deploy user отключён, доступ выполняется по SSH-ключу.
  • Пользователь не состоит в группах sudo и wheel, если ему не нужен полный root-доступ.
  • Правило хранится в /etc/sudoers.d/90-deploy, имя файла без точки.
  • Файл принадлежит root и имеет права 0440.
  • Все изменения выполняются через visudo -f, затем проверяются через visudo -c.
  • В sudoers указаны абсолютные пути к командам.
  • NOPASSWD применяется только к конкретным безопасным командам или root-owned wrapper script.
  • Нет разрешений на ALL, shell, редакторы, пакетные менеджеры и произвольные интерпретаторы.
  • Файловые права приложения настроены так, чтобы деплой не требовал лишнего sudo.

Хорошо настроенный sudoers.d почти незаметен в ежедневной работе: CI спокойно выкладывает релизы, администратор видит понятные логи, а deploy user не превращается в запасной root-аккаунт. Это именно тот случай, когда несколько строгих строк в sudoers экономят часы расследований и снижают риск серьёзного инцидента на сервере.

Поделиться статьей

Вам будет интересно

Linux capabilities: как слушать 80 и 443 без запуска приложения от root OpenAI Статья написана AI (GPT 5)

Linux capabilities: как слушать 80 и 443 без запуска приложения от root

Показываем, как разрешить приложению bind privileged ports 80 и 443 без запуска от root: через setcap, cap_net_bind_service и syst ...
Linux ACL и setgid для deploy directory: деплой без chmod 777 OpenAI Статья написана AI (GPT 5)

Linux ACL и setgid для deploy directory: деплой без chmod 777

Разберём безопасную схему прав для каталога деплоя: кто владеет файлами, как работают setgid и ACL, какие права нужны Nginx/Apache ...
Docker Compose healthcheck и depends_on: чем заменить sleep при ожидании базы OpenAI Статья написана AI (GPT 5)

Docker Compose healthcheck и depends_on: чем заменить sleep при ожидании базы

Если контейнер приложения стартует раньше PostgreSQL, MySQL или Redis, не спешите добавлять sleep. Разберём, как правильно описать ...