systemd 服务管理器支持“凭据”(credential)这一概念,用于安全获取凭据数据,并把它传给系统和服务。凭据的具体内容由应用决定,主要用于传递可能具有安全敏感性的加密密钥、证书、密码、身份信息等,也可以作为对系统和服务进行参数化配置的通用基础设施。
传统上,这类数据经常通过环境变量,或磁盘上的普通未加密文件传递给服务。环境变量存在问题:默认会沿进程树继承,具有大小限制,而且不适合处理二进制数据。systemd 的系统与服务凭据为此提供了更好的选择,具体具有以下特性:
- 服务激活时获取凭据,服务停用时释放。服务运行期间,凭据不可变。
- 服务代码像读取普通文件一样访问凭据,访问路径由环境变量
$CREDENTIALS_DIRECTORY得出。 - 访问权限限制为服务所属用户。凭据数据不会像环境变量那样沿进程树传播;每次访问都会由内核检查权限。如果服务使用文件系统命名空间,已加载的凭据数据对其他所有服务都不可见。
- 服务凭据可以来自磁盘文件、单元文件中的字面字符串、通过
AF_UNIX套接字动态提供数据的其他服务,或继承系统收到的系统凭据。 - 凭据可选择加密并进行认证。密钥可来自本地 TPM2 芯片、
/var/中存储的密钥,或二者组合。除先用一条简单命令加密凭据外,无需手动设置,详见下文。 - 服务凭据存放在不可交换到磁盘的内存中;权限允许时使用
ramfs。 - 凭据也可来自宿主虚拟机管理器的 SMBIOS OEM 字符串或 QEMU
fw_cfg、容器管理器、内核命令行、initrd、通过systemd-stub从 EFI 系统分区取得的 UEFI 环境,以及云实例元数据服务(IMDS)。这些系统凭据随后可按需传给各服务。 - 凭据适合向使用
RootImage=或RootDirectory=、无法直接读取宿主目录树资源的服务传递参数。例如,可用这种方式安全、可靠地参数化可移植服务(Portable Services)。 - 凭据可以是二进制,也可以比较大;当前每个服务的凭据总大小限制为 1M。
配置每个服务的凭据
单元文件中提供以下设置:
LoadCredential=:从磁盘、AF_UNIX套接字加载凭据,或传递系统凭据。ImportCredential=:从磁盘或凭据存储区加载一个或多个凭据,可包含加密凭据。SetCredential=:把单元文件中的字面字符串设为凭据。单元文件在磁盘上及通过 D-Bus 都可被所有用户读取,因此只适合公钥、证书等非敏感内容,不适合私钥。LoadCredentialEncrypted=:类似LoadCredential=,但加载加密凭据,并在传给服务前解密。SetCredentialEncrypted=:类似SetCredential=,但字面值应为加密凭据。即使单元文件可被所有人读取,没有 TPM2/加密密钥也无法解开其中的密文,因此适合敏感信息。
每个凭据在单元文件中都有一个适合用作文件名的简短名称,服务代码通过该名称取回数据。每个名称应只指定一次。设置详情见 systemd.exec 手册。
建议同时为处理凭据的服务启用挂载命名空间,使该服务的运行时凭据目录对其他服务不可见。最小设置为 PrivateMounts=。ProtectSystem=、ReadOnlyPaths= 等许多沙箱设置已隐含启用它,因此通常无需重复显式设置。
服务代码的编程接口
服务配置了一个或多个凭据时,启动后会收到环境变量 $CREDENTIALS_DIRECTORY,其值为存放凭据的目录的绝对路径。每个凭据对应一个文件。除该环境变量外,单元文件中的 %d 说明符也会解析为服务的凭据目录。
单元文件示例:
…
[Service]
ExecStart=/usr/bin/myservice.sh
LoadCredential=foobar:/etc/myfoobarcredential.txt
Environment=FOOBARPATH=%d/foobar
…
对应的服务 Shell 脚本 /usr/bin/myservice.sh:
#!/bin/sh
sha256sum $CREDENTIALS_DIRECTORY/foobar
sha256sum $FOOBARPATH
如此定义的服务会把 /etc/myfoobarcredential.txt 的内容作为名为 foobar 的凭据接收,并通过 $CREDENTIALS_DIRECTORY/foobar 访问。由于同时把路径放进环境变量 $FOOBARPATH,也可通过它访问。运行时,服务会输出两次相同的 SHA256 哈希值。
理想情况下,服务代码应直接支持这种接口:检查 $CREDENTIALS_DIRECTORY,从中读取需要的凭据。若守护进程不直接支持它,但接受命令行参数中的凭据文件路径,可在 ExecStart= 中用 ${CREDENTIALS_DIRECTORY} 引用目录。
若守护进程接受环境变量中的凭据路径,可在 Environment= 中用 %d 构造具体路径。加密凭据会在服务激活时自动解密并认证,服务代码只会收到明文。
生成器代码的编程接口
生成器(generator)可根据外部配置或系统参数,例如系统凭据,生成原生单元文件。它们运行在服务上下文之外,因此不会自动收到加密凭据的明文。
以加密形式传入系统的凭据,会原样放进 $ENCRYPTED_CREDENTIALS_DIRECTORY 指向的目录;明文凭据则位于 $CREDENTIALS_DIRECTORY。可以使用 systemd-creds --system cat … 访问两种形式,并按需解密,详见 systemd-creds(1)。
生成器通常与 initrd 代码类似,在启动早期运行,此时存放系统凭据加密秘密的 /var/ 文件系统不一定已挂载。因此,供生成器使用的凭据宜用 systemd-creds encrypt --with-key=auto-initrd 加密,确保仅绑定 TPM2,而不依赖 /var/ 下的凭据秘密。加密细节见后文。
工具
systemd-creds 用于访问和枚举系统/服务凭据,以及加密、解密凭据。
在服务上下文中不带其他参数运行它,会列出传入服务的凭据。systemd-creds cat xyz 会把凭据 xyz 写入标准输出。加上 --system 后,操作的是传入整个系统的凭据,而非当前服务收到的凭据。
例如:
systemd-run -P --wait -p LoadCredential=abc:/etc/hosts systemd-creds cat abc
这会运行一个临时服务,将系统的 /etc/hosts 作为凭据 abc 传入,然后通过 systemd-creds cat 将其输出。
加密
凭据可承载加密密钥等敏感信息。为提高静态存储安全性,systemd 支持对称加密与认证,可使用 AES256-GCM。密钥可以由本地 TPM2 设备派生,也可以保存在 /var/lib/systemd/credential.secret 中,或由二者组合产生。
如果具备 TPM2 且 /var/ 位于持久存储上,默认使用二者组合。这确保凭据只能在本地硬件与对应操作系统安装中解密、验证;没有 TPM2 芯片及上述密钥文件,就无法解开磁盘上的加密凭据,也无法在其他机器上按这一默认方式预制凭据。
解密一般发生在服务激活时。凭据进入系统后,可以一直保持原来的加密或明文形式,逐级传递给消费者;直到服务激活时才解密和认证,使服务只看到明文。
systemd-creds 提供 encrypt、decrypt 命令。例如:
systemd-creds encrypt --name=foobar plaintext.txt ciphertext.cred
shred -u plaintext.txt
systemd-run -P --wait -p LoadCredentialEncrypted=foobar:$(pwd)/ciphertext.cred systemd-creds cat foobar
这先将 plaintext.txt 加密为 ciphertext.cred,再按示例删除明文源文件,随后启动临时服务,把解密后的凭据 foobar 传给服务程序。这里服务程序就是把收到的数据写到标准输出的 systemd-creds。
也可以不把加密凭据存为独立文件,而是嵌入单元文件:
systemd-creds encrypt -p --name=foobar plaintext.txt -
它会输出可直接放入单元文件的 SetCredentialEncrypted= 行,例如:
…
[Service]
ExecStart=/usr/bin/systemd-creds cat foobar
SetCredentialEncrypted=foobar: \
k6iUCUh0RJCQyvL8k8q1UyAAAAABAAAADAAAABAAAAC1lFmbWAqWZ8dCCQkAAAAAgAAAA \
AAAAAALACMA0AAAACAAAAAAfgAg9uNpGmj8LL2nHE0ixcycvM3XkpOCaf+9rwGscwmqRJ \
cAEO24kB08FMtd/hfkZBX8PqoHd/yPTzRxJQBoBsvo9VqolKdy9Wkvih0HQnQ6NkTKEdP \
HQ08+x8sv5sr+Mkv4ubp3YT1Jvv7CIPCbNhFtag1n5y9J7bTOKt2SQwBOAAgACwAAABIA \
ID8H3RbsT7rIBH02CIgm/Gv1ukSXO3DMHmVQkDG0wEciABAAII6LvrmL60uEZcp5qnEkx \
SuhUjsDoXrJs0rfSWX4QAx5PwfdFuxPusgEfTYIiCb8a/W6RJc7cMweZVCQMbTARyIAAA \
AAJt7Q9F/Gz0pBv1Lc4Dpn1WpebyBBm+vQ5N/lSKW2XSm8cONwCopxpDc7wJjXg7OTR6r \
xGCpIvGXLt3ibwJl81woLya2RRjIvc/R2zNm/yWzZAjiOLPih4SuHthqiX98ey8PUmZJB \
VGXglCZFjBx+d7eCqTIdghtp5pkDGwMJT6pjw4FfyFK2nJPawFKPAqzw9DK2iYttFeXi5 \
19xCfLBH9NKS/idlYXrhp+XIEtsr26s4lx5y10Goyc3qDOR3RD2cuZj0gHwV35hhhhcCz \
JaYytef1X/YL+7fYH5kuE4rxSksoUuA/LhtjszBeGbcbIT+O8SuvBJHLKTSHxPL8FTyk3 \
L4FSkEHs0rYwUIkKmnGohDdsYrMJ2fjH3yDNBP16aD1+f/Nuh75cjhUnGsDLt9K4hGg== \
…
空密钥加密与启动策略
有时需要向尚不具备、甚至完全没有加密密钥的机器预置凭据,例如首次启动完成前,或没有 TPM2 的系统。systemd-creds encrypt --with-key=null 会使用固定的零长度密钥加密,仍把凭据封装为普通的 systemd-creds 格式。
它可以走与其他加密凭据相同的传递路径,例如放在 ESP 的 /loader/credentials/ 或 *.efi.extra.d/ 下,但不提供机密性或真实性保证。在具备更好保护能力的系统上接受这种凭据有风险,是否在启动时接受它,由内核命令行选项 systemd.credentials_boot_policy= 控制:
| 模式 | 接受空密钥凭据的条件 |
|---|---|
strict |
永不接受 |
tofu |
首次启动,或没有 TPM2 |
relaxed |
Secure Boot 关闭,或没有 TPM2 |
off |
总是接受 |
默认值为 relaxed。该策略只控制默认接受决定;绑定宿主密钥或 TPM2 设备加密的凭据始终可以被接受。详见 systemd(1)。
从容器管理器、虚拟机管理器、内核命令行或 UEFI 启动环境继承
有时需要像参数化服务一样,通过凭据参数化整个系统。例如,启动系统时传入一组凭据,再向下传递到最终消费它们的服务。
systemd 支持以下五种方式向系统传递凭据:
- 容器管理器为容器中作为 PID 1 运行的 systemd 设置
$CREDENTIALS_DIRECTORY,方式与 systemd 向服务设置该变量类似。systemd-nspawn(1)的--set-credential=和--load-credential=实现了这种方式,可把任意凭据从宿主传给容器负载,另见容器接口文档。 - 虚拟机可通过 SMBIOS OEM 字符串接收凭据。例如 QEMU 参数
-smbios type=11,value=io.systemd.credential:foo=bar,或接受 Base64 编码二进制数据的-smbios type=11,value=io.systemd.credential.binary:foo=YmFyCg==。也可用-fw_cfg name=opt/io.systemd.credentials/foo,string=bar,通过 QEMU 的fw_cfg从宿主经虚拟机管理器传入;这三种示例都把凭据foo设为bar。通常优先使用 SMBIOS,因为速度更快,也较少依赖特定 VMM。fw_cfg对名称还有 55 字符限制,因此部分设置可能放不下。 - 在 initrd → 宿主系统切换时传递。运行于 initrd 的预置系统可把文件放到
/run/credentials/@initrd/,切换时这些文件会被导入文件系统凭据集合,随后文件和目录都会被移除。 - 使用
systemd-stubUEFI 内核 stub 时,可以在 EFI 系统分区中放置加密凭据,由 stub 交给内核,最终由用户空间的 systemd 接收。这适合安全参数化由供应商构建并签名的 initrd:用户空间可把凭据放在 EFI 内核旁,确保 initrd 能安全访问。 - 通过内核命令行的
systemd.set_credential=或接受 Base64 二进制数据的systemd.set_credential_binary=。这里的数据经/proc/cmdline对所有用户空间应用可见,包括无特权应用,因此通常不适合传递敏感信息,应避免这样使用。
系统收到的凭据可通过 systemd-creds --system 枚举或显示,也可通过 LoadCredential= 继续传给服务。例如:
systemd-nspawn --set-credential=mycred:supersecret -i test.raw -b
或者:
qemu-system-x86_64 \
-machine type=q35,accel=kvm,smm=on \
-smp 2 \
-m 1G \
-cpu host \
-nographic \
-nodefaults \
-serial mon:stdio \
-drive if=none,id=hd,file=test.raw,format=raw \
-device virtio-scsi-pci,id=scsi \
-device scsi-hd,drive=hd,bootindex=1 \
-smbios type=11,value=io.systemd.credential:mycred=supersecret
前者通过 systemd-nspawn 以容器形式启动 test.raw,后者通过 QEMU 以虚拟机形式启动。两者都把凭据 mycred 设为 supersecret。
在所启动的系统内部查看:
systemd-creds --system cat mycred
或者进一步传给服务:
systemd-run -p ImportCredential=mycred -P --wait systemd-creds cat mycred
从云实例元数据服务(IMDS)获取
多数公有云提供实例元数据服务,即虚拟机可访问的节点本地网络端点,用于读取自身信息及启动参数,其中也可能包含适合作为 systemd 凭据的数据。
systemd 可以自动获取并导入这些数据到系统凭据存储区,之后服务通过 ImportCredential= 等方式使用,与其他来源的凭据相同。
该功能由提供本地 Varlink IPC 接口的 systemd-imdsd@.service 与客户端工具 systemd-imds 实现。systemd-imds-generator 通过 SMBIOS/DMI 硬件数据库 hwdb.d/40-imds.hwdb 检测云环境,并在识别出的云上自动将这些组件加入启动事务。
启动时,systemd-imds-import.service 运行 systemd-imds --import,从 IMDS 获取数据,把相关字段写到 /run/credstore/;加密凭据写到 /run/credstore.encrypted/。这些目录位于凭据搜索路径中,因此消费方单元的 ImportCredential= 会自动找到它们。具体导入:
- 实例的 userdata 字段,前提是它是包含
systemd.credentials数组的 JSON 对象。每个数组项包含name,以及text(字面字符串)、data(Base64 二进制数据)、encrypted(由systemd-creds encrypt产生的、Base64 编码的加密凭据)之一。这是向云实例传入任意凭据的主要机制。若使用cloud-init等传统 IMDS 客户端,userdata 可能已用于其配置,导入服务会平稳忽略该数据;换言之,使用 cloud-init 时这项功能不可用。 - 实例的 SSH 公钥,导入为
ssh.authorized_keys.root,用于为 root 用户配置 SSH 访问。 - 实例主机名,导入为
firstboot.hostname,由systemd-firstboot.service消费。
取得的 userdata 会在导入前度量到 TPM2 的 PCR 12,以便对云提供的参数配置进行证明。
通过云预置接口作为实例用户数据传入的示例如下:
{
"systemd.credentials": [
{
"name": "mycred",
"text": "supersecret"
},
{
"name": "mybinarycred",
"data": "YmFyCg=="
}
]
}
约定凭据
systemd 自带的多个服务会读取约定名称的凭据以调整行为:
systemd(1),即 PID 1 系统管理器,读取vmm.notify_socket,在系统完成启动时发送READY=1数据报,方便宿主虚拟机管理器或其他进程通过 VSOCK 接收通知。如果虚拟机管理器不支持AF_VSOCK上的SOCK_DGRAM,会尝试SOCK_SEQPACKET。凭据内容格式为vsock:<CID>:<PORT>;宿主与来宾内核都必须支持 VSOCK,并加载相关内核模块。systemd-sysusers(8)读取passwd.hashed-password.<username>、passwd.plaintext-password.<username>、passwd.shell.<username>,为创建的系统用户设置 UNIX 哈希密码、明文密码或 Shell。把<username>替换为目标用户,例如root。systemd-firstboot(1)读取firstboot.locale、firstboot.locale-messages、firstboot.keymap、firstboot.timezone,在/etc/尚未设置时配置语言区域、键盘映射或时区。tmpfiles.d(5)读取tmpfiles.extra中任意 tmpfiles.d 配置行;可进行 Base64 编码,便于从命令行传入。- 其他约定凭据见
systemd.system-credentials(7)。
未来可能有更多服务支持凭据。例如:
systemd-nspawn -i test.raw \
--set-credential=passwd.hashed-password.root:$(mkpasswd mysecret) \
--set-credential=firstboot.locale:C.UTF-8 \
-b
此命令把指定磁盘镜像作为 systemd-nspawn 容器启动,传入 root 密码 mysecret 和默认语言区域 C.UTF-8。数据默认传给 systemd-sysusers.service 和 systemd-firstboot.service 应用。它们只会在 /etc/ 尚未配置这些值时生效,因此仅影响尚未预置的系统,不会覆盖现有配置。QEMU 的类似命令为:
qemu-system-x86_64 \
-machine type=q35,accel=kvm,smm=on \
-smp 2 \
-m 1G \
-cpu host \
-nographic \
-nodefaults \
-serial mon:stdio \
-drive if=none,id=hd,file=test.raw,format=raw \
-device virtio-scsi-pci,id=scsi \
-device scsi-hd,drive=hd,bootindex=1 \
-smbios type=11,value=io.systemd.credential:passwd.hashed-password.root=$(mkpasswd mysecret) \
-smbios type=11,value=io.systemd.credential:firstboot.locale=C.UTF-8
下面通过 QEMU 启动镜像,用调用者的公钥为 root 配置 SSH 访问,并在启动完成时通知宿主进程:
qemu-system-x86_64 \
-machine type=q35,accel=kvm,smm=on \
-smp 2 \
-m 1G \
-cpu host \
-nographic \
-nodefaults \
-serial mon:stdio \
-drive if=none,id=hd,file=test.raw,format=raw \
-device virtio-scsi-pci,id=scsi \
-device scsi-hd,drive=hd,bootindex=1 \
-device vhost-vsock-pci,id=vhost-vsock-pci0,guest-cid=42 \
-smbios type=11,value=io.systemd.credential:vmm.notify_socket=vsock:2:1234 \
-smbios type=11,value=io.systemd.credential.binary:tmpfiles.extra=$(echo -e "d /root/.ssh 0750 root root -\nf~ /root/.ssh/authorized_keys 0600 root root - $(ssh-add -L | base64 -w 0)" | base64 -w 0)
宿主进程可以这样监听通知:
$ socat - VSOCK-LISTEN:1234,socktype=5
READY=1
相关路径
从服务角度看,运行时凭据目录由 $CREDENTIALS_DIRECTORY 提供。系统服务通常使用 /run/credentials/<unit name>,但不鼓励硬编码,因为该路径不适用于用户服务。对于尚不支持相对 $CREDENTIALS_DIRECTORY 查找凭据的软件,打包者和管理员可把硬编码路径作为最后手段。
从生成器角度看,系统明文凭据目录由 $CREDENTIALS_DIRECTORY 提供,加密凭据目录由 $ENCRYPTED_CREDENTIALS_DIRECTORY 提供。
运行时,传入系统的普通凭据(例如来自容器管理器或 QEMU)位于 /run/credentials/@system/;使用前需要解密/验证的凭据(例如来自 systemd-stub)位于 /run/credentials/@encrypted/。
ImportCredential=,以及配置为相对来源路径的 LoadCredential=、LoadCredentialEncrypted=,会自动搜索源文件。首先搜索传给系统的凭据;未找到时依次搜索 /etc/credstore/、/run/credstore/、/usr/lib/credstore/。LoadCredentialEncrypted= 还会搜索 /etc/credstore.encrypted/ 等对应目录。
ImportCredential= 同时搜索明文与加密目录,因此这些目录适合存放系统待加载的凭据。
按凭据决定服务是否运行
有时,应只有在系统收到指定凭据时才启动某个系统服务。可使用单元文件设置 ConditionCredential= 和 AssertCredential= 实现。











暂无评论内容