Секреты сервиса в systemd: credentials вместо EnvironmentFile
Практический способ передать токен Linux-сервису через зашифрованные credentials systemd, проверить совместимость, запуск и ротацию без вывода секрета в журнал.
- ПодготовкаАдминистратор временно помещает значение в закрытый файл под /run, не передавая его в командной строке.
- Шифрованиеsystemd-creds создаёт аутентифицированный контейнер, связанный с ключом хоста и при наличии TPM2 — с оборудованием.
- АктивацияLoadCredentialEncrypted расшифровывает значение только при запуске unit и размещает его в каталоге credentials сервиса.
- ЧтениеПриложение получает путь через %d или CREDENTIALS_DIRECTORY; само значение не попадает в Environment и аргументы процесса.
EnvironmentFile= удобен для обычных настроек, но пароль или токен в нём остаётся простым файлом, а значение после загрузки становится переменной окружения процесса. Для чувствительных данных у systemd есть отдельный механизм credentials: сервис получает секрет как закрытый файл только на время своей работы.
Ниже — практический вариант с LoadCredentialEncrypted=. Он подходит для токена API, пароля к внешнему сервису или закрытого ключевого материала небольшого размера. В примере само значение не появляется в unit-файле, командной строке, выводе systemctl status и журнале.
Сначала проверяем версию systemd
Поддержка каталога credentials и LoadCredential= появилась в systemd 247, а утилита systemd-creds с шифрованием — в версии 250. Поэтому первым делом проверяем фактическую версию на сервере:
systemd --version
systemd-creds --version
Если второй команды нет или версия ниже 250, не добавляйте LoadCredentialEncrypted=: старый менеджер не сможет разобрать unit. Сначала обновите систему штатным способом либо используйте временный вариант с закрытым файлом и LoadCredential=, понимая, что исходник останется на диске в открытом виде.
Почему credentials лучше переменной с секретом
systemd создаёт для unit отдельный каталог, доступный пользователю сервиса и root. Путь к нему доступен как $CREDENTIALS_DIRECTORY, а внутри unit его можно записать короче — спецификатором %d. В переменную окружения приложения передаётся только путь, а не содержимое токена.
Это важно по двум причинам:
- секрет не наследуется дочерними процессами как обычная переменная окружения;
- при файловой изоляции каталог credentials не виден другим сервисам.
Credentials существуют только во время активности unit, доступны только для чтения и по возможности размещаются в неподкачиваемой памяти. Шифрование защищает значение при хранении: systemd расшифровывает его непосредственно при запуске сервиса.
Создаём зашифрованный credential
Готовим закрытый временный файл
Не вводите токен после echo: он может остаться в истории оболочки или попасть в аудит команд. Создадим файл с правами только для root в /run, отредактируем его и сразу после шифрования удалим:
sudo install -d -m 0700 /etc/credstore.encrypted
sudo install -m 0600 /dev/null /run/report-api-token
sudoedit /run/report-api-token
Имя report-api-token — не секрет, а назначение credential. Оно должно оставаться одинаковым в команде шифрования и в unit.
Шифруем и удаляем открытый текст
sudo systemd-creds encrypt \
--name=report-api-token \
/run/report-api-token \
/etc/credstore.encrypted/report-api-token
sudo rm -f /run/report-api-token
sudo chmod 0600 /etc/credstore.encrypted/report-api-token
В режиме по умолчанию systemd-creds выбирает доступный защищённый ключ: ключ хоста, TPM2 либо их сочетание. Не применяйте --with-key=null для рабочего секрета — этот режим не обеспечивает конфиденциальность и аутентичность.
Зашифрованный файл нельзя бездумно переносить между серверами. Если контейнер связан с ключом хоста или TPM2, расшифровать его должна именно та установка, для которой он был создан.
Подключаем credential к сервису
Предположим, приложение умеет читать токен из файла, путь к которому задаётся переменной APP_TOKEN_FILE. Unit может выглядеть так:
[Unit]
Description=Report worker
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=report-worker
Group=report-worker
LoadCredentialEncrypted=report-api-token:/etc/credstore.encrypted/report-api-token
Environment=APP_TOKEN_FILE=%d/report-api-token
ExecStartPre=/usr/bin/test -s %d/report-api-token
ExecStart=/usr/local/bin/report-worker
PrivateMounts=yes
NoNewPrivileges=yes
Restart=on-failure
[Install]
WantedBy=multi-user.target
ExecStartPre= проверяет, что расшифрованный файл существует и не пуст, но не печатает его содержимое. Если приложение само понимает $CREDENTIALS_DIRECTORY, строка Environment= не нужна. Если ему требуется аргумент с путём, передавайте путь к файлу, но не подставляйте значение через $(cat ...): секрет окажется в списке процессов.
PrivateMounts=yes добавляет файловую изоляцию. Она особенно полезна сервису, который работает с credentials; некоторые более строгие параметры sandboxing уже включают отдельное пространство монтирования косвенно.
Проверяем unit до запуска
Сначала проверяем синтаксис, затем перечитываем конфигурацию и запускаем сервис:
sudo systemd-analyze verify /etc/systemd/system/report-worker.service
sudo systemctl daemon-reload
sudo systemctl start report-worker.service
Результат проверяем без вывода самого credential:
systemctl show report-worker.service \
-p ActiveState -p SubState -p MainPID
sudo systemctl status report-worker.service --no-pager
sudo journalctl -u report-worker.service --since "-10 min" --no-pager
Ожидаемый признак успеха — ActiveState=active и ненулевой MainPID. Если credential отсутствует, не расшифровывается или пуст, unit завершится на этапе подготовки либо на ExecStartPre.
Диагностика типичных ошибок
Unknown key name LoadCredentialEncrypted
Менеджер systemd слишком старый. Сверьте systemd --version и не ориентируйтесь только на версию дистрибутива или пакета из документации.
Не удалось расшифровать credential
Проверьте журнал unit и три условия:
- файл создан на текущем хосте или зашифрован совместимым способом;
- встроенное имя
report-api-tokenсовпадает с именем вLoadCredentialEncrypted=; - у service manager есть доступ к зашифрованному файлу.
Не используйте systemd-creds cat в диагностических скриптах и не направляйте расшифрованное значение в journal: эта команда предназначена для вывода содержимого.
Сервис стартует вручную, но не через systemd
Проверьте, что приложение действительно читает APP_TOKEN_FILE, а не ожидает сам токен. Также учитывайте пользователя из User= и рабочий каталог. Общий маршрут такой диагностики разобран в статье «Служба systemd не запускается: короткий маршрут диагностики».
Безопасная ротация токена
Новый credential лучше подготовить рядом со старым, проверить возможность расшифрования без вывода содержимого, затем заменить файл атомарно и перезапустить сервис:
sudo install -m 0600 /dev/null /run/report-api-token
sudoedit /run/report-api-token
sudo systemd-creds encrypt \
--name=report-api-token \
/run/report-api-token \
/etc/credstore.encrypted/report-api-token.new
sudo systemd-creds decrypt \
--name=report-api-token \
/etc/credstore.encrypted/report-api-token.new \
/dev/null
sudo rm -f /run/report-api-token
sudo mv /etc/credstore.encrypted/report-api-token.new \
/etc/credstore.encrypted/report-api-token
sudo systemctl restart report-worker.service
После перезапуска повторите проверку ActiveState, SubState и журнала. Старый процесс не получит новое значение автоматически: credentials неизменяемы в течение одного запуска unit.
Что считать готовым результатом
- unit проходит
systemd-analyze verify; - сервис активен, а предварительная проверка непустого credential завершилась успешно;
- секрет отсутствует в unit, аргументах процесса и журнале;
- зашифрованный файл лежит отдельно от кода приложения;
- ротация выполняется заменой контейнера и контролируемым перезапуском.
Credentials решают задачу доставки секрета, но не заменяют остальные меры защиты unit. После настройки полезно отдельно проверить ограничения пользователя, файловой системы и capabilities. Для периодических операций лучше не встраивать расписание в скрипт, а использовать отдельный timer — пример есть в материале «Таймер systemd: расписание отдельно от команды».
Первичные источники
- System and Service Credentials — модель credentials, каталог, изоляция и шифрование.
- systemd.exec — параметры
LoadCredential=иLoadCredentialEncrypted=. - systemd-creds — команды шифрования, проверки и варианты ключей.
- Документация в репозитории systemd — актуальная версия описания механизма в исходном проекте.
