Управление учётными данными в Systemd

Управление учётными данными в Systemd


В версии 247 systemd получил систему управления учётными данными. Она позволяет передавать чувствительную информацию от системы в юнит. Основной сценарий использования — передача конфигурации, паролей, приватных ключей и прочего подобного. Подробнее эта функциональность описана в systemd.exec(5).

В Release Note нововведение было описано следующим образом:

Появилась поддержка шифрованных и аутентифицированных учётных данных.
   Она расширяет логику, появившуюся в v247, добавляя возможность
   неинтерактивного симметричного шифрования и проверки подлинности.
   Для этого используется ключ, который хранится в /var/ или в чипе
   TPM2 (если он есть), или сразу оба источника (по умолчанию, если
   TPM2 присутствует, применяется комбинированный вариант, иначе —
   только ключ в /var/). Учётные данные автоматически расшифровываются
   в момент запуска сервиса и предоставляются ему уже в открытом виде.
   Новый инструмент `systemd-creds` позволяет заранее зашифровать данные
   для такого использования, а новые параметры в файле сервиса —
   LoadCredentialEncrypted= и SetCredentialEncrypted= — настраивают работу
   с такими учётными данными.

   Эта возможность полезна для того, чтобы хранить чувствительные вещи,
   вроде SSL-сертификатов, паролей и подобного, в зашифрованном виде
   «на покое», расшифровывая их только при необходимости — и так, чтобы
   всё это было привязано к конкретной установке ОС или оборудованию.

Выглядит как минимум любопытно. Давайте посмотрим, как это работает на практике!

Как сказано в релизнотах, systemd-creds будет использовать чип TPM2, если он есть. Если его нет, используется секрет из /var/lib/systemd/credential.secret. Так что сначала проверим, включён ли у нас TPM. Это можно сделать, посмотрев содержимое файла:

cat /sys/class/tpm/tpm0/tpm_version_major

Если доступен TPM 2.0, вывод команды будет 2.

Сначала нужно зашифровать наши старые учётные данные. Это делается через systemd-creds. Команда принимает входной и выходной файл. Входной файл — наш старый, незашифрованный файл с секретом, выходной — уже зашифрованный:

systemd-creds encrypt secrets/old/token_unencrypted secrets/token

После этого остаётся только поменять настройку в нашем .service-файле (это пример сервиса для бота, который я гоняю на своём сервере):

[Unit]
Description=My Service
After=network-online.target

[Service]
ExecStart=/usr/bin/important-service
Type=exec
user=foo
WorkingDirectory=/home/foo

# Hardening
PrivateDevices=true
ProtectControlGroups=true
ProtectSystem=full
ProtectKernelTunables=true
RestrictSUIDSGID=true
PrivateTmp=true
PrivateUsers=true
ProtectClock=true
ProtectHome=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectProc=noaccess
# Everything is RO, we can only write to ReadWritePaths
ProtectSystem=strict
RemoveIPC=true
UMask=0077
ProtectHostname=true
NoNewPrivileges=true

CapabilityBoundingSet=
RestrictNamespaces=true
RestrictAddressFamilies=AF_INET AF_INET6

# Secrets
LoadCredentialEncrypted=token:/home/foo/secrets/token


[Install]
WantedBy=multi-user.target

Важно: ID токена (имя перед двоеточием) должен совпадать с именем файла секрета.

После этого вроде бы всё должно заработать, верно? Не совсем. Когда я впервые запустил юнит, он сразу же упал. Сможете угадать, в чём была проблема? Я даже приведу вам логи ошибки из юнита:

Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tcti-device.c:442:Tss2_Tcti_Device_Init() Failed to open specified TCTI device file /dev/tpmrm0: Operation not permitted
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tctildr-dl.c:154:tcti_from_file() Could not initialize TCTI file: libtss2-tcti-device.so.0
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tcti-device.c:442:Tss2_Tcti_Device_Init() Failed to open specified TCTI device file /dev/tpm0: Operation not permitted
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tctildr-dl.c:154:tcti_from_file() Could not initialize TCTI file: libtss2-tcti-device.so.0
Jan 11 14:19:57 examplehost service[13245]: WARNING:tcti:src/util/io.c:252:socket_connect() Failed to connect to host 127.0.0.1, port 2321: errno 111: Connection refused
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tcti-swtpm.c:591:Tss2_Tcti_Swtpm_Init() Cannot connect to swtpm TPM socket
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tctildr-dl.c:154:tcti_from_file() Could not initialize TCTI file: libtss2-tcti-swtpm.so.0
Jan 11 14:19:57 examplehost service[13245]: WARNING:tcti:src/util/io.c:252:socket_connect() Failed to connect to host 127.0.0.1, port 2321: errno 111: Connection refused
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tctildr-dl.c:154:tcti_from_file() Could not initialize TCTI file: libtss2-tcti-mssim.so.0
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tctildr-dl.c:254:tctildr_get_default() No standard TCTI could be loaded
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tctildr.c:428:Tss2_TctiLdr_Initialize_Ex() Failed to instantiate TCTI
Jan 11 14:19:57 examplehost service[13245]: ERROR:esys:src/tss2-esys/esys_context.c:69:Esys_Initialize() Initialize default tcti. ErrorCode (0x000a000a)
Jan 11 14:19:57 examplehost systemd[13245]: Failed to initialize TPM context: tcti:IO failure
Jan 11 14:19:57 examplehost systemd[13244]: sergeantbot-staging.service: Failed to set up credentials: Protocol error
Jan 11 14:19:57 examplehost systemd[13244]: sergeantbot-staging.service: Failed at step CREDENTIALS spawning /srv/bot/prod/service: Protocol error

Проблема была в одном из параметров hardening. Конкретно — в PrivateDevices=true. Если эта опция включена, systemd даёт доступ только к очень ограниченному набору псевдоустройств (null, zero, pty). Так что делать это нужно по-другому. Один вариант — просто отключить эту опцию. Но мне такой подход не очень нравится. Другой вариант — использовать опцию DevicePolicy. В ней можно задать политику доступа к устройствам. Доступно два режима: strict и closed. closed разрешает доступ к псевдоустройствам, а strict полностью запрещает доступ ко всем устройствам. Поскольку доступ к определённым псевдоустройствам нас не беспокоит, поставим пока closed. Затем мы можем разрешить конкретные устройства через DeviceAllow. Просто найдите в логах устройство, на которое ругается сервис (в моём случае это tpm0 и tpmrm0), и явно разрешите доступ:

DevicePolicy=closed
DeviceAllow=/dev/tpm0
DeviceAllow=/dev/tpmrm0

После этого перезапустите сервис, и всё должно заработать.

На этом наше небольшое погружение в systemd credentials можно закончить. Мне действительно нравится эта возможность, и я считаю, что это очень хороший способ передавать секреты в сервис (точно лучше, чем использовать переменные окружения!).

Report Page