云端部署

Azure:用 Bicep 给新项目建一套安全基线:存储账户禁止匿名访问和账户密钥、只允许 TLS 1.2

用 Bicep 给新项目建一套安全基线:存储账户禁止匿名访问和账户密钥、只允许 TLS 1.2,Blob 误删保留 14 天;Key Vault 用 RBAC 授权、开启清除保护,只给指定身份“读取机密”的权限。

BINBHO · 图文教程

用 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 https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/install
Bicep linter https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/linter
用 Azure CLI 部署 Bicep https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deploy-cli
Key Vault RBAC 指南 https://learn.microsoft.com/en-us/azure/key-vault/general/rbac-guide
Azure 内置角色 https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
禁止共享密钥授权 https://learn.microsoft.com/en-us/azure/storage/common/shared-key-authorization-prevent

安全基线最好在第一天就写进模板。以后每个新项目都从这份 Bicep 开始,就不会出现“先公开着用,回头再关”的情况。