用 Bicep 给新项目建一套安全基线:存储账户禁止匿名访问和账户密钥、只允许 TLS 1.2,Blob 误删保留 14 天;Key Vault 用 RBAC 授权、开启清除保护,只给指定身份“读取机密”的权限。
先看清成品
本机用 Bicep CLI 0.48.1 执行了
bicep lint、bicep build和bicep build-params。第一版 lint 报了 3 条 API 版本过旧的警告(见调试示例),更新版本后 lint 无输出,编译出 4 个 ARM 资源。没有执行az deployment group create,实际部署结果以你的订阅为准。
注意:Azure 按用量计费,价格以官方价格页和账单为准,本文不列金额。中国大陆的 Azure 由世纪互联运营(azure.cn),账号、门户和终结点(如
*.chinacloudapi.cn)与全球版不同,本文按全球版编写。不要共享账号或把密钥提交到仓库。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
| 存储账户 | 私有、禁用共享密钥、TLS 1.2 |
| 软删除 | Blob 和容器都保留 14 天 |
| Key Vault | RBAC、软删除 30 天、清除保护 |
| 角色分配 | Key Vault Secrets User |
| lint 规则 | 过旧 API 警告、禁止硬编码位置 |
需要准备哪些环境?
| 项目 | 要求 | 说明 |
|---|---|---|
| Azure 订阅 | 资源组的 Owner 或 User Access Administrator | 角色分配需要权限 |
| Azure CLI | 已 az login |
az bicep install 可安装 Bicep |
| Bicep CLI | 本文用 0.48.1 |
文件结构
22-azure-B-src/
├── main.bicep
├── main.bicepparam
└── bicepconfig.json
第 1 步:模板
main.bicep:
// 项目基础设施:私有存储账户 + 使用 RBAC 的 Key Vault + 日志工作区
@description('资源名前缀,3-11 位小写字母或数字')
@minLength(3)
@maxLength(11)
param prefix string
param location string = resourceGroup().location
@description('需要读取机密的用户或服务主体的对象 ID')
param readerObjectId string
var suffix = uniqueString(resourceGroup().id)
resource storage 'Microsoft.Storage/storageAccounts@2026-04-01' = {
name: '${prefix}st${take(suffix, 8)}'
location: location
sku: { name: 'Standard_LRS' }
kind: 'StorageV2'
properties: {
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
allowSharedKeyAccess: false // 强制用 Entra ID 身份访问
supportsHttpsTrafficOnly: true
}
}
resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2026-04-01' = {
parent: storage
name: 'default'
properties: {
deleteRetentionPolicy: { enabled: true, days: 14 }
containerDeleteRetentionPolicy: { enabled: true, days: 14 }
}
}
resource vault 'Microsoft.KeyVault/vaults@2026-02-01' = {
name: '${prefix}-kv-${take(suffix, 6)}'
location: location
properties: {
tenantId: subscription().tenantId
sku: { family: 'A', name: 'standard' }
enableRbacAuthorization: true
enableSoftDelete: true
softDeleteRetentionInDays: 30
enablePurgeProtection: true
}
}
// 内置角色 Key Vault Secrets User:只能读机密
var secretsUserRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')
resource readerAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(vault.id, readerObjectId, secretsUserRole)
scope: vault
properties: {
roleDefinitionId: secretsUserRole
principalId: readerObjectId
}
}
output storageName string = storage.name
output vaultUri string = vault.properties.vaultUri
要点:
allowSharedKeyAccess: false后,连接字符串和账户密钥 SAS 都不能用,只能用 Entra ID 身份(本工具 C 篇的用户委托 SAS 正是为此准备)。enablePurgeProtection: true一旦开启不能关闭,软删除期间连管理员也不能彻底删除。测试环境想随时删干净,可以先不开。uniqueString(resourceGroup().id)让名字在同一资源组内稳定、在全局大概率唯一。
第 2 步:参数文件和 lint 配置
main.bicepparam:
using 'main.bicep'
param prefix = 'binbho'
param readerObjectId = '00000000-0000-0000-0000-000000000000'
readerObjectId 换成真实对象 ID:
az ad signed-in-user show --query id -o tsv
bicepconfig.json:
{
"analyzers": {
"core": {
"enabled": true,
"rules": {
"use-recent-api-versions": { "level": "warning" },
"no-hardcoded-location": { "level": "error" },
"secure-secrets-in-params": { "level": "error" }
}
}
}
}
第 3 步:本地检查
bicep lint main.bicep
bicep build main.bicep --outfile main.json
bicep build-params main.bicepparam
检查结果:lint 无输出;main.json 中有 storageAccounts、blobServices、vaults、roleAssignments 四个资源。
第 4 步:预览并部署(本文未执行)
az group create -n rg-baseline -l eastasia
az deployment group what-if -g rg-baseline --parameters main.bicepparam
az deployment group create -g rg-baseline --parameters main.bicepparam
先看 what-if 列出的变更,再执行 create。
第 5 步:验证
az storage account show -g rg-baseline -n <storageName> --query "{public:allowBlobPublicAccess, sharedKey:allowSharedKeyAccess, tls:minimumTlsVersion}"
az keyvault secret set --vault-name <vault 名> -n demo --value test
预期结果:存储账户显示 false / false / TLS1_2。写机密会因为只有 Secrets User(只读)角色而被拒绝,需要写机密的人另外分配 Key Vault Secrets Officer。
调试示例
第一版用的是 2023-05-01 / 2023-07-01 API 版本,bicep lint 实际输出(节选):
main.bicep(14,53) : Warning use-recent-api-versions: Use more recent API version for 'Microsoft.Storage/storageAccounts'. '2023-05-01' is 1269 days old, should be no more than 730 days old, or the most recent. Acceptable versions: 2026-06-01, 2026-04-01, 2025-08-01, ...
改为 Microsoft.Storage/...@2026-04-01 和 Microsoft.KeyVault/vaults@2026-02-01 后 lint 通过。这条规则默认不开启,是在 bicepconfig.json 里打开的。
出错了怎么办?
| 现象 | 原因 | 解决 |
|---|---|---|
StorageAccountAlreadyTaken |
存储账户名全局唯一 | 换 prefix |
角色分配 AuthorizationFailed |
你没有分配角色的权限 | 找订阅 Owner 授权 |
VaultAlreadyExists / 名字被占用 |
同名 Key Vault 在软删除状态 | az keyvault list-deleted 查看;开了清除保护只能等保留期结束或换名 |
| 应用读不到机密 | 角色分配传播有延迟,或对象 ID 不对 | 等几分钟,核对对象 ID |
| 旧程序连不上存储 | 禁用了共享密钥 | 改用托管身份 / Entra ID |
模板规则
- 新存储账户默认禁用匿名访问和共享密钥。
- Key Vault 一律 RBAC,按最小角色分配。
- 部署前 lint + what-if。
项目资源
安全基线最好在第一天就写进模板。以后每个新项目都从这份 Bicep 开始,就不会出现“先公开着用,回头再关”的情况。