PowerShell 技能连载 - Azure Key Vault 密钥管理

适用于 PowerShell 7.0 及以上版本

在现代 DevOps 和云原生环境中,密钥管理是安全体系的重要基石。数据库连接字符串、API 密钥、存储账户凭据等敏感信息如果以明文形式散落在脚本、配置文件或环境变量中,一旦代码仓库泄露,攻击者就能直接获取这些凭据,进而访问生产环境的各类服务。这种安全隐患在自动化程度越高的团队中,影响面越大。

Azure Key Vault 是微软 Azure 提供的集中式密钥管理服务,支持存储机密(Secrets)、加密密钥(Keys)和证书(Certificates)。它不仅提供了硬件安全模块(HSM)保护的加密存储,还支持基于 Azure Active Directory 的细粒度访问控制、完整的访问日志审计以及自动化的密钥轮换策略。通过 PowerShell 的 Az.KeyVault 模块,我们可以将密钥的创建、读取、轮换和审计全部纳入自动化流水线。

本文将从三个实战场景出发,演示如何使用 PowerShell 完成密钥的创建与存储、在自动化脚本中安全引用密钥,以及建立密钥轮换与合规审计机制,帮助团队彻底消除脚本中的硬编码凭据。

创建 Key Vault 并存储密钥

第一步是创建 Key Vault 并将常用的敏感信息存储进去。以下脚本展示了完整的初始化流程,包括资源组创建、Key Vault 实例创建以及多种类型机密的写入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 定义变量
$ResourceGroup = 'rg-security-demo'
$Location = 'eastasia'
$VaultName = 'kv-pstip-demo-2025'

# 连接 Azure 账户(交互式登录)
Connect-AzAccount

# 创建资源组
New-AzResourceGroup -Name $ResourceGroup -Location $Location

# 创建 Key Vault(启用软删除和清除保护)
New-AzKeyVault `
-ResourceGroupName $ResourceGroup `
-VaultName $VaultName `
-Location $Location `
-EnablePurgeProtection `
-EnableSoftDelete

# 设置当前用户为机密管理员
$currentUser = (Get-AzContext).Account.Id
Set-AzKeyVaultAccessPolicy `
-VaultName $VaultName `
-UserPrincipalName $currentUser `
-PermissionsToSecrets get,set,list,delete,recover

# 存储数据库连接字符串
$dbConnectionString = 'Server=tcp:prod-sql.database.windows.net,1433;Database=AppDb;User ID=appuser;Password=P@ssw0rd!2025;'
$secretValue = ConvertTo-SecureString $dbConnectionString -AsPlainText -Force
Set-AzKeyVaultSecret `
-VaultName $VaultName `
-Name 'DatabaseConnectionString' `
-SecretValue $secretValue `
-ContentType 'text/plain'

# 存储 API 密钥(带自定义元数据和过期时间)
$apiKey = 'sk-proj-abc123def456ghi789jkl012mno345pqr678'
$apiKeySecret = ConvertTo-SecureString $apiKey -AsPlainText -Force
$expiryDate = (Get-Date).AddDays(90)
Set-AzKeyVaultSecret `
-VaultName $VaultName `
-Name 'ExternalServiceApiKey' `
-SecretValue $apiKeySecret `
-Expires $expiryDate

# 存储存储账户凭据(使用标签标记用途)
$storageKey = 'DefaultEndpointsProtocol=https;AccountName=prodstorage;AccountKey=xyz789==;EndpointSuffix=core.windows.net'
$storageSecret = ConvertTo-SecureString $storageKey -AsPlainText -Force
$tags = @{
Environment = 'Production'
Service = 'BlobStorage'
Team = 'DevOps'
RotatedDate = (Get-Date).ToString('yyyy-MM-dd')
}
Set-AzKeyVaultSecret `
-VaultName $VaultName `
-Name 'StorageAccountKey' `
-SecretValue $storageSecret `
-Tag $tags

# 列出 Vault 中所有机密
Get-AzKeyVaultSecret -VaultName $VaultName |
Select-Object Name, ContentType, Expires, Enabled |
Format-Table -AutoSize

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ResourceGroupName : rg-security-demo
Location : eastasia
ProvisioningState : Succeeded

Vault Name : kv-pstip-demo-2025
Resource Group Name : rg-security-demo
Location : eastasia
Resource ID : /subscriptions/xxxx/resourceGroups/rg-security-demo/providers/Microsoft.KeyVault/vaults/kv-pstip-demo-2025
Vault URI : https://kv-pstip-demo-2025.vault.azure.net/
Tenant ID : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
SKU : Standard
Enabled For Deployment? : False
Enabled For Template Deployment? : False
Enabled For Disk Encryption? : False
Soft Delete Enabled? : True
Purge Protection Enabled? : True

Name ContentType Expires Enabled
---- ----------- ------- -------
DatabaseConnectionString text/plain True
ExternalServiceApiKey 2026-03-23 08:00:00 True
StorageAccountKey 2026-12-23 08:00:00 True

在自动化脚本中安全引用密钥

密钥存入 Key Vault 后,自动化脚本不再需要硬编码任何凭据。以下示例展示了如何使用托管标识(Managed Identity)或服务主体来读取密钥,并直接应用到数据库连接、API 调用等场景中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# 方式一:使用服务主体进行非交互式认证(适用于 CI/CD 流水线)
$tenantId = $env:AZURE_TENANT_ID
$appId = $env:AZURE_CLIENT_ID
$appSecret = ConvertTo-SecureString $env:AZURE_CLIENT_SECRET -AsPlainText -Force

$credential = New-Object System.Management.Automation.PSCredential($appId, $appSecret)
Connect-AzAccount `
-ServicePrincipal `
-TenantId $tenantId `
-Credential $credential

# 方式二:使用托管标识(适用于 Azure VM / App Service / Functions)
# Connect-AzAccount -Identity

$VaultName = 'kv-pstip-demo-2025'

# 读取数据库连接字符串
$dbSecret = Get-AzKeyVaultSecret `
-VaultName $VaultName `
-Name 'DatabaseConnectionString'
$dbConnectionString = $dbSecret.SecretValue | ConvertFrom-SecureString -AsPlainText

# 使用密钥连接数据库(以 SqlConnection 为例)
$connection = New-Object System.Data.SqlClient.SqlConnection($dbConnectionString)
$connection.Open()
Write-Host "数据库连接成功,服务器版本:$($connection.ServerVersion)"
$connection.Close()

# 读取 API 密钥并调用外部服务
$apiSecret = Get-AzKeyVaultSecret `
-VaultName $VaultName `
-Name 'ExternalServiceApiKey'
$apiKey = $apiSecret.SecretValue | ConvertFrom-SecureString -AsPlainText

$headers = @{
'Authorization' = "Bearer $apiKey"
'Content-Type' = 'application/json'
}
$response = Invoke-RestMethod `
-Uri 'https://api.example.com/v1/status' `
-Headers $headers `
-Method Get

Write-Host "API 调用成功,状态:$($response.status)"

# 封装为可复用的辅助函数
function Get-KeyVaultSecretText {
<#
.SYNOPSIS
从 Key Vault 读取机密并返回明文字符串
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$VaultName,

[Parameter(Mandatory)]
[string]$SecretName,

[string]$Version
)

$params = @{
VaultName = $VaultName
Name = $SecretName
}
if ($Version) {
$params['Version'] = $Version
}

$secret = Get-AzKeyVaultSecret @params
if (-not $secret) {
throw "机密 '$SecretName' 在 Vault '$VaultName' 中不存在"
}

return $secret.SecretValue | ConvertFrom-SecureString -AsPlainText
}

# 使用辅助函数
$storageKey = Get-KeyVaultSecretText -VaultName $VaultName -SecretName 'StorageAccountKey'
Write-Host "成功读取存储账户密钥,长度:$($storageKey.Length) 字符"

执行结果示例:

1
2
3
数据库连接成功,服务器版本:Microsoft SQL Server 2019 (RTM)
API 调用成功,状态:healthy
成功读取存储账户密钥,长度:142 字符

密钥轮换策略与合规审计

密钥的定期轮换是安全合规的基本要求。以下脚本演示了如何实现自动化的密钥轮换流程,并通过 Key Vault 的诊断日志功能进行访问审计,确保所有密钥操作可追溯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
$VaultName = 'kv-pstip-demo-2025'

# --- 密钥轮换 ---

# 查找即将过期(30 天内)的机密
$warningDate = (Get-Date).AddDays(30)
$expiringSecrets = Get-AzKeyVaultSecret -VaultName $VaultName |
Where-Object { $_.Expires -and $_.Expires -le $warningDate -and $_.Enabled }

Write-Host "发现 $($expiringSecrets.Count) 个即将过期的机密:"
$expiringSecrets | Select-Object Name, Expires | Format-Table

# 轮换函数:生成新密钥值并更新到 Key Vault
function Invoke-SecretRotation {
[CmdletBinding()]
param(
[string]$VaultName,
[string]$SecretName,
[int]$ValidityDays = 90
)

# 获取当前机密信息
$current = Get-AzKeyVaultSecret -VaultName $VaultName -Name $SecretName
if (-not $current) {
Write-Warning "机密 $SecretName 不存在,跳过"
return
}

# 根据机密类型生成新值(示例使用随机字符串)
$newSecretValue = -join ((48..57) + (65..90) + (97..122) |
Get-Random -Count 48 |
ForEach-Object { [char]$_ })

$newSecret = ConvertTo-SecureString $newSecretValue -AsPlainText -Force

# 更新机密(Key Vault 会自动创建新版本)
$newExpiry = (Get-Date).AddDays($ValidityDays)
Set-AzKeyVaultSecret `
-VaultName $VaultName `
-Name $SecretName `
-SecretValue $newSecret `
-Expires $newExpiry `
-Tag @{
RotatedDate = (Get-Date).ToString('yyyy-MM-dd')
RotatedBy = $env:USERNAME
PreviousVersion = $current.Version
}

Write-Host "已轮换机密 '$SecretName',新过期时间:$newExpiry"
}

# 对即将过期的机密执行轮换
foreach ($secret in $expiringSecrets) {
Invoke-SecretRotation `
-VaultName $VaultName `
-SecretName $secret.Name `
-ValidityDays 90
}

# --- 合规审计 ---

# 查看 Key Vault 的访问策略
$accessPolicies = (Get-AzKeyVault -VaultName $VaultName).AccessPolicies
Write-Host "`n当前访问策略(共 $($accessPolicies.Count) 条):"
$accessPolicies | ForEach-Object {
[PSCustomObject]@{
PrincipalId = $_.ObjectId
Permissions = ($_.PermissionsToSecrets -join ', ')
ApplicationId = $_.ApplicationId
}
} | Format-Table -AutoSize

# 获取机密版本历史(确认轮换记录)
$secretVersions = Get-AzKeyVaultSecret `
-VaultName $VaultName `
-Name 'ExternalServiceApiKey' `
-IncludeVersions |
Select-Object Version, Created, Expires, Enabled |
Sort-Object Created -Descending |
Select-Object -First 5

Write-Host "`n机密 'ExternalServiceApiKey' 最近 5 个版本:"
$secretVersions | Format-Table -AutoSize

# 生成合规报告
$allSecrets = Get-AzKeyVaultSecret -VaultName $VaultName
$report = $allSecrets | ForEach-Object {
$detail = Get-AzKeyVaultSecret -VaultName $VaultName -Name $_.Name
[PSCustomObject]@{
Name = $_.Name
Enabled = $_.Enabled
Created = $_.Created.ToString('yyyy-MM-dd')
Updated = $_.Updated.ToString('yyyy-MM-dd')
Expires = if ($_.Expires) { $_.Expires.ToString('yyyy-MM-dd') } else { '永不过期' }
DaysUntilExpiry = if ($_.Expires) { ($_.Expires - (Get-Date)).Days } else { 'N/A' }
Status = if (-not $_.Enabled) { '已禁用' }
elseif ($_.Expires -and $_.Expires -le (Get-Date)) { '已过期' }
elseif ($_.Expires -and ($_.Expires - (Get-Date)).Days -le 30) { '即将过期' }
else { '正常' }
}
}

$reportPath = "./keyvault-compliance-report-$(Get-Date -Format 'yyyyMMdd').csv"
$report | Export-Csv -Path $reportPath -NoTypeInformation -Encoding Utf8
Write-Host "`n合规报告已导出:$reportPath"
$report | Format-Table -AutoSize

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
发现 2 个即将过期的机密:

Name Expires
---- -------
ExternalServiceApiKey 2026-03-23 08:00:00
StorageAccountKey 2025-12-30 08:00:00

已轮换机密 'ExternalServiceApiKey',新过期时间:2026-03-23 08:00:00
已轮换机密 'StorageAccountKey',新过期时间:2026-03-22 08:00:00

当前访问策略(共 1 条):

PrincipalId Permissions ApplicationId
----------- ------------ -------------
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx get,set,list,delete,recover,purge,backup,restore

机密 'ExternalServiceApiKey' 最近 5 个版本:

Version Created Expires Enabled
------- ------- ------- -------
abc12345def67890 2025-12-23 08:00:00 2026-03-23 08:00:00 True
fed09876cba54321 2025-12-23 07:55:00 2026-03-23 08:00:00 True

合规报告已导出:./keyvault-compliance-report-20251223.csv

Name Enabled Created Updated Expires DaysUntilExpiry Status
---- ------- ------- ------- ------- --------------- ------
DatabaseConnectionString True 2025-12-23 2025-12-23 永不过期 N/A 正常
ExternalServiceApiKey True 2025-12-23 2025-12-23 2026-03-23 90 正常
StorageAccountKey True 2025-12-23 2025-12-23 2026-03-22 89 正常

注意事项

  1. 使用托管标识代替服务主体:在 Azure VM、App Service 或 Azure Functions 中运行脚本时,优先使用系统分配的托管标识(Connect-AzAccount -Identity),避免在环境中再存储服务主体的凭据,从根本上消除凭据泄露风险。

  2. 启用软删除和清除保护:创建 Key Vault 时务必启用 EnableSoftDeleteEnablePurgeProtection。软删除保留了 90 天的恢复窗口,清除保护则防止恶意删除后立即清空,两者配合可以有效防御勒索类攻击。

  3. 遵循最小权限原则:为不同的应用和服务主体分配最小必要的权限。只读工作负载只需 GetList 权限,轮换脚本需要 SetDelete 权限。避免为所有主体授予完全控制权限。

  4. 密钥轮换后同步下游服务:轮换密钥后,所有使用该密钥的下游应用需要及时获取新版本。建议在应用层实现自动重试逻辑,或者在轮换后触发一次服务重启或配置热加载,确保不会出现认证失败。

  5. 定期审计访问日志:通过 Key Vault 的诊断设置,将访问日志导出到 Log Analytics 工作区或存储账户。定期检查异常访问模式(如非工作时间的频繁读取、未知主体的访问尝试),及时发现潜在的安全威胁。

  6. 不要将密钥输出到日志或控制台:在调试时避免使用 Write-Host 输出机密明文。如果必须记录操作,只记录机密名称和操作结果,永远不要记录 SecretValue 的内容。CI/CD 流水线中也应屏蔽包含密钥值的变量输出。

PowerShell 技能连载 - Azure Key Vault 密钥管理

适用于 PowerShell 5.1 及以上版本

背景引入

在现代云原生应用的运维与开发中,密钥管理是安全体系中不可或缺的一环。数据库连接字符串、API 密钥、证书私钥等敏感信息如果以明文形式存储在代码仓库或配置文件中,一旦泄露就会造成严重的安全事故。Azure Key Vault 正是微软 Azure 平台提供的集中式密钥管理服务,它可以安全地存储和管理密钥(Key)、机密(Secret)及证书(Certificate)。

对于 PowerShell 用户而言,通过 Az.KeyVault 模块可以方便地完成 Key Vault 的创建、密钥的读写、访问策略的配置等操作。无论是日常运维脚本中拉取数据库密码,还是在 CI/CD 流水线中注入签名证书,PowerShell 都提供了简洁高效的 cmdlet 来实现这些需求。

本文将围绕实际场景,演示如何使用 PowerShell 完成 Azure Key Vault 的创建、Secret 的增删改查、批量操作以及访问权限控制。

前置准备

在开始操作 Key Vault 之前,需要确保已安装 Azure PowerShell 模块并完成登录认证。

1
2
3
4
5
6
7
8
# 安装 Az 模块(如果尚未安装)
Install-Module -Name Az -Repository PSGallery -Scope CurrentUser -Force

# 登录 Azure 账户(会弹出浏览器窗口进行交互式认证)
Connect-AzAccount

# 查看当前订阅信息,确认上下文正确
Get-AzContext | Select-Object Account, Subscription, Tenant

登录成功后,终端会显示当前账户的订阅和租户信息,确认是你期望操作的 Azure 订阅即可继续。

1
2
3
Account        SubscriptionName   TenantId
------- ---------------- --------
user@demo.com MySubscription xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

创建 Key Vault

Azure Key Vault 需要一个全局唯一的名称,并且必须指定资源组和所在区域。下面通过 PowerShell 一键完成创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 定义变量
$resourceGroupName = "rg-keyvault-demo"
$location = "EastAsia"
$vaultName = "kv-demo-$(Get-Random -Minimum 1000 -Maximum 9999)"

# 创建资源组(如果不存在)
New-AzResourceGroup -Name $resourceGroupName -Location $location -Force

# 创建 Key Vault
# 启用软删除(Soft Delete)和清除保护(Purge Protection)以增强安全性
$newVaultSplat = @{
VaultName = $vaultName
ResourceGroupName = $resourceGroupName
Location = $location
EnableSoftDelete = $true
EnablePurgeProtection = $true
Sku = "Standard"
}
$vault = New-AzKeyVault @newVaultSplat

Write-Host "Key Vault 创建成功: $($vault.VaultName)"
Write-Host "Vault URI: $($vault.VaultUri)"

这段脚本首先创建了一个资源组作为容器,然后在其中创建 Key Vault。EnableSoftDelete 参数确保删除的密钥可以在保留期内恢复,EnablePurgeProtection 则防止密钥被立即永久清除。

1
2
Key Vault 创建成功: kv-demo-7421
Vault URI: https://kv-demo-7421.vault.azure.net/

管理机密(Secret)

Key Vault 最常见的用途就是存储和管理机密信息。下面演示如何创建、读取、更新和删除 Secret。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 创建一个数据库连接字符串作为 Secret
$secretValue = ConvertTo-SecureString -String "Server=myserver.database.windows.net;Database=mydb;User=admin;Password=P@ssw0rd!" -AsPlainText -Force

$setSecretSplat = @{
VaultName = $vaultName
Name = "DatabaseConnectionString"
SecretValue = $secretValue
ContentType = "text/plain"
Tag = @{ Environment = "Production"; Owner = "DBA-Team" }
}
$secret = Set-AzKeyVaultSecret @setSecretSplat

Write-Host "Secret 创建成功"
Write-Host "名称: $($secret.Name)"
Write-Host "版本: $($secret.Version)"
Write-Host "过期时间: $($secret.Expires)"

# 读取 Secret 的值
$retrievedSecret = Get-AzKeyVaultSecret -VaultName $vaultName -Name "DatabaseConnectionString"
$plainText = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($retrievedSecret.SecretValue)
)
Write-Host "读取到的连接字符串: $plainText"

注意,Key Vault 返回的 Secret 值始终是 SecureString 类型。如果你需要在脚本中获取明文值(例如传递给数据库驱动),需要通过 Marshal 类进行转换。在实际生产环境中,应尽量避免将明文输出到控制台。

1
2
3
4
5
6
Secret 创建成功
名称: DatabaseConnectionString
版本: 1a2b3c4d5e6f7890abcdef1234567890
过期时间:

读取到的连接字符串: Server=myserver.database.windows.net;Database=mydb;User=admin;Password=P@ssw0rd!

批量管理 Secret

在真实项目中,我们往往需要一次性管理多个配置项。例如将应用的所有环境变量从本地配置文件批量导入到 Key Vault 中。下面演示一个批量写入和批量读取的完整流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 定义多个配置项(模拟从配置文件读取)
$configItems = @(
@{ Name = "App-Api-Key"; Value = "sk-api-20251023-abcdefg123456" }
@{ Name = "App-Jwt-Secret"; Value = "my-super-secret-jwt-key-2025" }
@{ Name = "App-Redis-Connection"; Value = "redis://cache.redis.cache.windows.net:6380,password=xxx" }
@{ Name = "App-Storage-Key"; Value = "storageAccountAccessKeyXYZ789" }
@{ Name = "App-SendGrid-Key"; Value = "SG.sendgridapikey123456" }
)

# 批量写入 Key Vault
foreach ($item in $configItems) {
$secureValue = ConvertTo-SecureString -String $item.Value -AsPlainText -Force

$setSplat = @{
VaultName = $vaultName
Name = $item.Name
SecretValue = $secureValue
}
Set-AzKeyVaultSecret @setSplat | Out-Null
Write-Host "已写入 Secret: $($item.Name)"
}

Write-Host "`n批量写入完成,共 $($configItems.Count) 个 Secret"

# 批量读取并汇总
Write-Host "`n--- 当前 Key Vault 中的所有 Secret ---"
$allSecrets = Get-AzKeyVaultSecret -VaultName $vaultName

foreach ($s in $allSecrets) {
$detail = Get-AzKeyVaultSecret -VaultName $vaultName -Name $s.Name
$tagsStr = ""
if ($detail.Tags) {
$tagParts = @()
foreach ($key in $detail.Tags.Keys) {
$tagParts += "$key=$($detail.Tags[$key])"
}
$tagsStr = $tagParts -join ", "
}
Write-Host ("{0,-30} 创建于: {1}" -f $s.Name, $detail.Created.ToString("yyyy-MM-dd HH:mm"))
}

这段代码首先定义了一个配置数组,然后通过 foreach 循环逐个写入 Key Vault。读取时先获取所有 Secret 的列表,再逐个获取详细信息。这种方式适合在应用部署流水线中做配置初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
已写入 Secret: App-Api-Key
已写入 Secret: App-Jwt-Secret
已写入 Secret: App-Redis-Connection
已写入 Secret: App-Storage-Key
已写入 Secret: App-SendGrid-Key

批量写入完成,共 5 个 Secret

--- 当前 Key Vault 中的所有 Secret ---
DatabaseConnectionString 创建于: 2025-10-23 08:15
App-Api-Key 创建于: 2025-10-23 08:16
App-Jwt-Secret 创建于: 2025-10-23 08:16
App-Redis-Connection 创建于: 2025-10-23 08:16
App-Storage-Key 创建于: 2025-10-23 08:16
App-SendGrid-Key 创建于: 2025-10-23 08:16

配置访问策略

Key Vault 默认采用访问策略(Access Policy)模型来控制谁可以执行什么操作。创建者自动拥有全部权限,但对于团队协作场景,需要精确地为不同角色分配最小权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 获取当前用户或服务主体的对象 ID
$currentUser = Get-AzADUser -SignedIn

# 为当前用户设置只读权限(Get 和 List)
$readOnlySplat = @{
VaultName = $vaultName
UserPrincipalName = $currentUser.UserPrincipalName
PermissionsToSecrets = "Get", "List"
}
Set-AzKeyVaultAccessPolicy @readOnlySplat

Write-Host "已为用户 $($currentUser.UserPrincipalName) 设置只读权限"

# 为另一个团队成员设置完整 Secret 权限
$devOpsUpn = "devops@demo.com"
$devOpsSplat = @{
VaultName = $vaultName
UserPrincipalName = $devOpsUpn
PermissionsToSecrets = "Get", "List", "Set", "Delete", "Recover", "Backup", "Restore", "Purge"
}
Set-AzKeyVaultAccessPolicy @devOpsSplat

Write-Host "已为用户 $devOpsUpn 设置完整 Secret 管理权限"

# 查看当前所有访问策略
$vault = Get-AzKeyVault -VaultName $vaultName
Write-Host "`n--- 当前访问策略 ---"
foreach ($policy in $vault.AccessPolicies) {
$secrets = $policy.PermissionsToSecrets -join ", "
Write-Host ("对象ID: {0} | Secret权限: {1}" -f $policy.ObjectId, $secrets)
}

在实际项目中,建议遵循最小权限原则:运维人员只授予 GetList 权限,密钥管理员才授予 SetDelete 等写入权限。对于应用程序,可以使用托管标识(Managed Identity)代替服务主体,避免在代码中硬编码凭据。

1
2
3
4
5
6
7
已为用户 user@demo.com 设置只读权限
已为用户 devops@demo.com 设置完整 Secret 管理权限

--- 当前访问策略 ---
对象ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | Secret权限: Get, List, Set, Delete, Recover, Backup, Restore, Purge
对象ID: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy | Secret权限: Get, List
对象ID: zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz | Secret权限: Get, List, Set, Delete, Recover, Backup, Restore, Purge

设置 Secret 过期与版本管理

Key Vault 支持 Secret 的自动过期和版本管理。当密钥需要定期轮换时(例如每 90 天更换一次 API 密钥),可以通过设置过期时间来实现自动提醒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 为已有的 Secret 设置过期时间
$expireDate = (Get-Date).AddDays(90)

$secretValue = ConvertTo-SecureString -String "sk-new-api-key-$(Get-Random -Minimum 100000 -Maximum 999999)" -AsPlainText -Force

$setExpiringSplat = @{
VaultName = $vaultName
Name = "App-Api-Key"
SecretValue = $secretValue
Expires = $expireDate
ContentType = "text/plain"
Tag = @{ RotatedBy = "PowerShell-Script"; RotationCycle = "90-days" }
}
$newVersion = Set-AzKeyVaultSecret @setExpiringSplat

Write-Host "Secret 已更新,新版本过期时间: $($newVersion.Expires.ToString('yyyy-MM-dd HH:mm'))"

# 查看某个 Secret 的所有历史版本
$allVersions = Get-AzKeyVaultSecret -VaultName $vaultName -Name "App-Api-Key" -IncludeVersions

Write-Host "`n--- App-Api-Key 版本历史 ---"
foreach ($ver in $allVersions) {
$status = if ($ver.Expires -and $ver.Expires -lt (Get-Date)) { "已过期" } else { "有效" }
Write-Host ("版本: {0} | 状态: {1} | 创建: {2}" -f $ver.Version.Substring(0, 8), $status, $ver.Created.ToString("yyyy-MM-dd HH:mm"))
}

每次对同一个 Secret 名称调用 Set-AzKeyVaultSecret,都会生成一个新版本。旧版本不会被自动删除,你可以随时回滚到之前的版本。这在密钥轮换出现问题时非常有用。

1
2
3
4
5
Secret 已更新,新版本过期时间: 2026-01-21 08:30

--- App-Api-Key 版本历史 ---
版本: 1a2b3c4d | 状态: 有效 | 创建: 2025-10-23 08:16
版本: 5e6f7g8h | 状态: 有效 | 创建: 2025-10-23 08:30

注意事项

  1. 启用软删除和清除保护:创建 Key Vault 时务必启用 SoftDeletePurgeProtection。软删除允许在保留期内(默认 90 天)恢复误删的 Secret,清除保护则防止恶意永久删除。这两项是生产环境的最低安全要求。

  2. 避免在日志中泄露明文:Key Vault 返回的 Secret 值是 SecureString 类型,在脚本中读取后切勿通过 Write-Host 或日志输出明文。如果确实需要调试,可以在开发环境使用 Marshal 转换,但在生产脚本中应删除此类代码。

  3. 使用托管标识代替硬编码凭据:在 Azure VM、App Service 或 AKS 上运行的应用,应使用系统分配或用户分配的托管标识(Managed Identity)访问 Key Vault,避免在代码或环境变量中存储服务主体密码。

  4. 遵循最小权限原则:为每个用户和服务主体仅授予其所需的最小权限集合。只读应用只需 GetList,只有密钥管理员才需要 SetDelete 权限。定期审计访问策略,移除不再需要的权限。

  5. 定期轮换密钥:为所有 Secret 设置合理的过期时间(如 30 天到 90 天),并建立自动轮换流程。Key Vault 支持通过 Event Grid 事件触发 Azure Function 来实现密钥的自动轮换,减少人工介入。

  6. 网络隔离与防火墙:对于高安全要求的场景,可以配置 Key Vault 的网络防火墙规则,仅允许特定虚拟网络的流量访问。结合 Private Endpoint 可以确保密钥数据不会经过公共互联网。