App Service Managed Identity 和 Key Vault 的正确方法
App Service Managed Identity and Key Vault the right way
我目前正在尝试使用 azure bicep 部署一个资源组,但是,我 运行 遇到了使用 key vault 用于我的 azure 应用服务的问题。我想知道我是否真的以正确的方式这样做。我有一个主要的二头肌文件,内容如下:
// params removed for brevity...
targetScope = 'subscription'
resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: 'rg-${appName}-${region}'
location: 'centralus'
}
module appServicePlan 'appplan.bicep' = {
params: {
sku: appServicePlanSku
appName: appName
region: region
}
scope: rg
name: 'AppServicePlanDeploy'
}
module keyVault 'keyvault.bicep' = {
params: {
keyVaultName: keyVaultName
sqlPassword: sqlServerPassword
webSiteManagedId: webSite.outputs.webAppPrincipal
}
scope: rg
name: 'KeyVaultDeploy'
dependsOn: [
webSite
]
}
module ai 'ai.bicep' = {
scope: rg
name: 'ApplicationInsightsDeploy'
params: {
name: appName
region: region
keyVaultName: keyVault.outputs.keyVaultName
}
dependsOn: [
keyVault
]
}
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: keyVaultName
scope: rg
}
module sql 'sqlserver.bicep' = {
scope: rg
name: 'SQLServerDeploy'
params: {
appName: appName
region: region
sqlPassword: kv.getSecret('sqlPassword')
sqlCapacitity: sqlCapacitity
sqlSku: sqlSku
sqlTier: sqlTier
}
dependsOn: [
keyVault
]
}
module webSite 'site.bicep' = {
params: {
appName: appName
region: region
keyVaultName: keyVaultName
serverFarmId: appServicePlan.outputs.appServicePlanId
}
scope: rg
name: 'AppServiceDeploy'
dependsOn: [
appServicePlan
]
}
我的问题来自 site.bicep 的实施,我首先从导出变量传递秘密 uri,最后创建网络应用程序作为应用程序洞察,sql,等等...在我们使用它们导出的秘密 uri 构建配置之前,所有这些都需要设置并保存在 keyvault 中。我有一些类似的东西:
site.bicep(之前):
properties: {
serverFarmId: serverFarmId
keyVaultReferenceIdentity: userAssignedId
siteConfig: {
appSettings: [
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: '@Microsoft.KeyVault(SecretUri=${appInsightsConnectionString})'
}
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: '@Microsoft.KeyVault(SecretUri=${appInsightsKey})'
}
]
netFrameworkVersion: 'v5.0'
}
}
此实现的唯一问题是密钥保管库必须在网站之前构建,因为 sql、ai 和其他服务会将它们的值存储在密钥保管库内以供 Web 应用程序使用通过他们各自的 uri。这个问题是 KeyVault 理所当然地不知道允许哪个 azure 服务访问它的密钥。
我的问题是在密钥保管库之前构建 Web 应用程序的解决方案是解决此问题的唯一方法吗?我在 Web 应用程序上使用托管身份,如果可能的话,我想继续这样做。我的最终解决方案有点像这样:
site.bicep(最终)
// params removed for brevity...
resource webApplication 'Microsoft.Web/sites@2020-12-01' = {
name: 'app-${appName}-${region}'
location: resourceGroup().location
tags: {
'hidden-related:${resourceGroup().id}/providers/Microsoft.Web/serverfarms/appServicePlan': 'Resource'
}
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: serverFarmId
siteConfig: {
appSettings: [
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: '@Microsoft.KeyVault(SecretUri=${keyVaultName}.vault.azure.net/secrets/aiConnectionString)'
}
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: '@Microsoft.KeyVault(SecretUri=${keyVaultName}.vault.azure.net/secrets/aiInstrumentationKey)'
}
{
name: 'AngularConfig:ApplicationInsightsKey'
value: '@Microsoft.KeyVault(SecretUri=${keyVaultName}.vault.azure.net/secrets/aiInstrumentationKey)'
}
]
netFrameworkVersion: 'v5.0'
}
}
}
output webAppPrincipal string = webApplication.identity.principalId
以及需要依赖网站的 KeyVault
keyVault.bicep(最终):
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' = {
name: keyVaultName
location: resourceGroup().location
properties: {
enabledForDeployment: true
enabledForTemplateDeployment: true
enabledForDiskEncryption: true
enableRbacAuthorization: true
tenantId: subscription().tenantId
sku: {
name: 'standard'
family: 'A'
}
accessPolicies: [
{
tenantId: subscription().tenantId
objectId: webSiteManagedId
permissions: {
keys: [
'get'
]
secrets: [
'list'
'get'
]
}
}
]
}
}
一种解决方案是使用用户分配身份而不是系统分配。然后您将部署以下内容:
- 部署用户分配身份
- Key Vault 并为用户分配的身份分配权限
- 使用用户分配的身份和读/写机密部署网络应用程序
分配的用户与资源无关,因此您可以避免先有鸡还是先有蛋的问题。
更多:
您应该将部署的三个部分拆分为单独的资源:
- 部署 Key Vault - 没有任何访问策略!
- 部署应用服务 - 使用 SystemAssigned Identity,但没有应用设置
- 为 MSI
部署 Key Vault Access Policy
- 部署 App Settings
只需将您的 accessPolicies
视为单独的资源,并在创建 Key Vault 和 App Service 时添加它们。这同样适用于配置部分和连接字符串。检查文档 here.
在 ARM 模板中,您可以使用嵌套模板实现相同的效果。在 Bicep 中它是相同的,但是您将它们声明为通常包含父名称的单独资源(例如 name: '${kv.name}/add'
、name: '${webSite.name}/connectionstrings'
)
样本
第 1 步:创建不带配置部分的应用服务
resource webSite 'Microsoft.Web/sites@2020-12-01' = {
name: webSiteName
location: location
properties: {
serverFarmId: hostingPlan.id
siteConfig:{
netFrameworkVersion: 'v5.0'
}
}
identity: {
type:'SystemAssigned'
}
}
步骤 2:创建没有访问策略的 Key Vault
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' = {
name: keyVaultName
location: location
properties:{
sku:{
family: 'A'
name: 'standard'
}
tenantId: tenantId
enabledForTemplateDeployment: true
accessPolicies:[
]
}
}
第 3 步:创建新的访问策略并引用 Web Apps Managed Identity
resource keyVaultAccessPolicy 'Microsoft.KeyVault/vaults/accessPolicies@2021-06-01-preview' = {
name: '${kv.name}/add'
properties: {
accessPolicies: [
{
tenantId: tenantId
objectId: webSite.identity.principalId
permissions: {
keys: [
'get'
]
secrets: [
'list'
'get'
]
}
}
]
}
}
第 4 步:更新 Webb 应用配置部分
resource webSiteConnectionStrings 'Microsoft.Web/sites/config@2020-06-01' = {
name: '${webSite.name}/connectionstrings'
properties: {
DefaultConnection: {
value: '@Microsoft.KeyVault(SecretUri=${keyVaultName}.vault.azure.net/secrets/aiConnectionString)'
type: 'SQLAzure'
}
}
}
我目前正在尝试使用 azure bicep 部署一个资源组,但是,我 运行 遇到了使用 key vault 用于我的 azure 应用服务的问题。我想知道我是否真的以正确的方式这样做。我有一个主要的二头肌文件,内容如下:
// params removed for brevity...
targetScope = 'subscription'
resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: 'rg-${appName}-${region}'
location: 'centralus'
}
module appServicePlan 'appplan.bicep' = {
params: {
sku: appServicePlanSku
appName: appName
region: region
}
scope: rg
name: 'AppServicePlanDeploy'
}
module keyVault 'keyvault.bicep' = {
params: {
keyVaultName: keyVaultName
sqlPassword: sqlServerPassword
webSiteManagedId: webSite.outputs.webAppPrincipal
}
scope: rg
name: 'KeyVaultDeploy'
dependsOn: [
webSite
]
}
module ai 'ai.bicep' = {
scope: rg
name: 'ApplicationInsightsDeploy'
params: {
name: appName
region: region
keyVaultName: keyVault.outputs.keyVaultName
}
dependsOn: [
keyVault
]
}
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: keyVaultName
scope: rg
}
module sql 'sqlserver.bicep' = {
scope: rg
name: 'SQLServerDeploy'
params: {
appName: appName
region: region
sqlPassword: kv.getSecret('sqlPassword')
sqlCapacitity: sqlCapacitity
sqlSku: sqlSku
sqlTier: sqlTier
}
dependsOn: [
keyVault
]
}
module webSite 'site.bicep' = {
params: {
appName: appName
region: region
keyVaultName: keyVaultName
serverFarmId: appServicePlan.outputs.appServicePlanId
}
scope: rg
name: 'AppServiceDeploy'
dependsOn: [
appServicePlan
]
}
我的问题来自 site.bicep 的实施,我首先从导出变量传递秘密 uri,最后创建网络应用程序作为应用程序洞察,sql,等等...在我们使用它们导出的秘密 uri 构建配置之前,所有这些都需要设置并保存在 keyvault 中。我有一些类似的东西: site.bicep(之前):
properties: {
serverFarmId: serverFarmId
keyVaultReferenceIdentity: userAssignedId
siteConfig: {
appSettings: [
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: '@Microsoft.KeyVault(SecretUri=${appInsightsConnectionString})'
}
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: '@Microsoft.KeyVault(SecretUri=${appInsightsKey})'
}
]
netFrameworkVersion: 'v5.0'
}
}
此实现的唯一问题是密钥保管库必须在网站之前构建,因为 sql、ai 和其他服务会将它们的值存储在密钥保管库内以供 Web 应用程序使用通过他们各自的 uri。这个问题是 KeyVault 理所当然地不知道允许哪个 azure 服务访问它的密钥。
我的问题是在密钥保管库之前构建 Web 应用程序的解决方案是解决此问题的唯一方法吗?我在 Web 应用程序上使用托管身份,如果可能的话,我想继续这样做。我的最终解决方案有点像这样:
site.bicep(最终)
// params removed for brevity...
resource webApplication 'Microsoft.Web/sites@2020-12-01' = {
name: 'app-${appName}-${region}'
location: resourceGroup().location
tags: {
'hidden-related:${resourceGroup().id}/providers/Microsoft.Web/serverfarms/appServicePlan': 'Resource'
}
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: serverFarmId
siteConfig: {
appSettings: [
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: '@Microsoft.KeyVault(SecretUri=${keyVaultName}.vault.azure.net/secrets/aiConnectionString)'
}
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: '@Microsoft.KeyVault(SecretUri=${keyVaultName}.vault.azure.net/secrets/aiInstrumentationKey)'
}
{
name: 'AngularConfig:ApplicationInsightsKey'
value: '@Microsoft.KeyVault(SecretUri=${keyVaultName}.vault.azure.net/secrets/aiInstrumentationKey)'
}
]
netFrameworkVersion: 'v5.0'
}
}
}
output webAppPrincipal string = webApplication.identity.principalId
以及需要依赖网站的 KeyVault
keyVault.bicep(最终):
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' = {
name: keyVaultName
location: resourceGroup().location
properties: {
enabledForDeployment: true
enabledForTemplateDeployment: true
enabledForDiskEncryption: true
enableRbacAuthorization: true
tenantId: subscription().tenantId
sku: {
name: 'standard'
family: 'A'
}
accessPolicies: [
{
tenantId: subscription().tenantId
objectId: webSiteManagedId
permissions: {
keys: [
'get'
]
secrets: [
'list'
'get'
]
}
}
]
}
}
一种解决方案是使用用户分配身份而不是系统分配。然后您将部署以下内容:
- 部署用户分配身份
- Key Vault 并为用户分配的身份分配权限
- 使用用户分配的身份和读/写机密部署网络应用程序
分配的用户与资源无关,因此您可以避免先有鸡还是先有蛋的问题。
更多:
您应该将部署的三个部分拆分为单独的资源:
- 部署 Key Vault - 没有任何访问策略!
- 部署应用服务 - 使用 SystemAssigned Identity,但没有应用设置
- 为 MSI 部署 Key Vault Access Policy
- 部署 App Settings
只需将您的 accessPolicies
视为单独的资源,并在创建 Key Vault 和 App Service 时添加它们。这同样适用于配置部分和连接字符串。检查文档 here.
在 ARM 模板中,您可以使用嵌套模板实现相同的效果。在 Bicep 中它是相同的,但是您将它们声明为通常包含父名称的单独资源(例如 name: '${kv.name}/add'
、name: '${webSite.name}/connectionstrings'
)
样本
第 1 步:创建不带配置部分的应用服务
resource webSite 'Microsoft.Web/sites@2020-12-01' = {
name: webSiteName
location: location
properties: {
serverFarmId: hostingPlan.id
siteConfig:{
netFrameworkVersion: 'v5.0'
}
}
identity: {
type:'SystemAssigned'
}
}
步骤 2:创建没有访问策略的 Key Vault
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' = {
name: keyVaultName
location: location
properties:{
sku:{
family: 'A'
name: 'standard'
}
tenantId: tenantId
enabledForTemplateDeployment: true
accessPolicies:[
]
}
}
第 3 步:创建新的访问策略并引用 Web Apps Managed Identity
resource keyVaultAccessPolicy 'Microsoft.KeyVault/vaults/accessPolicies@2021-06-01-preview' = {
name: '${kv.name}/add'
properties: {
accessPolicies: [
{
tenantId: tenantId
objectId: webSite.identity.principalId
permissions: {
keys: [
'get'
]
secrets: [
'list'
'get'
]
}
}
]
}
}
第 4 步:更新 Webb 应用配置部分
resource webSiteConnectionStrings 'Microsoft.Web/sites/config@2020-06-01' = {
name: '${webSite.name}/connectionstrings'
properties: {
DefaultConnection: {
value: '@Microsoft.KeyVault(SecretUri=${keyVaultName}.vault.azure.net/secrets/aiConnectionString)'
type: 'SQLAzure'
}
}
}