如何在本地 Azure DevOps Server 流水线中接入 HashiCorp Vault?
可以。在本地 Azure DevOps Server 2022.2 的流水线中,执行任务的 agent 可以通过 Vault HTTP API 或 CLI 完成认证并读取 secrets,再把所需的值提供给后续任务。这个过程可以用脚本实现,无需依赖所提到的第三方扩展。
可以先尝试使用 AppRole 登录 Vault,获取短期 Vault token,读取指定路径的密钥,再通过 Azure Pipelines 的秘密变量传递给同一 job 中的后续步骤。AppRole 用于机器和应用认证,默认通过 role_id 和 secret_id 换取 token。Vault AppRole 文档
为什么不能直接照搬云端指南?
问题中引用的指南依赖 Azure DevOps service connection 的 workload identity federation,并使用相应身份向 Vault 认证。如果本地环境无法完成这些配置,可以选择 AppRole 等认证方式。
接入时要分别解决两个问题:agent 如何向 Vault 证明身份,以及认证成功后允许读取哪些 secrets。Vault 的认证方法和访问策略负责这两部分,流水线脚本负责调用接口和传递结果。云端指南中的 WIF 流程只是其中一条接入路径。HashiCorp 集成指南
将现有 Library 的名称或地址改成 Vault,变量组也不会自动读取 Vault。使用脚本方案时,需要显式增加读取步骤。
接入前需要准备什么?
先确认以下条件:
- 执行流水线的 agent 能访问 Vault,且信任 Vault 的 HTTPS 证书。如果使用内部 CA,应将其加入 agent 的信任配置。
- Vault 管理员已启用 AppRole,并为对应流水线建立角色和访问策略。
- 已确定密钥所在的 secrets engine、挂载路径、密钥路径及字段名。
- 已安排 AppRole 初始凭据的安全交付与轮换方式。
例如,假设使用 KV v2,挂载点为 secret,密钥路径为 ci/myapp,其中有一个 password 字段。只允许读取该条密钥的策略可以写成:
path "secret/data/ci/myapp" {
capabilities = ["read"]
}
将该策略绑定到流水线使用的 AppRole。KV v2 的数据读取 API 路径包含 /data/;KV v1 的路径和响应结构不同,不能直接套用下面的示例。上述策略授权读取整条密钥记录,并不会把权限进一步限制到 password 这个字段。KV v2 API 文档
如何在流水线中读取并传递 secrets?
下面的示例使用 Linux agent、Bash 和 Python 3 标准库,不需要安装 Vault CLI。示例假设:
- agent pool 名称为
SelfHostedLinux,需要替换为实际名称。 - 已建立变量组
vault-bootstrap,并授权目标流水线使用。 - 变量组包含
VAULT_ADDR、VAULT_ROLE_ID、VAULT_SECRET_ID,其中VAULT_SECRET_ID必须设为秘密变量。 VAULT_ADDR类似https://vault.example.internal:8200,不包含/v1。- AppRole 挂载在默认的
auth/approle,未使用 Vault namespace。 - 仓库中的
deploy.sh会从环境变量APP_PASSWORD读取密码;实际使用时替换为项目自己的任务。
pool:
name: SelfHostedLinux
variables:
- group: vault-bootstrap
steps:
- bash: |
set +x
set -euo pipefail
python3 - <<'PY'
import json
import os
import urllib.request
base = os.environ["VAULT_ADDR"].rstrip("/")
if not base.startswith("https://"):
raise ValueError("VAULT_ADDR must use HTTPS")
def request_json(method, path, payload=None, token=None):
headers = {"Content-Type": "application/json"}
if token is not None:
headers["X-Vault-Token"] = token
body = None if payload is None else json.dumps(payload).encode("utf-8")
request = urllib.request.Request(
base + "/v1/" + path,
data=body,
headers=headers,
method=method,
)
with urllib.request.urlopen(request, timeout=30) as response:
return json.load(response)
login = request_json("POST", "auth/approle/login", {
"role_id": os.environ["VAULT_ROLE_ID"],
"secret_id": os.environ["VAULT_SECRET_ID"],
})
token = login["auth"]["client_token"]
result = request_json("GET", "secret/data/ci/myapp", token=token)
password = result["data"]["data"]["password"]
if not isinstance(password, str) or not password:
raise ValueError("Expected a non-empty password string")
escaped = password.replace("%", "%AZP25")
escaped = escaped.replace("\r", "%0D").replace("\n", "%0A")
print("##vso[task.setvariable variable=APP_PASSWORD;issecret=true]" + escaped)
PY
displayName: Read secret from Vault
env:
VAULT_ADDR: $(VAULT_ADDR)
VAULT_ROLE_ID: $(VAULT_ROLE_ID)
VAULT_SECRET_ID: $(VAULT_SECRET_ID)
- bash: |
set +x
set -euo pipefail
./deploy.sh
displayName: Deploy
env:
APP_PASSWORD: $(APP_PASSWORD)
登录使用 POST /v1/auth/approle/login,返回的 auth.client_token 用于后续请求。AppRole API 文档
读取步骤通过 task.setvariable 将值标记为秘密变量。秘密变量不会自动成为每个任务的环境变量,后续任务需要通过 env 显式接收。示例还对日志命令中的百分号和换行符做了转义。Azure Pipelines logging commands
使用 Vault 后,还需要在 Library 中保存凭据吗?
如果采用上面的 AppRole 方案,并通过变量组交付 secret_id,仍需要保存一份用于首次认证的凭据。初始信任问题需要单独处理。
业务密码可以迁移到 Vault,Library 暂时只保存受限的认证凭据。后续可根据基础设施条件,改为由可信系统按次交付 SecretID,或使用短期 response-wrapping token。Response wrapping 能减少 SecretID 在交付过程中的直接暴露,但仍需要安排谁签发、谁交付以及何时交付。AppRole 凭据交付说明
不要为了省去这一步,把 root token 或高权限长期 token 放进流水线。
多条流水线接入时有哪些注意事项?
- 按项目和环境划分 AppRole、策略与 agent 使用权限,避免所有流水线共享能够读取全部 secrets 的身份。
- 为 Vault token 和 SecretID 分别配置合适的有效期与使用次数。一次性 SecretID 需要每次登录前重新交付,不能长期放在变量组中重复使用。示例未主动撤销 token,因此必须设置适当的 token TTL。
- 示例变量用于同一 job 的后续步骤。跨 job 或 stage 时,需要配置输出变量及依赖关系,或者让每个 job 分别向 Vault 认证并读取所需值。
- 禁止脚本跟踪和直接打印 secrets,也不要把它们写入构建产物。日志脱敏不能遮盖所有派生值或子字符串;能修改流水线脚本的人,也可能利用脚本读取任务可访问的密钥。
- 如果使用动态数据库凭据等带租约的 secrets,还需要安排租约续期、撤销及使用时长。读取 KV 静态密码的示例没有覆盖这些生命周期操作。
可以先选一条流水线和一条受限密钥完成接入,再把认证、读取及变量设置步骤整理成共享 YAML 模板,供其他流水线复用。
备注:内容仅供参考。
