I am creating a bicep files that deploys a key vault and a few storage accounts. But these resources are in different module files. I can seem to reference the key vault when I am trying to add the storage account connection string to the key vault.
main.bicep
module resourceKeyVaultModule './modules/keyvault.bicep' = {
name: 'resourceKeyVaultModuleDeployment'
params: {
application: application
location: location
environment: environment
severity: severity
}
scope: resourceGroup
}
module resourceStorageAccountModule './modules/storage.bicep' = {
name: 'resourceStorageAccountModuleDeployment'
params: {
application: application
location: location
environment: environment
severity: severity
keyVault: resourceKeyVaultModule.outputs.name
}
scope: resourceGroup
}
keyvault.bicep
// == Key Vault
resource keyVault 'Microsoft.KeyVault/vaults#2022-07-01' = {
name: nameKeyVault
location: location
tags: {
location: location
environment: environment
severity: severity
}
properties: {
accessPolicies: [
{
objectId: ''
permissions: {
certificates: [
'all'
]
keys: [
'all'
]
secrets: [
'all'
]
storage: [
'all'
]
}
tenantId: ''
}
]
sku: {
family: 'A'
name: 'standard'
}
tenantId: ''
}
}
output name string = keyVault.name
storage.bicep
param keyVault string
// == Storage Account
resource storageAccount 'Microsoft.Storage/storageAccounts#2022-05-01' = {
name: nameStorageAccount
location: location
tags: {
location: location
environment: environment
severity: severity
}
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
minimumTlsVersion: 'TLS1_2'
}
}
resource secretConnectionString 'Microsoft.KeyVault/vaults/secrets#2022-07-01' = {
name: 'connectionString-storageAccount'
dependsOn: [keyVault]
tags: {
location: location
environment: environment
severity: severity
}
properties: {
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${listKeys(storageAccount.id, storageAccount.apiVersion).keys[0].value};EndpointSuffix=${az.environment().suffixes.storage}'
}
}
dependsOn: [keyVault]
Error: The enclosing array expected an item of type "module[] | (resource | module) | resource[]", but the provided item was of type "string".bicep(BCP034)
I think you are looking for the existing keyword.
To reference an existing resource that isn't deployed in your current Bicep file, declare the resource with the existing keyword. Use the existing keyword when you're deploying a resource that needs to get a value from an existing resource. You access the existing resource's properties through its symbolic name.
The resource isn't redeployed when referenced with the existing keyword.
Source: Existing resources in Bicep
Related
I'm trying to create a resource group and add a key vault to it.
However, I'm not able to set the new resource group as a target resource group for the key vault.
How can I have the key vault assigned to the newly created resource group without creating a second Bicep module for it?
var loc = 'westus'
// outputs the newly created resource group
module rgCreate 'test.rg.bicep' = {
scope: subscription()
name: 'rgCreate'
params: {
rgLocation: loc
}
}
resource keyVault 'Microsoft.KeyVault/vaults#2021-10-01' = {
name: 'Test'
location: loc
properties: {
enabledForTemplateDeployment: true
sku: {
family: 'A'
name: 'standard'
}
tenantId: tenant().tenantId
}
}
This is the workflow I'm aiming at:
First, if the resource group does not exist, you can't have targetScope = 'resourceGroup' in the main.bicep file. The command az deployment group create will fail:
{"code": "ResourceGroupNotFound", "message": "Resource group '' could not be found."}
You could always trigger the deployment form another resource that already exists (Not sure if it s a good idea tho).
An approach could be to have you main.bicep invoking two modules: one for resource group creation, one for resource creation:
// =========== rg.bicep ===========
// Setting target scope
targetScope = 'subscription'
param name string
param location string
// Creating resource group
resource rg 'Microsoft.Resources/resourceGroups#2021-01-01' = {
name: name
location: location
}
// =========== resources.bicep ===========
param location string = resourceGroup().location
param keyVaultName string
...
//Deploying key vault
resource keyVault 'Microsoft.KeyVault/vaults#2021-10-01' = {
name: keyVaultName
location: location
properties: {
enabledForTemplateDeployment: true
sku: {
family: 'A'
name: 'standard'
}
tenantId: tenant().tenantId
}
}
// Deploying other resources
...
// =========== main.bicep ===========
// Setting target scope
targetScope = 'subscription'
// Parameters
param rgName string = 'test-rg'
param rgLocation string = 'westus'
param keyVaultName string
...
// Creating resource group
module rgModule 'rg.bicep' = {
scope: subscription()
name: '${rgName}-create'
params:{
name: rgName
location: rgLocation
}
}
// Deploying resources in the newly created resource
module resources 'resources.bicep' = {
name: '${rgName}-resources-deployment'
scope: resourceGroup(rgName)
dependsOn: [ rgModule ]
params: {
location: rgLocation
keyVaultName: keyVaultName
...
}
}
To be honest, you could just run az group create command before deploying your template it will make things simpler.
I'm trying to deploy a .NET Core 3.1 Azure App Service on Linux using a Bicep template from Azure CLI. The app service and corresponding app service plan are deployed correctly but the app service stack settings are empty on the Azure portal and I have to set these manually. I tried setting the metadata property on the 'Microsoft.Web/sites' resource and also on the 'Microsoft.Web/sites/config' resource, but the result was the same.
This is my app service plan:
resource appServicePlan 'Microsoft.Web/serverfarms#2021-02-01' = {
name: 'MyAppService'
location: resourceGroup().location
properties: {
reserved: true
}
sku: {
name: 'P1v2'
}
kind: 'linux'
}
Here is my first attempt to set the stack using 'Microsoft.Web/sites' as suggested here:
https://github.com/Azure/bicep/issues/3314
resource appService 'Microsoft.Web/sites#2021-02-01' = {
name: 'MyApp'
location: resourceGroup().location
identity: {
type: 'SystemAssigned'
}
kind: 'app'
properties: {
enabled: true
serverFarmId: appServicePlan.id
siteConfig: {
linuxFxVersion: 'dotnet|3.1'
appCommandLine: 'dotnet MyApp.dll'
metadata: [
{
name: 'CURRENT_STACK'
value: 'dotnetcore'
}
]
}
}
}
Here is my second attempt to set the stack using 'Microsoft.Web/sites/config' as suggested here:
Bicep - How to config Runtime Stack to Azure App Service (Bicep version 0.4)
resource appService 'Microsoft.Web/sites#2021-02-01' = {
name: 'MyApp'
location: resourceGroup().location
identity: {
type: 'SystemAssigned'
}
kind: 'app'
properties: {
enabled: true
serverFarmId: appServicePlan.id
siteConfig: {
linuxFxVersion: 'dotnet|3.1'
appCommandLine: 'dotnet MyApp.dll'
}
}
resource webConfig 'config' = {
name: 'web'
properties: {
metadata: [
{
name: 'CURRENT_STACK'
value: 'dotnetcore'
}
]
}
}
}
The result is the same. The deployment is completed with the following warning:
Warning BCP037: The property "metadata" is not allowed on objects of
type "SiteConfig". Permissible properties include
"acrUseManagedIdentityCreds", "acrUserManagedIdentityID", "alwaysOn",
"apiDefinition", "apiManagementConfig", "autoHealEnabled",
"autoHealRules", "autoSwapSlotName", "azureStorageAccounts",
"connectionStrings", "cors", "defaultDocuments",
"detailedErrorLoggingEnabled", "documentRoot", "experiments",
"ftpsState", "functionAppScaleLimit",
"functionsRuntimeScaleMonitoringEnabled", "handlerMappings",
"healthCheckPath", "http20Enabled", "httpLoggingEnabled",
"ipSecurityRestrictions", "javaContainer", "javaContainerVersion",
"javaVersion", "keyVaultReferenceIdentity", "limits", "loadBalancing",
"localMySqlEnabled", "logsDirectorySizeLimit", "managedPipelineMode",
"managedServiceIdentityId", "minimumElasticInstanceCount",
"minTlsVersion", "netFrameworkVersion", "nodeVersion",
"numberOfWorkers", "phpVersion", "powerShellVersion",
"preWarmedInstanceCount", "publicNetworkAccess", "publishingUsername",
"push", "pythonVersion", "remoteDebuggingEnabled",
"remoteDebuggingVersion", "requestTracingEnabled",
"requestTracingExpirationTime", "scmIpSecurityRestrictions",
"scmIpSecurityRestrictionsUseMain", "scmMinTlsVersion", "scmType",
"tracingOptions", "use32BitWorkerProcess", "virtualApplications",
"vnetName", "vnetPrivatePortsCount", "vnetRouteAllEnabled",
"websiteTimeZone", "webSocketsEnabled", "windowsFxVersion",
"xManagedServiceIdentityId". If this is an inaccuracy in the
documentation, please report it to the Bicep Team.
[https://aka.ms/bicep-type-issues]
The resources are deployed, but the app service stack setting is blank and I have to set it manually to make it work.
I know that in the ARM template this is set on the CURRENT_STACK property of the Microsoft.Web/sites/config metadata (as suggested here https://cloudstep.io/2020/11/18/undocumented-arm-oddities-net-core-app-services/). However, this doesn't seem to be supported (yet) in Bicep. If anyone has found a working solution, please post it here.
Thanks.
The Metadata parameter is not available anymore in the SiteConfig. The stack setting can be mentioned LinuxFxVersion.
So, solution will be Instead of using dotnet|3.1 , You should use DOTNETCORE|3.1.The over all code will be as below:
resource appServicePlan 'Microsoft.Web/serverfarms#2021-02-01' = {
name: 'MyAppService'
location: resourceGroup().location
properties: {
reserved: true
}
sku: {
name: 'P1v2'
}
kind: 'linux'
}
resource appService 'Microsoft.Web/sites#2021-02-01' = {
name: 'anumantestapp'
location: resourceGroup().location
identity: {
type: 'SystemAssigned'
}
kind: 'app'
properties: {
enabled: true
serverFarmId: appServicePlan.id
siteConfig: {
linuxFxVersion: 'DOTNETCORE|3.1'
appCommandLine: 'dotnet MyApp.dll'
}
}
}
Ouptut:
I have a bicep template that creates 2 webApps and a KeyVault. Each WebApp is created with a managedID which I need to add to Keyvault so the webapp can pull in the secrets.
But when creating 2 webapps, I can't work out how to assign both ManagedIDs to KeyVault.
The bicep template is using modules
name: 'ciKeyVault'
params: {
keyVaultName: keyVaultName
aclBypass: keyVaultSettings.aclBypass
aclDefaultAction: keyVaultSettings.aclDefaultAction
enabledForDeployment: keyVaultSettings.enabledForDeployment
enabledForDiskEncryption: keyVaultSettings.enabledForDiskEncryption
enabledForTemplateDeployment: keyVaultSettings.enabledForTemplateDeployment
keyPermissions: keyVaultSettings.keyPermissions
keyVaultSettings: keyVaultSettings
secretsPermissions: keyVaultSettings.secretsPermissions
skuFamily: keyVaultSettings.skuFamily
skuName: keyVaultSettings.skuName
tenantId: subscription().tenantId
objectId: 'b71e61c4-7cff-41d0-8370-a7d9c01dde84'
}
}
and the objectId needs to be retrieved from the AppService Deployment. using this module:
module AppService '../../../Modules/Azure.App.Service.template.bicep' = [for i in range(0, length(webAppSettings.webApps)): {
name: webAppSettings.webApps[i].Name
dependsOn: [
frontEndAppServicePlan
]
params: {
webAppName: webAppSettings.webApps[i].appServiceType == 'functionApp' ? toLower('fnc-${webAppSettings.webApps[i].name}-${resourceGroupNameSuffix}') : toLower('web-${webAppSettings.webApps[i].name}-${resourceGroupNameSuffix}')
hostingPlan: frontEndAppServicePlan.outputs.hostingPlanId
virtualNetworkResourceGroup: virtualNetworkResourceGroup
environmentName:environmentName
webAppSettings:webAppSettings
appServiceType: webAppSettings.webApps[i].appServiceType
LinuxFX:webAppSettings.webApps[i].LinuxFX
appSettings:webAppSettings.webapps[i].appSettings
}
}]
Its fine when its a single appService cause I can reference the ID using output usid string = AppServices.identity.principalId
but when I have 2 appServices I can't work out how to pass in both IDs
Any ideas?
Cheers
Let's say you have a module Azure.App.Service.template.bicep that looks like that:
param webAppName string
...
// Create the web app
resource webApp 'Microsoft.Web/sites#2020-09-01' = {
name: webAppName
location: resourceGroup().location
identity: {
type: 'SystemAssigned'
}
...
}
output usid string = webApp.identity.principalId
In the parent template you can create an array of module to create your webapps (the same way you are doing it) and then create an access policies resource to grant access to key vault to all the web apps.
...
// Create the app services
module AppServices '../../../Modules/Azure.App.Service.template.bicep' = [for webApp in webAppSettings.webApps: {
name: webApp.Name
params: {
webAppName: webApp.Name
...
}
}]
// Granting the app services access ot key vault
resource appServicesKeyVaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies#2019-09-01' = {
name: '${keyVaultName}/add'
properties: {
accessPolicies: [for i in range(0, length(webAppSettings.webApps)): {
tenantId: subscription().tenantId
objectId: AppServices[i].outputs.usid
permissions: {
secrets: keyVaultSettings.secretsPermissions
keys: keyVaultSettings.keyPermissions
}
}]
}
}
I'm using Azure Bicep to create a virtualNetwork with a single subnet and then use that as the input for creating an aks cluster with : vnetSubnetID: virtualNetwork.properties.subnets[0].id
The first time I run the command, it creates the virtual network and cluster just fine, but the second time I run the command it gives this error :
{"error":{"code":"InvalidTemplateDeployment","message":"The template
deployment 'cluster' is not valid according to the validation
procedure. The tracking id is '[REDACTED_JUST_IN_CASE]'. See inner errors for
details.","details":[{"code":"PropertyChangeNotAllowed","message":"Provisioning
of resource(s) for container service playground-cluster0 in resource
group showcase-kevinplayground2 failed. Message: {\n "code":
"PropertyChangeNotAllowed",\n "message": "Changing property
'agentPoolProfile.vnetSubnetID' is not allowed.",\n "target":
"agentPoolProfile.vnetSubnetID"\n }. Details: "}]}}
I double checked and there is just the one subnet inside the virtualNetwork created by the deployment (no other magically appeared or anything).
I repeated the experiment on a second resource group and the same thing happened, so it's reproducible.
Here is the full bicep file (just call az deployment group create --resource-group showcase-kevinplayground2 -f cluster.bicep in the resource group of your choice)
targetScope = 'resourceGroup'
resource virtualNetwork 'Microsoft.Network/virtualNetworks#2021-02-01' = {
name: 'aksVirtualNetwork'
location: resourceGroup().location
properties:{
addressSpace:{
addressPrefixes:[
'10.10.0.0/16'
]
}
subnets:[
{
name: 'aks'
properties:{
addressPrefix: '10.10.5.0/24'
}
}
]
}
}
resource aksManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities#2018-11-30' = {
name: 'playgroundIdentity'
location: resourceGroup().location
}
resource aks 'Microsoft.ContainerService/managedClusters#2021-02-01' = {
name: 'playground-cluster0'
location: resourceGroup().location
identity: {
type:'UserAssigned'
userAssignedIdentities: {
'${aksManagedIdentity.id}': {}
}
}
sku: {
name: 'Basic'
tier: 'Free'
}
properties: {
kubernetesVersion: '1.21.2'
dnsPrefix: 'playground'
enableRBAC: true
networkProfile: {
networkPlugin: 'azure'
networkPolicy: 'calico'
}
aadProfile: {
managed: true
enableAzureRBAC: true
}
autoUpgradeProfile: {}
apiServerAccessProfile: {
enablePrivateCluster: false
}
agentPoolProfiles: [
{
name: 'aksnodes'
count: 1
vmSize: 'Standard_B2s'
osDiskSizeGB: 30
osDiskType: 'Managed'
vnetSubnetID: virtualNetwork.properties.subnets[0].id
osType: 'Linux'
maxCount: 1
minCount: 1
enableAutoScaling: true
type: 'VirtualMachineScaleSets'
mode: 'System'
orchestratorVersion: null
}
]
}
}
Looking at this reported github issue, you need to use the resourceId function.
In your case, something like that should work:
vnetSubnetID: resourceId('Microsoft.Network/virtualNetworks/subnets', 'aksVirtualNetwork', 'aks')
I am currently trying to deploy out a resource group using azure bicep, however, I am running into an issue using key vault for my azure app service. I would like to know if I am actually doing this the correct way. I have a main bicep file that is along the lines of:
// 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
]
}
My question comes with the implementation of the site.bicep, I started off by passing the secret uri from exported variables and creating the web app last as app insights, sql, etc... all need to be setup and in keyvault before we use their exported secret uri to construct a config. I had something along the lines of:
site.bicep (before):
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'
}
}
The only problem with this implementation is that the key vault MUST be constructed before the website because sql, ai, and the other services will store their values inside of the key vault for the web app to consume by their respective uris. The issue with this is that the KeyVault rightfully so has no idea which azure service to let access it's keys.
My question is the solution of constructing the web app before the key vault the only way to beat this problem? I am using managed identities on the web app and would like to continue doing so if possible. My final solution ended up somewhat like this:
site.bicep (final)
// 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
And the KeyVault which will take a dependsOn webSite
keyVault.bicep(final):
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'
]
}
}
]
}
}
Just treat your accessPolicies as separate resource and add them when both Key Vault and App Service are created. Same applies for Config section and Connection Strings. Check documentation here.
In ARM templates you can achieve same effect using nested templates. In Bicep it is kind the same, but you declare them as separate resource that usually contains parent name (e.g. name: '${kv.name}/add', name: '${webSite.name}/connectionstrings')
Sample
Step 1: Create an App Service without config section
resource webSite 'Microsoft.Web/sites#2020-12-01' = {
name: webSiteName
location: location
properties: {
serverFarmId: hostingPlan.id
siteConfig:{
netFrameworkVersion: 'v5.0'
}
}
identity: {
type:'SystemAssigned'
}
}
Step 2: Create Key Vault without access policies
resource kv 'Microsoft.KeyVault/vaults#2019-09-01' = {
name: keyVaultName
location: location
properties:{
sku:{
family: 'A'
name: 'standard'
}
tenantId: tenantId
enabledForTemplateDeployment: true
accessPolicies:[
]
}
}
Step 3: Create new access policy and reference 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'
]
}
}
]
}
}
Step 4: Update Webb app config section
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'
}
}
}
One solution could be to use User Assigend Identity instead of System Assigned. Then you would deploy the following:
Deploy a user assigend identity
Key Vault and assign permissions for user assigned identity
Deploy web app with user assigned identity and read / write secrets
User assigned is independent of the resources and so you avoid your chicken and egg problem.
More:
https://learn.microsoft.com/en-us/azure/templates/microsoft.managedidentity/userassignedidentities?tabs=bicep
https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview
You should split up three parts of your deployment into separate resources:
Deploy the Key Vault - without any access policies!
Deploy the App Service - with the SystemAssigned Identity, but without the app settings
Deploy the Key Vault Access Policy for the MSI
Deploy the App Settings