Salt 密钥管理实战:掌握 salt-key 命令从入门到源码级解析
【免费下载链接】saltSoftware to automate the management and configuration of infrastructure and applications at scale.项目地址: https://gitcode.com/gh_mirrors/sa/salt
导读
salt-key是 Salt 基础设施中负责管理 master 与 minion 之间认证公钥的核心命令:当一台 minion 首次向 master 发起连接时,它会把自己的公钥提交给 master,管理员必须通过salt-key在 master 上完成接受(accept)操作,minion 才能与 master 建立受信通信。本文以仓库文档 doc/ref/cli/salt-key.rst 为骨架,结合 salt/cli/key.py、salt/key.py 等源码实现,系统讲解四种密钥状态、全部命令行选项、密钥生成与签名功能,以及背后的实现原理与真实测试用例,帮助你安全、熟练地管理 Salt 认证体系。
命令概览:salt-key 是什么
salt-key的基本调用格式如下:
salt-key [ options ]从文档描述看,salt-key执行的是 Salt 服务器端(master)用于认证的公钥的简单管理。在初始连接时,Salt minion 会将它的公钥发送给 Salt master,这个公钥必须在 master 上通过salt-key命令接受。也就是说,所有 minion 公钥的生命周期管理(查看、接受、拒绝、删除、指纹比对)都由这一条命令完成。
从源码入口 salt/cli/key.py 可以看到它的完整执行路径:SaltKey.run()首先解析参数,然后实例化salt.key.KeyCLI并执行key.run()。其中SaltKey继承自 salt/utils/parsers.py 中的SaltKeyOptionParser,该解析器组合了多个 mixin:ConfigDirMixIn(配置目录)、LogLevelMixIn(日志级别)、OutputOptionsMixIn(输出格式)、RunUserMixin(运行用户)、EAuthMixIn(外部认证)等,其描述字符串为 "salt-key is used to manage Salt authentication keys"。
四种密钥状态:理解 Salt 认证模型
文档明确指出,Salt minion 密钥可以处于以下四种状态之一:
- unaccepted(未接受):密钥正在等待被接受。minion 首次连接 master 后即处于此状态。
- accepted(已接受):密钥已被接受,minion 可以与 Salt master 正常通信。
- rejected(已拒绝):密钥被使用
salt-key命令手动拒绝。处于该状态的 minion 不会从 Salt master 收到任何通信。 - denied(已拒绝/自动拒绝):密钥被 Salt master 自动拒绝。这通常发生在:minion 使用了重复的 ID,或者 minion 被重建/重新生成了新密钥而旧密钥未从 master 上删除。处于该状态的 minion 同样不会从 master 收到任何通信。
这四种状态在源码 salt/key.py 的Key类中有对应的目录常量定义:
ACC = "minions" # 已接受密钥目录(minions/) PEND = "minions_pre" # 待接受密钥目录(minions_pre/) REJ = "minions_rejected" # 已拒绝密钥目录(minions_rejected/) DEN = "minions_denied" # 自动拒绝密钥目录(minions_denied/)对应到磁盘上,这些状态表现为 master 的pki_dir(默认/etc/salt/pki/master)下的同名子目录,Key._check_minions_directories()方法正是将这四个常量与pki_dir拼接出实际的密钥存放路径:
minions_accepted = os.path.join(self.pki_dir, self.ACC) minions_pre = os.path.join(self.pki_dir, self.PEND) minions_rejected = os.path.join(self.pki_dir, self.REJ) minions_denied = os.path.join(self.pki_dir, self.DEN)关于状态转换,文档特别强调:要改变某个 minion 密钥的状态,请使用-d删除该密钥,然后重新接受或拒绝它。这是因为 Salt 的认证模型中,一个 minion ID 在同一时刻只能对应一种密钥状态,跨状态的"直接转换"并不被支持。
通用选项:控制运行方式与安全性
salt-key的通用选项控制了命令的运行用户、交互方式、日志与输出行为:
| 选项 | 说明 |
|---|---|
-u USER, --user=USER | 指定运行 salt-key 的用户。源码中SaltKey.run()会调用check_user(self.config["user"])校验该用户是否有权限操作密钥目录 |
--hard-crash | 抛出原始异常而不是优雅退出,默认值为 False(对应HardCrashMixin) |
-q, --quiet | 抑制输出。结合 salt/key.py 中的KeyCLI.run()可以看到,quiet模式下发生SaltException时不会打印错误信息 |
-y, --yes | 对所有提问回答 "Yes",默认值为 False |
--rotate-aes-key=ROTATE_AES_KEY | 设为 False 可阻止 master 在删除或拒绝密钥时刷新密钥会话(key session),但这会降低删除/拒绝操作的安全性,默认值为 True |
其中--rotate-aes-key值得重点关注:当密钥被删除或拒绝后,master 默认会轮换 AES 加密密钥,使被移除的 minion 即使残留旧凭据也无法继续与 master 通信。若设置为 False,虽然操作更快,但已删除的密钥在旧会话有效期内可能仍被信任,因此生产环境不建议关闭。
日志相关选项遵循LogLevelMixIn的约定,salt-key 的日志文件配置项名为key_logfile,默认值取自 master 配置中的key_logfile(在 conf/master 中默认为/var/log/salt/key),默认日志级别为warning。此外还支持--output等输出格式选项(OutputOptionsMixIn),例如--out=json便于脚本化处理。
动作选项:密钥的查看、接受、拒绝与删除
列出密钥:-l, --list与-L, --list-all
salt-key -l all # 列出所有密钥(等价于 -l un, -l rej, -l acc 的合集) salt-key -l unaccepted # 列出未接受/未签名密钥 salt-key -l accepted # 列出已接受/已签名密钥 salt-key -l rejected # 列出已拒绝密钥 salt-key -l denied # 列出被自动拒绝的密钥参数pre、un、unaccepted都用于列出未接受/未签名密钥;acc、accepted列出已接受/已签名密钥;rej、rejected列出已拒绝密钥;den、denied列出自动拒绝密钥;all列出全部密钥。
-L, --list-all在文档中标记为已弃用(Deprecated),请改用--list all。源码解析器中的 help 文本同样注明 "Deprecated: use "--list all""。
此外,解析器process_list()会对--list的参数做前置校验,只允许acc、pre、un、rej、den、all开头的取值,非法参数会直接报错退出。
接受密钥:-a, --accept与-A, --accept-all
salt-key -a minion1 # 接受指定密钥 salt-key -a 'minion*' # 接受匹配通配符的密钥(支持 glob) salt-key -A # 接受所有待接受(pending)密钥--accept默认只匹配待接受(pending)密钥;如需同时匹配已拒绝(rejected)密钥,可结合--include-all(即同时设置--include-rejected与--include-denied)使用。源码解析器支持更细粒度的--include-rejected与--include-denied选项,其中--include-all已被标注为弃用。
拒绝密钥:-r, --reject与-R, --reject-all
salt-key -r minion1 # 拒绝指定密钥 salt-key -r 'minion*' # 拒绝匹配通配符的密钥(支持 glob) salt-key -R # 拒绝所有待接受密钥与接受操作对称,--reject默认只匹配 pending 密钥,可用--include-accepted和--include-denied扩展匹配范围。
打印密钥内容:-p, --print与-P, --print-all
salt-key -p minion1 # 打印指定公钥内容 salt-key -P # 打印所有公钥内容删除密钥:-d, --delete与-D, --delete-all
salt-key -d minion1 # 删除指定密钥(支持 glob) salt-key -D # 删除所有密钥注意-D不接受任何额外参数:源码 salt/cli/key.py 中明确检查delete_all时若有残留参数会抛出SaltInvocationError("Delete all takes no arguments..."),这一点也被集成测试 tests/pytests/integration/cli/test_salt_key.py 的test_remove_all_keys_with_arg用例验证。
指纹比对:-f, --finger与-F, --finger-all
salt-key -f minion1 # 打印指定密钥的指纹 salt-key -F # 打印所有密钥的指纹指纹(fingerprint)是公钥的哈希摘要,用于在无法建立可信信道的场景下,通过电话、邮件等带外方式与 minion 侧输出的指纹进行人工比对,确认密钥未被中间人篡改。
交互确认与匹配的底层实现
在执行accept、reject、delete这类有副作用的操作时,KeyCLI.run()(salt/key.py)会先调用glob_match()做 glob 匹配,将命中的密钥以key输出格式展示给用户,然后默认弹出确认提示:
- 对
delete操作,提示Proceed? [N/y],默认不执行(回车相当于n); - 对
accept/reject操作,提示Proceed? [n/Y],默认执行(回车相当于y)。
只有回答了y(或使用-y/--yes跳过确认)后,才会真正调用对应的_run_cmd()执行。执行过程中若匹配不到任何密钥,会输出形如The key glob '...' does not match any ... keys.的提示。
密钥生成选项:生成密钥对与 master 签名
salt-key还具备密钥生成能力,属于SaltKeyOptionParser中的 "Key Generation Options" 选项组:
| 选项 | 说明 |
|---|---|
--gen-keys=GEN_KEYS | 指定一个名称来生成用于 Salt 的密钥对 |
--gen-keys-dir=GEN_KEYS_DIR | 指定保存生成的密钥对的目录,仅与--gen-keys配合使用,默认为当前目录 |
--keysize=KEYSIZE | 指定生成密钥的位数,仅与--gen-keys配合使用;密钥大小必须为 2048 或更高,低于 2048 会被向上取整到 2048,默认值为 2048 |
--gen-signature | 创建 master 公钥的签名文件,名为master_pubkey_signature。该签名可在 master 的 auth-reply 中发送给 minion,使 minion 能够以密码学方式验证 master 的公钥;这需要一对新的签名密钥对,可通过--auto-create参数自动创建 |
--priv=PRIV | 用于创建签名的私钥文件 |
--signature-path=SIGNATURE_PATH | 签名文件的写入路径 |
--pub=PUB | 用于创建签名的公钥文件 |
--auto-create | 若签名密钥对尚不存在则自动创建 |
--keysize的实际校验逻辑在 salt/utils/parsers.py 的process_keysize()中:小于 2048 报错、大于 32768 也报错,即合法范围为[2048, 32768]。--gen-keys还会触发process_config_dir()的特殊分支:当没有权限访问配置文件目录时,会把配置目录直接指向生成密钥的目录,以便在无 master 配置的环境下也能独立生成密钥对。
从源码 salt/key.py 的gen_keys()方法可以看出,密钥生成最终调用master_keys.find_or_create_keys(keyname, keysize=keysize, cache=cache),即复用 master 的密钥生成逻辑;而gen_keys_signature()则实现了签名流程:默认公钥取pki_dir/master.pub,默认私钥取pki_dir/master_sign.pem(对应 master 配置中的master_sign_key_name),若私钥不存在且开启了--auto-create,则会强制重新生成签名密钥对并计算签名。
在生成签名密钥对时,如果 master 配置了signing_key_pass(见 conf/master 中的# signing_key_pass: sdb://masterkeyring/signing_pass示例),可以通过 sdb 引用保管私钥的 passphrase。
与 master 配置的联动
salt-key的行为与 master 配置文件(conf/master)中的多个参数直接相关:
pki_dir(默认/etc/salt/pki/master):所有 minion 密钥及 master 自身密钥的存放根目录,四种状态的密钥分别存放在其下的minions/、minions_pre/、minions_rejected/、minions_denied/子目录中。auto_accept(默认False):设为 True 时 master 自动接受所有传入密钥,通常仅用于测试环境;生产环境保持关闭,由管理员手动salt-key -a接受。key_logfile(默认/var/log/salt/key):salt-key 自身的日志文件。keys.cache_driver(如localfs_key、mmap_key):密钥缓存驱动。从 salt/key.py 的list_keys()注释可以看到,支持list_all的驱动(mmap_key、localfs_key)可以单次遍历密钥存储,避免逐个探测,性能更好。key_cache:设为sched时,master 会将已接受 minion 列表缓存到pki_dir/minions/.key_cache文件,salt-key列出已接受密钥时可直接读取缓存,避免大规模环境下全量扫描。preserve_minions/preserve_minion_cache:--preserve-minions设为 True 时,master 在删除密钥后不清理对应 minion 的缓存数据。源码check_minion_cache()默认会在密钥删除时清理grains、pillar等缓存 bank,保留缓存可能带来安全隐患(被删除的 minion ID 若被重新注册可能复用旧数据)。
另外,在 Salt 集群(cluster)场景下,Key类会使用cluster_pki_dir替代pki_dir定位密钥(见 salt/key.py 的__init__),这一点与 conf/master 中cluster_pki_dir的注释描述一致。
两种运行模式:本地直连与 eauth 认证
从KeyCLI的源码可以看出,salt-key支持两种执行路径:
- 本地模式(默认):未指定
--eauth时,直接在 master 本机调用Key对象的对应方法操作文件系统,认证基于本地 master 密钥(.root_key)与check_user校验。 - eauth 远程模式:指定
--eauth(如pam、ldap)时,通过salt.wheel.WheelClient将请求发送给 master 上的 wheel 模块(key.accept、key.reject、key.delete等)执行,此时需要提供 eauth 凭据(--username/--password)或 token。
集成测试 tests/pytests/integration/cli/test_salt_key.py 中的test_remove_key_eauth用例演示了通过 PAM 认证执行salt-key -d删除密钥的完整流程,这为需要远程或权限分离管理密钥的场景提供了参考实现。
实战示例:完整管理一个 minion 密钥
综合上述内容,一个典型的密钥生命周期管理流程如下:
# 1. 查看当前所有密钥及其状态 salt-key -L # 2. minion 首次连接后,查看待接受密钥 salt-key -l unaccepted # 3. 查看待接受密钥的指纹,并通过带外方式与 minion 核对 salt-key -f <minion-id> # 4. 接受该 minion(也可使用通配符批量接受) salt-key -a <minion-id> # 5. 确认已接受 salt-key -l accepted # 6. 如需移除该 minion(例如重建 minion、更换密钥),先删除再重新接受 salt-key -d <minion-id> -y # minion 重新提交新密钥后: salt-key -a <minion-id> # 7. 维护时也可手动拒绝可疑密钥 salt-key -r <suspicious-minion-id> # 8. 独立生成一对测试用密钥对(无需 master 配置目录) salt-key --gen-keys=testminion --gen-keys-dir=/tmp/salt-test-keys --keysize=2048当遇到"minion 无法通信"的排查场景时,应首先使用salt-key -l all确认该 minion 密钥处于何种状态:denied状态通常意味着 minion ID 重复或密钥被重建,此时按文档建议删除旧密钥、让 minion 重新提交新密钥并接受即可恢复通信。
小结
salt-key虽是一个命令行工具,但它承载着 Salt 安全体系的第一道关口——认证信任。理解四种密钥状态(unaccepted / accepted / rejected / denied)、熟练使用-l/-a/-r/-d/-p/-f等动作选项,并掌握--rotate-aes-key、--gen-keys、--gen-signature等安全与生成类选项,是每个 Salt 管理员的基本功。透过 salt/key.py 的Key/KeyCLI类与 salt/utils/parsers.py 的SaltKeyOptionParser,我们可以清楚地看到:状态即目录、动作即文件操作、确认机制保护操作安全,这也为深入理解 Salt master 的认证流程打下了坚实基础。更多细节可参阅同目录下的 salt(7)、salt-master(1) 与 salt-minion(1) 手册页。
【免费下载链接】saltSoftware to automate the management and configuration of infrastructure and applications at scale.项目地址: https://gitcode.com/gh_mirrors/sa/salt
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考