RustFS Vault KMS 认证实战指南:AppRole、Kubernetes 与 Agent Token File 的配置、续期与故障排查
【免费下载链接】rustfs🚀2.3x faster than MinIO for 4KB object payloads. RustFS is an open-source, S3-compatible high-performance object storage system supporting migration and coexistence with other S3-compatible platforms such as MinIO and Ceph.项目地址: https://gitcode.com/GitHub_Trending/rus/rustfs
导读
本文是 RustFS 对象存储系统中 Vault KMS 后端(KV2 与 Transit)**认证(Authentication)**体系的完整操作手册。你将从零掌握四种认证方式(静态 Token、AppRole、Kubernetes、Vault Agent Token File)的选型与配置、RustFS 后台凭据续期机制(半 TTL 续期 + 失败重登 + 失败关闭窗口)的底层原理,以及"KMS credentials unavailable"类错误的系统性排查方法。文中的所有结论均可回溯到crates/kms/src/backends/vault_credentials.rs与crates/kms/src/config.rs的实现细节,并附带真实可用的 Vault 配置命令与日志定位依据。
适用场景与核心事实来源
本运行手册(runbook)适用于以下三类操作场景:
- 配置 RustFS 的 Vault KMS 后端(KV2 与 Transit)如何向 Vault 认证;
- 轮换 AppRole SecretID 或 Vault Agent 管理的 token;
- 诊断
KMS credentials unavailable类错误。
实现的权威来源(Source of truth)位于:
- crates/kms/src/backends/vault_credentials.rs:定义
DEFAULT_TOKEN_FILE_POLL_INTERVAL_SECS、refresh_safety_window_secs、后台续期循环及其全部日志行; - crates/kms/src/config.rs:定义
RUSTFS_KMS_TIMEOUT_SECS默认值(30 秒)及全部RUSTFS_KMS_VAULT_*环境变量的解析与冲突校验。
关于每个后端在 Vault 中存储什么、KV2/Transit 的策略(policy)作用域如何裁剪,请参阅 KMS backend security properties。
选择认证方式:四种方法对比
RustFS 的 Vault 后端支持四种认证方式,配置标签与行为差异如下:
| 方法 | 配置标签 | 凭据生命周期 | 后台续期 | 推荐场景 |
|---|---|---|---|---|
| 静态 Token | Token | 由运维方在 Vault 侧配什么就是什么;RustFS 从不续期 | 无 | 开发环境、短期实验 |
| AppRole | AppRole | 通过 login 换取带租约(lease-bound)的 token;由 RustFS 续期 | 半 TTL 续期,失败则重新登录 | 无 Vault Agent sidecar 的生产环境 |
| Kubernetes | Kubernetes | 通过 login 换取带租约的 token;由 RustFS 续期 | 半 TTL 续期,失败则重新登录 | Kubernetes 上的生产环境,无需分发任何凭据 |
| Agent Token File | TokenFile | 生命周期归 Vault Agent 所有;RustFS 只重读 sink 文件 | 每个轮询周期重读一次文件 | 由 Vault Agent(或等价物)管理认证的生产环境 |
方法互斥:只允许配置一种认证方式
RustFS 严格要求四种方法中恰好配置一种。以下组合会在启动时被直接拒绝并报配置错误(因为有效身份会产生歧义):
RUSTFS_KMS_VAULT_TOKEN_FILE与任何其他方法同时设置;RUSTFS_KMS_VAULT_KUBERNETES_ROLE与RUSTFS_KMS_VAULT_APPROLE_ROLE_ID同时设置。
这一点在源码 crates/kms/src/config.rs 的vault_auth_method_from_env中有明确实现:TokenFile 被设置为唯一权威凭据源,任何与其并存的登录方法都会触发configuration_error;Kubernetes 与 AppRole 同为"显式登录方法",同时配置即报错。
与之相对,一个残留的RUSTFS_KMS_VAULT_TOKEN与已配置的登录方法并存时会被容忍并忽略——这样过期的环境变量不会静默地把身份降级回静态 Token。
配置入口的等价性
无论服务是通过RUSTFS_KMS_ENABLE=true启动,还是稍后通过POST /rustfs/admin/v3/kms/configure动态配置,以上所有认证方式都以相同方式读取。也就是说,环境变量与管理员 API 解析出的VaultAuthMethod结构是同一套。
安全默认值:开发模式之外的强制校验
以下不安全配置在非开发模式下会被拒绝:
RUSTFS_KMS_VAULT_TOKEN的默认回退值dev-token(仅当RUSTFS_KMS_ALLOW_INSECURE_DEV_DEFAULTS=true时才被接受);- 明文 HTTP(
http://)的 Vault 地址; - 关闭 TLS 验证(
RUSTFS_KMS_VAULT_SKIP_TLS_VERIFY)。
这些开关在KmsConfig::validate中统一把关,防止默认配置误入不安全状态。
AppRole 认证
AppRole 适合没有 Vault Agent sidecar 的生产部署:RustFS 负责登录、续期与故障恢复,运维只需管理一份 SecretID 的投递与轮换。
Vault 侧配置
首先创建一个只覆盖后端所需权限的策略(KV2 与 Transit 的策略示例见 KMS backend security properties),再创建签发该策略 token 的 AppRole:
vault policy write rustfs-kms rustfs-kms-policy.hcl vault auth enable approle vault write auth/approle/role/rustfs-kms \ token_policies="rustfs-kms" \ token_ttl=1h \ token_max_ttl=24h \ secret_id_ttl=90d \ secret_id_num_uses=0TTL 选取的关键约束:token_ttl必须显著高于 RustFS 的单次尝试超时(默认 30 秒)。因为失败关闭(fail-closed)窗口默认等于一次尝试超时,若 token TTL 与该窗口接近,token 几乎没有任何可用寿命。建议保持两者至少一个数量级的差距。
KV2 后端的最小 Vault 策略示例(来自 kms-backend-security.md)如下,其中尾部的通配符同时覆盖了.../keys/{key_id}/versions/{N}下的逐版本密钥材料记录:
# RustFS KMS (Vault KV2 backend) — key storage only, no Transit access needed. path "secret/data/rustfs/kms/keys/*" { capabilities = ["create", "read", "update"] } path "secret/metadata/rustfs/kms/keys/*" { capabilities = ["list", "read", "delete"] }RustFS 配置
RUSTFS_KMS_BACKEND=vault-transit # or "vault" for the KV2 backend RUSTFS_KMS_VAULT_ADDRESS=https://vault.example.com:8200 RUSTFS_KMS_VAULT_APPROLE_ROLE_ID=<role-id> RUSTFS_KMS_VAULT_APPROLE_SECRET_ID_FILE=/etc/rustfs/approle-secret-id # Alternatively, inline (the file takes precedence when both are set): # RUSTFS_KMS_VAULT_APPROLE_SECRET_ID=<secret-id> # Optional, defaults to "approle": # RUSTFS_KMS_VAULT_APPROLE_MOUNT=approle从源码 crates/kms/src/config.rs 看,AppRole 的解析逻辑是:设置RUSTFS_KMS_VAULT_APPROLE_ROLE_ID即选择 AppRole 认证;SecretID 优先从RUSTFS_KMS_VAULT_APPROLE_SECRET_ID_FILE读取(每次登录都会重读),否则回退到内联的RUSTFS_KMS_VAULT_APPROLE_SECRET_ID;两者都未设置则直接报配置错误。
登录与续期的运行时行为
RustFS 在启动时执行一次登录(VaultCredentialProvider::new中的vault_login操作),随后在后台按半 TTL续期 token。续期流程(对应 vault_credentials.rs 的refresh与renewal_loop):
- 等待到达当前代(generation)token 的半 TTL 时刻;
- 尝试
renew-self;若续期失败(网络问题、Vault 被 seal、token 被吊销),日志输出Vault token renewal failed; falling back to a fresh login,回退为一次全新登录; - 若全新登录也失败,每隔数秒(
DEFAULT_REFRESH_RETRY_INTERVAL,5 秒)持续重试,直到 Vault 恢复。
值得注意的是续期循环是**单飞(single-flight)**的:所有触发通过refresh_lock互斥量串行化,后到的触发者若发现已有更新的 generation 安装完毕,就直接返回而不触碰 Vault。
SecretID 的投递与轮换
SecretID 属于敏感凭据,应带外投递:
- 通过 secrets-manager 挂载的文件;
- 由 init-container 写入
RUSTFS_KMS_VAULT_APPROLE_SECRET_ID_FILE; - 由部署工具解包 Vault response wrapping 后的值。
请像对待密码一样对待它:文件权限仅 owner 可读,绝不写入日志或 shell history。
由于 secret_id 文件在每次登录尝试时都会重读,轮换 SecretID 无需重启:
- 生成新 SecretID:
vault write -f auth/approle/role/rustfs-kms/secret-id; - 原子替换文件(先写临时文件再 rename);
- 吊销旧 SecretID 的 accessor。已签发的 token 会继续续期,新 SecretID 只在下一次完整重登时才需要。
文件缺失/为空的语义:空文件或缺失文件会立即让登录尝试失败(不发起任何 Vault 往返)。在启动阶段该错误是致命的——provider 构造失败、进程退出,因此启动时缺文件只能通过重启进程恢复,而不是进程内重试。一旦 RustFS 已运行,同样的失败会在正常刷新节奏上重试,因此运行中修复文件即可自愈后端,无需重启。这一行为在源码注释中明确为:文件读取失败对该次尝试是 Fatal,但续期循环会按自己的节奏持续重试。
Kubernetes 认证
在 Kubernetes 上这是首选方式:pod 自身的 ServiceAccount 即身份,无需分发、轮换任何凭据,也不存在泄入 Secret 的凭据。
Vault 侧配置
vault auth enable kubernetes vault write auth/kubernetes/config \ kubernetes_host="https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT" vault write auth/kubernetes/role/rustfs \ bound_service_account_names=rustfs \ bound_service_account_namespaces=rustfs \ token_policies=rustfs-kms \ token_ttl=1h与 AppRole 相同,token_ttl必须显著高于 RustFS 的单次尝试超时(默认 30s)。
RustFS 配置
RUSTFS_KMS_BACKEND=vault-transit # or "vault" for the KV2 backend RUSTFS_KMS_VAULT_ADDRESS=https://vault.vault.svc.cluster.local:8200 RUSTFS_KMS_VAULT_KUBERNETES_ROLE=rustfs # Optional, defaults to "kubernetes": # RUSTFS_KMS_VAULT_KUBERNETES_MOUNT=kubernetes # Optional, defaults to the kubelet's projected token path: # RUSTFS_KMS_VAULT_KUBERNETES_JWT_PATH=/var/run/secrets/kubernetes.io/serviceaccount/token运行时行为
与 AppRole 完全一致:RustFS 启动时登录,半 TTL 续期,失败回退全新登录。关键差异在于ServiceAccount token 每次登录都从磁盘重读而非缓存——kubelet 会在 pod 生命周期内轮换 projected token,缓存会把源困在过期断言上;重读保证 kubelet 轮换后的 token 无需重启即可被拾取。
缺失或空的 token 文件会让登录尝试立即失败(无 Vault 往返)。启动阶段该错误是致命的(provider 构造失败、进程退出),所以慢启动期间 token 投影过晚的场景由 pod 重启循环恢复;运行中 token 文件消失或变空则会在正常刷新节奏上重试,自愈后端。需要留意的是,Kubernetes 登录的 JWT 文件不做权限位校验:kubelet 默认以 world-readable 挂载 projected token,若校验 group/other 位会拒绝所有标准 pod(这是与 TokenFile 模式的刻意差异,见 vault_credentials.rs 中KubernetesLogin的注释)。
Vault Agent Token File 模式
该模式下,Vault Agent(或任何等价进程)全权负责认证与 token 续期,RustFS 只读取 token sink 文件。
Vault Agent 配置示例
auto_auth { method "approle" { config = { role_id_file_path = "/etc/vault-agent/role-id" secret_id_file_path = "/etc/vault-agent/secret-id" } } sink "file" { config = { path = "/run/vault-agent/token" mode = 0600 } } }RustFS 配置
RUSTFS_KMS_BACKEND=vault-transit RUSTFS_KMS_VAULT_ADDRESS=https://vault.example.com:8200 RUSTFS_KMS_VAULT_TOKEN_FILE=/run/vault-agent/token轮询与文件校验
轮询间隔由TokenFile认证配置中的poll_interval_secs控制(默认DEFAULT_TOKEN_FILE_POLL_INTERVAL_SECS,30 秒)。从源码看,每次成功读取会为 token 授予两倍轮询间隔的观测有效期限(validity),并安装一个新的 client generation——原因在于续期循环在租约 TTL 的一半处触发,于是文件每轮询间隔被重读一次,agent 原子替换后的新 token 会在一个轮询间隔内被拾取。
每次读取都强制执行以下校验,任何一项不满足都会在不联系 Vault 的情况下让刷新失败:
- 文件必须存在,且 trim 空白后非空;
- 在 Unix 上,文件不得被 group 或 other 可读/可写(mode
0600或更严格)。更宽的权限是硬错误,错误信息会点名具体的 mode,这与 SFTP host-key 规则一致。RustFS 必须以文件所有者的身份运行。
若 agent 停止刷新文件,只要 token 本身在 Vault 侧仍然有效,RustFS 会持续重读同一个 token 并继续服务;若文件消失或变空,RustFS 会继续用最后读到的 token 服务请求,直到失败关闭窗口触发,文件恢复后自动自愈。
失败关闭窗口(Fail-closed Window)
对于带租约的凭据(AppRole token、Kubernetes token、token 文件),current()会拒绝发放已进入到期前安全窗口且尚未刷新的 token。请求此时以KMS credentials unavailable: ...失败——这比带着可能在飞行途中过期的 token 发出请求、在 Vault 侧产生不可预测的失败要安全得多。对应错误类型在 crates/kms/src/error.rs 中定义为KmsError::CredentialsUnavailable,审计分类为credentials_unavailable(见 crates/kms/src/audit.rs)。
| 方面 | 取值 |
|---|---|
| 默认窗口 | 一次尝试超时(RUSTFS_KMS_TIMEOUT_SECS,默认 30s):此刻发出的请求在理论上可以合法地在途这么久,因此 token 必须比它活得更久 |
| 覆盖方式 | AppRole、Kubernetes或TokenFile认证配置上的refresh_safety_window_secs |
| 静态 Token | 永不触发窗口:它们不携带租约,在 Vault 明确否定之前假定一直有效 |
该窗口是一个症状阈值而非故障本身:当它触发时,刷新已经失败了大约半个 token TTL(AppRole、Kubernetes)或两个轮询间隔(token 文件)。
在实现层面(vault_credentials.rs 的VaultCredentialProvider::current),窗口比较使用了checked_add饱和算术:若持久化的refresh_safety_window_secs大得无法与当前时刻相加,则饱和为"拒绝"——这既是失败关闭的正确答案,也正是算术原本要达成的结论(有专门测试test_current_refuses_rather_than_panics_on_an_unrepresentable_safety_window钉住该行为)。同理,Vault 返回的lease_duration若大到无法表示,则视同"永不过期",token 保持可用。
可观测性指标
续期循环会以固定节奏(CREDENTIAL_GAUGE_INTERVAL,10 秒)重发布两个无标签 gauge,避免 scrape 落在刷新间隙读到冻结的 TTL 或已翻转的失败关闭状态:
rustfs_kms_vault_token_ttl_seconds:当前 Vault token 到期前的剩余秒数(过期后为 0);rustfs_kms_vault_credentials_fail_closed:当current()因 token 进入安全窗口而拒绝发放时为 1,否则为 0。
由于 gauge 描述的是当前唯一安装的凭据 generation,且 Vault 地址、mount、auth path 与 token 都不允许作为标签值,这两个指标天然无标签。
Troubleshooting 速查表
| 症状 | 需要寻找的日志行 | 可能原因与修复 |
|---|---|---|
请求失败,报KMS credentials unavailable | Vault credential refresh failed; retrying until the credentials recover(warn,反复出现) | Vault 不可达/sealed,或凭据源损坏;一旦刷新成功 provider 自动恢复。修复根因即可,无需重启 |
| 续期成功但随后重登失败 | Vault token renewal failed; falling back to a fresh login后跟登录错误 | SecretID 过期/被吊销,或 AppRole role 被改动;轮换 secret_id 文件 |
| 启动或轮询时 token 文件权限错误 | 错误信息含has insecure permissions | 修复 sink 的mode(0600)与文件属主;下一个轮询周期自愈 provider |
| token 文件缺失/为空 | Failed to read Vault token file/token file ... is empty | Vault Agent 宕机或 sink 配置错误;重启 agent,下一个轮询周期自愈 |
| Kubernetes 登录权限错误 | Vault Kubernetes login failed | pod 的 ServiceAccount 不在 role 的bound_service_account_names/_namespaces中,或auth/kubernetes/config指向了错误的 API server |
| Kubernetes ServiceAccount token 错误 | Failed to read Kubernetes ServiceAccount token/ServiceAccount token ... is empty | token 未投影进 pod(检查automountServiceAccountToken与卷挂载);下一个刷新周期自愈 |
| 启动立即失败,报出两个环境变量名 | (无日志) | 同时配置了两种认证方法;保持 token、AppRole、Kubernetes、token file 四者中恰好一种 |
诊断顺序:三个时钟
诊断时请按顺序核对三个时钟/寿命:
- Vault token TTL:用
vault token lookup(配合 token 的 accessor)查看; - RustFS 刷新节奏:半 TTL 或轮询间隔;
- 失败关闭窗口:默认一次尝试超时。
续期任务会记录每次失败的周期,因此"警告静默 + 出现CredentialsUnavailable错误"的组合,指向的是进程时钟或运行时被暂停,而非 Vault 本身。
源码级验证:凭据链路的实现骨架
本文涉及的实现分布在以下文件中,供进一步深入:
- crates/kms/src/backends/vault_credentials.rs:
TokenSourcetrait(acquire/renew)、StaticToken/AppRoleLogin/KubernetesLogin/TokenFileSource四种实现、VaultCredentialProvider(单飞刷新、失败关闭)、renewal_loop(半 TTL 调度、5 秒重试间隔、gauge 发布);所有 secret 值使用zeroize在 Drop 时清零,Debug 输出统一走redacted_secret; - crates/kms/src/config.rs:
RUSTFS_KMS_BACKEND解析(vault/vault-kv2别名 KV2,vault-transit别名 Transit)、RUSTFS_KMS_TIMEOUT_SECS(默认 30 秒)、vault_auth_method_from_env的方法互斥校验、各 mount 默认值(approle、kubernetes、kubelet projected token 路径); - crates/kms/src/error.rs 与 crates/kms/src/audit.rs:
KMS credentials unavailable错误及credentials_unavailable审计分类; - docs/operations/kms-backend-security.md:KV2/Transit 策略作用域、Vault TLS(CA bundle / mTLS / skip verify)的完整参数表。
仓库还提供了真实 Vault 环境下的验证手段:
- 实时测试脚本 scripts/test/vault_approle_kms_live.sh:完整演示了 Vault 侧 AppRole role 创建(
token_ttl=10m、secret_id_num_uses=0)、ROLE_ID/SECRET_ID 获取,以及对 KV2(vault)与 Transit(vault-transit)两个后端的实测运行; - 集成测试 crates/kms/tests/vault_approle_live.rs、crates/kms/tests/vault_fault_injection.rs 与 crates/kms/tests/vault_ha_failover_live.rs:钉住失败重试、故障注入与 HA 切换下的凭据行为。
总结
RustFS 的 Vault 认证体系围绕一个核心设计展开:带租约凭据永远由后台刷新循环托管,任何接近过期的 token 都在本地失败关闭,而不是带着隐患发往 Vault。无论选择 AppRole(无 sidecar 的生产)、Kubernetes(容器化首选)还是 Vault Agent Token File(外部托管认证),配置的公共骨架是一致的——恰好一种认证方法、Vault 地址、TLS 材料与超时窗口。诊断任何凭据问题时,按照"Vault TTL → RustFS 刷新节奏 → 失败关闭窗口"的顺序核对三个时钟,配合续期循环的 warn 日志与两个无标签 gauge,即可快速定位是 Vault 侧故障、文件/权限问题还是时钟异常。
【免费下载链接】rustfs🚀2.3x faster than MinIO for 4KB object payloads. RustFS is an open-source, S3-compatible high-performance object storage system supporting migration and coexistence with other S3-compatible platforms such as MinIO and Ceph.项目地址: https://gitcode.com/GitHub_Trending/rus/rustfs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考