能否通过Azure DevOps管道服务连接实现Azure AD服务主体客户端令牌自动轮换?
自动轮换Azure DevOps服务连接的Azure AD服务主体令牌方案
主流实践:Azure Key Vault + Azure Functions 自动化方案
这是社区广泛采用的可靠方案,通过云服务组合实现全流程自动轮换,无需手动干预。
前置准备
- 创建并配置Azure Key Vault:启用软删除与 purge 保护,创建两个机密项,分别存储服务主体的客户端ID和初始的客户端密钥。
- 配置权限:
- 给Azure Functions分配Key Vault的
Secret User权限,用于读取和更新机密。 - 给Functions或运行身份分配Azure DevOps的
Project Administrator(或集合级管理员)权限,用于更新服务连接。 - 确保运行身份拥有Azure AD的
Application Administrator权限,用于为服务主体生成新密钥。
- 给Azure Functions分配Key Vault的
核心实现步骤
编写Azure Functions逻辑
采用定时器触发器(可根据令牌过期周期设置触发时间,比如过期前7天执行),核心逻辑分为三步:- 调用Microsoft Graph API为服务主体生成新的客户端密钥,设置合理的过期时间。
- 将新密钥更新到Azure Key Vault的对应机密项中(支持版本化存储,便于回溯)。
- 调用Azure DevOps REST API,定位目标服务连接,更新其授权参数中的客户端密钥。
示例Python代码片段:
import os import msal import requests from azure.keyvault.secrets import SecretClient from azure.identity import DefaultAzureCredential # 初始化客户端 kv_vault_url = os.environ["KEY_VAULT_URL"] kv_client = SecretClient(vault_url=kv_vault_url, credential=DefaultAzureCredential()) devops_pat = os.environ["DEVOPS_PAT"] tenant_id = os.environ["TENANT_ID"] sp_client_id = kv_client.get_secret("sp-client-id").value old_secret = kv_client.get_secret("sp-client-secret").value # 1. 生成新的服务主体密钥 graph_app = msal.ConfidentialClientApplication( sp_client_id, client_credential=old_secret, authority=f"https://login.microsoftonline.com/{tenant_id}" ) graph_token = graph_app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"]) new_secret_req = requests.post( f"https://graph.microsoft.com/v1.0/applications/{sp_client_id}/addPassword", headers={"Authorization": f"Bearer {graph_token['access_token']}"}, json={"passwordCredential": {"displayName": "Auto-Rotated-Secret", "endDateTime": "2025-12-31T23:59:59Z"}} ) new_secret = new_secret_req.json()["secretText"] # 2. 更新Key Vault中的机密 kv_client.set_secret("sp-client-secret", new_secret) # 3. 更新Azure DevOps服务连接 devops_headers = {"Authorization": f"Basic {devops_pat}", "Content-Type": "application/json"} service_conn_id = os.environ["SERVICE_CONNECTION_ID"] devops_org = os.environ["DEVOPS_ORG"] devops_project = os.environ["DEVOPS_PROJECT"] requests.patch( f"https://dev.azure.com/{devops_org}/{devops_project}/_apis/serviceendpoint/endpoints/{service_conn_id}?api-version=7.1-preview.4", headers=devops_headers, json={ "authorization": { "parameters": {"serviceprincipalkey": new_secret}, "scheme": "ServicePrincipal" } } )配置触发与监控
- 设置定时器表达式(例如
0 0 12 * * 1,每周一中午执行,根据密钥有效期调整)。 - 启用Azure Application Insights监控Functions运行状态,配置失败警报,确保轮换异常时能及时告警。
- 设置定时器表达式(例如
其他可选方案
- 直接引用Key Vault机密的服务连接:若你的服务连接类型支持,可在Azure DevOps服务连接配置中选择"使用Azure Key Vault",直接引用存储在Key Vault的服务主体密钥。后续仅需轮换Key Vault中的机密,无需更新服务连接(部分服务类型支持此方式,需提前验证兼容性)。
- Azure Automation Runbook:逻辑与Functions类似,使用PowerShell或Python编写Runbook,通过定时器触发完成密钥轮换与服务连接更新,适合已使用Automation账户的场景。
关键注意事项
- 保留旧密钥24小时以上,避免因系统缓存导致服务连接短暂中断。
- 先在非生产环境测试轮换流程,验证无误后再推广到生产环境。
- 确保服务主体和运行身份的权限最小化,遵循权限原则。
内容的提问来源于stack exchange,提问作者Jonah Andersson
相关产品推荐
相关产品推荐

