Creating Azure KeyVault secret as a child resource in nested template - azure

I'm trying to create resource group, key vault and key vault secret using a single template json with subscription level scope. I'm able to create resource group and key vault without any issues. However, adding a key vault secret template as a child resource to key vault template with 'dependsOn' section generates errors like "Key vault secret doesn't depend on parent resource. Please add dependency explicitly using the 'dependsOn' syntax." Here is the template:
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {... parameters for key vault and key vault secret resources ...},
"variables": {
"rgName": "[concat('rg-', substring(uniqueString(subscription().id), 0, 4))]",
"keyvaultName": "[concat('keyvault-', substring(uniqueString(subscription().id), 0, 4))]"
},
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2021-04-01",
"location": "[parameters('location')]",
"name": "[variables('rgName')]"
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2021-04-01",
"name": "keyvaultDeployment",
"resourceGroup": "[variables('rgName')]",
"dependsOn": [
"[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.KeyVault/vaults",
"apiVersion": "2021-10-01",
"name": "[variables('keyvaultName')]",
"location": "[parameters('location')]",
"properties": {... key vault properties ...},
"resources": [
{
"type": "Microsoft.KeyVault/vaults/secrets",
"apiVersion": "2021-10-01",
"name": "[concat(variables('keyvaultName'), '/', parameters('keyvaultSecretName'))]",
"dependsOn": [
"[subscriptionResourceId('Microsoft.KeyVault/vaults', variables('keyvaultName'))]"
],
"properties": {... key vault secret properties ...}
}
]
}
]
}
}
}
]
}
I've also tried to move key vault secret template out of key vault section:
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {... parameters for key vault and key vault secret resources ...},
"variables": {
"rgName": "[concat('rg-', substring(uniqueString(subscription().id), 0, 4))]",
"keyvaultName": "[concat('keyvault-', substring(uniqueString(subscription().id), 0, 4))]"
},
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2021-04-01",
"location": "[parameters('location')]",
"name": "[variables('rgName')]"
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2021-04-01",
"name": "keyvaultDeployment",
"resourceGroup": "[variables('rgName')]",
"dependsOn": [
"[resourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.KeyVault/vaults",
"apiVersion": "2021-10-01",
"name": "[variables('keyvaultName')]",
"location": "[parameters('location')]",
"properties": {... key vault properties ...}
},
{
"type": "Microsoft.KeyVault/vaults/secrets",
"apiVersion": "2021-10-01",
"name": "[concat(variables('keyvaultName'), '/', parameters('keyvaultSecretName'))]",
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults', variables('keyvaultName'))]"
],
"properties": {... key vault secret properties ...}
}
]
}
}
}
]
}
But it has generated the error "Key vault resource is not defined in the template." Is there a way to use child resources in subscription scope templates at all?

I figured it out. Since I was working mostly with resource group deployments, I've used resourceId() function to pass values for 'dependsOn' template parameter. However, in subscription deployment scenario with child resources defined in the template resourceId() function wasn't working properly. As it turned out, you have to use either concat() or format() functions (or plain text) to pass the value for 'dependsOn' parameter for a child resource.
Here is the code that worked:
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2021-04-01",
"name": "keyvaultDeployment",
"resourceGroup": "[variables('rgName')]",
"dependsOn": [
"[resourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.KeyVault/vaults",
"apiVersion": "2021-10-01",
"name": "[variables('keyvaultName')]",
"location": "[parameters('location')]",
"properties": {... key vault properties ...},
"resources": [
{
"type": "Microsoft.KeyVault/vaults/secrets",
"apiVersion": "2021-10-01",
"name": "[concat(variables('keyvaultName'), '/', parameters('keyvaultSecretName'))]",
"dependsOn": [
"[concat('Microsoft.KeyVault/vaults/', variables('keyvaultName'))]"
],
"properties": {... key vault secret properties ...}
}
]
}
]
}
}
}
This is probably pretty obvious for more experienced users, but I've worked with multiple templates and multiple deployment tasks in my pipelines so I had to use resourceId() functions. Probably the conclusion above is valid for any child resources in any scope (subscription or resource group).

Related

Can't reference principalId of user assigned identity for key vault in same arm template

I'm having trouble referencing a user assigned identity that I create alongside a KeyVault instance within the same template. I've searched through documentation on how to reference managed identities in general and I believe it looks like the following:
reference(resourceId('resource-type', 'resource-name'), 'api-version', 'Full)).identity.principalId
However, this doesn't work for me and I'm not sure if it has something to do with deploying my templates at the subscription scope. I'm currently using linkedTemplates so that I can organize my code better and have a main template like the following:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.1",
"parameters": {},
"resources": [
{
"apiVersion": "2020-06-01",
"location": "[variables('location')]",
"name": "key-vault-test”,
"properties": {
"mode": "Incremental",
"parameters": { },
"templateLink": {
"relativePath": “vault.json"
}
},
"type": "Microsoft.Resources/deployments"
}
],
}
Next, vault.json is as follows:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.1",
"parameters": {
…
},
"resources": [
{
"apiVersion": "2018-05-01",
"location": “[…..]”,
"name": "key-vault",
"type": "Microsoft.Resources/resourceGroups"
},
{
"apiVersion": "2020-06-01",
"dependsOn": [
"[resourceId('Microsoft.Resources/resourceGroups', 'key-vault')]"
],
"name": “user-assigned-identity-dep”,
"properties": {
"expressionEvaluationOptions": {
"scope": "outer"
},
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"apiVersion": "2018-11-30",
"location": “[…]”,
"name": “myIdentity”,
"type": "Microsoft.ManagedIdentity/userAssignedIdentities"
}
]
}
},
"resourceGroup": "key-vault",
"type": "Microsoft.Resources/deployments"
},
{
"apiVersion": "2020-06-01",
"name": "key-vault-dep”,
"properties": {
"expressionEvaluationOptions": {
"scope": "outer"
},
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"apiVersion": "2018-02-14",
"location": “[…]”,
"name": "[concat('key-vault-', uniqueString(subscription().id))]",
"properties": {
"accessPolicies": [
{
"objectId": "[reference(variables('keyVaultIdentityId'), '2018-11-30', 'Full').identity.principalId]",
"permissions": {
"secrets": [
"get",
"list"
]
},
"tenantId": "[subscription().tenantId]"
}
],
"enableSoftDelete": true,
"sku": {
"family": "A",
"name": "Standard"
},
"tenantId": "[subscription().tenantId]"
},
"type": "Microsoft.KeyVault/vaults"
}
]
}
},
"resourceGroup": "key-vault",
"type": "Microsoft.Resources/deployments"
}
],
"variables": {
"keyVaultIdentityId": "/subscriptions/…/resourceGroups/key-vault/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myIdentity”
}
}
When I deploy the main template, the reference function that I've crafted returns me the deployment of the keyVault and not the managed identity at all.
'The language expression property 'identity' doesn't exist, available properties are 'apiVersion, location, tags, properties, deploymentResourceLineInfo, subscriptionId, resourceGroupName, scope, resourceId, referenceApiVersion, condition, isConditionTrue, isTemplateResource, isAction, provisioningOperation
I'm not sure if I'm doing something wrong or if there's a better way to do this. In summary, I'm attempting to create a user assigned identity and create a key vault with access policies for that identity in the same template.
If you want to get the principalId of the user assigned identity, you need to use the following expression. For more details, please refer to here
[reference(resourceId('<subscriptionId>','<resourceGroupName>','Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')),'2018-11-30','Full').properties.principalId]
for example
my template
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"name": {
"defaultValue": "mytest",
"type": "String"
}
},
"variables": {},
"resources": [{
"type": "Microsoft.ManagedIdentity/userAssignedIdentities",
"name": "[parameters('name')]",
"apiVersion": "2018-11-30",
"location": "[resourceGroup().location]"
}
],
"outputs": {
"principalId": {
"value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')),'2018-11-30','Full').properties.principalId]",
"type": "string"
}
}
}
I got the same error but I had forgotten to assign a managed identity to my resource in the ARM template like:
"identity": {
"type": "SystemAssigned"
},
Example:
{
"type": "Microsoft.Web/sites",
"kind": "functionapp",
"name": "[variables('uniqueResourceNameBase')]",
"apiVersion": "2016-08-01",
"location": "[resourceGroup().location]",
"identity": {
"type": "SystemAssigned"
},
"properties": { ... }
}
After doing this I could use .identity.principalId.
Source:
https://www.codeisahighway.com/there-is-a-new-way-to-reference-managed-identity-in-arm-template/
You can also manually set it in Azure Portal under your service -> Identity.
A system assigned managed identity is restricted to one per resource
and is tied to the lifecycle of this resource. You can grant
permissions to the managed identity by using Azure role-based access
control (Azure RBAC). The managed identity is authenticated with Azure
AD, so you don’t have to store any credentials in code. Learn more
about Managed identities.

How do I use dependsOn on a copyIndex (array) resource?

I'm using an arm template to deploy topics and subscriptions. My resources to be deployed are of type Microsoft.Resources/deployments because I'm targeting a resource group external to where the deployment lives.
If the topics already exists, the template works.
Because arm template resources are deploy in parallel, I need to have the topics deploy before the subscriptions - this ordering is made possible by dependsOn.
The problem I'm having is that because the "name" value of the topics have a copyIndex(), I'm not sure how I can target the topic resource.
Among the many things I have tried, here are some:
[concat(parameters('serviceBusNamespaceName'), '/', parameters('subscriptions')[copyIndex()].topic)]
[resourceId('Microsoft.Resources/deployments',
parameters('topics'))]
["topicLoop"]
Here are the topics and subscriptions resource objects in my template:
{
"apiVersion": "2018-02-01",
"type": "Microsoft.Resources/deployments",
"name": "[concat(parameters('serviceBusNamespaceName'), copyIndex())]",
"resourceGroup": "[parameters('sharedResourcesResourceGroupName')]",
"copy": {
"name": "topicLoop",
"count": "[length(parameters('topics'))]"
},
"properties": {
"mode": "Incremental",
"template": {
"$schema": "2018-05-01",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.ServiceBus/namespaces/topics",
"name": "[concat(parameters('serviceBusNamespaceName'), '/', parameters('topics')[copyIndex()])]",
"apiVersion": "2017-04-01",
"location": "[resourceGroup().location]",
"properties": {}
}
]
}
}
},
{
"apiVersion": "2018-02-01",
"type": "Microsoft.Resources/deployments",
"name": "[concat(parameters('subscriptions')[copyIndex()].topic, copyIndex())]",
"resourceGroup": "[parameters('sharedResourcesResourceGroupName')]",
"copy": {
"name": "subscriptionLoop",
"count": "[length(parameters('subscriptions'))]"
},
"properties": {
"mode": "Incremental",
"template": {
"$schema": "2018-05-01",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.ServiceBus/namespaces/topics/subscriptions",
"name": "[concat(parameters('serviceBusNamespaceName'), '/', parameters('subscriptions')[copyIndex()].topic, '/', parameters('subscriptions')[copyIndex()].subscription)]",
"apiVersion": "2017-04-01",
"location": "[resourceGroup().location]",
"properties": {}
}
]
}
},
"dependsOn": [
// what goes here?! I need to have this depend on the topics
]
}
The dependsOn value is simply:
"dependsOn" : ["topicLoop"]
But it needed to be on the most external resource and not nested template.
yes, you can use dependsOn for each individual resource if you can generate its name. or you can hardcode those. either way, you can dependsOn the whole loop by using its name or individual resources inside the loop by referencing them by their name

How to use copyIndex in a nested template resource?

Big picture: I want to use the ARM template to create multiple topics on a service bus.
Known fact: The app service that deploys the template is in a different resource group than the service bus.
Pain point: I'm using a nested template because I'm trying to create resources (topics) that are external to the targeted resource group. Within this nested template, I'm not sure how to get copy to work correctly.
From this MS doc , I believe my syntax is correct.
This is how my parameters are listed:
"sharedResourcesResourceGroupName": {
"type": "string",
"defaultValue": "sharedResourceGroupName",
"metadata": {
"description": "Resource Group in which platform shared resources live"
}
},
"serviceBusNamespaceName": {
"type": "string",
"defaultValue": "serviceBusName",
"metadata": {
"description": "Name of the Service Bus namespace"
}
},
"topics": {
"type": "array",
"metadata": {
"description": "List of topics"
},
"defaultValue": [
"topic1",
"topic2"
]
}
This is my resource object for creating the topics with the copyIndex() method:
{
"apiVersion": "2018-05-01",
"type": "Microsoft.Resources/deployments",
"name": "[concat(parameters('serviceBusNamespaceName'))]",
"resourceGroup": "[parameters('sharedResourcesResourceGroupName')]",
"properties": {
"mode": "Incremental",
"template":{
"$schema": "2018-05-01",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"type": "Microsoft.ServiceBus/namespaces/topics",
"name": "[concat(parameters('serviceBusNamespaceName'), '/', parameters('topics')[copyIndex()])]",
"apiVersion": "2017-04-01",
"location": "[resourceGroup().location]",
"properties": {},
"copy": {
"name": "topics",
"count": "[length(parameters('topics'))]"
},
"dependsOn": [
"[parameters('serviceBusNamespaceName')]"
]
}
]
}
}
}
I am testing the arm template deployment using the Azure Powershell with these commands:
Connect-AzAccount
Set-AZContext -SubscriptionName subscriptionWhereTheAppServiceLives
New-AzResourceGroupDeployment -ResourceGroupName resourceGroupWhereAppServiceLives -TemplateFile <path to template file>\azuredeploy.json -TemplateParameterFile <path to parameters file>\azuredeploy.parameters.json
The error I'm getting from the Azure powershell console is:
The template function 'copyIndex' is not expected at this location. The function can only be used in a resource with copy specified.
If I remove the "copy" object and replace "name" with something like "[concat(parameters('serviceBusNamespaceName'), '/topicName')]", then the template is able to create ONE topic in the right service bus. But I'm looking to create multiple topics.
Any insight would be greatly appreciated!
I think you can do this:
{
"apiVersion": "2018-05-01",
"type": "Microsoft.Resources/deployments",
"name": "[concat(parameters('serviceBusNamespaceName'), copyIndex())]",
"resourceGroup": "[parameters('sharedResourcesResourceGroupName')]",
"copy": {
"name": "topics",
"count": "[length(parameters('topics'))]"
},
"dependsOn": [
"[parameters('serviceBusNamespaceName')]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "2018-05-01",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.ServiceBus/namespaces/topics",
"name": "[concat(parameters('serviceBusNamespaceName'), '/', parameters('topics')[copyIndex()])]",
"apiVersion": "2017-04-01",
"location": "[resourceGroup().location]",
"properties": {}
}
]
}
}
}

ARM nested template ‘Invalid Template could not find template resource’

I'm deploying arm template to create an SSL certificate to existing traffic managers and to bind the certificates to the app services.
Since the app services in one resource group and the traffic manager and certificate in a different resource group - I use nested template.
i got an error with my certificate SSL:
Deployment template validation failed: 'The template reference
'blabla-ssl1' is not valid: could not find template resource or
resource copy with this name
"comments": "Get the Traffic Manager SSL cert that will be binded to the app",
"copy": {
"name": "loop",
"count": "[length(variables('locations'))]"
},
"type": "Microsoft.Web/certificates",
"name": "[concat(variables('tmsslcert')['secretname'], copyIndex())]",
"apiVersion": "2016-03-01",
"location": "[variables('locations')[copyIndex()]]",
"dependsOn": [
"[variables('TMName')]"
],
"properties": {
"keyVaultId": "[variables('tmsslcert')['KeyVaultId']]",
"secretname": "[variables('tmsslcert')['secretname']]"
}
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2018-05-01",
"resourceGroup": "[variables('webappResourceGroup')]",
"name": "[concat('AddTMSSLCert_',variables('locations')[copyIndex()],'_nestedTemplate')]",
"copy": {
"name": "endpointloop",
"count": "[length(variables('locations'))]"
},
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"comments": "app hostname binding of TM CNAME",
"type": "Microsoft.Web/sites/hostNameBindings",
"name": "[concat(variables('webappDNSNamePrefix'), '-', variables('locations')[copyIndex()], '/', variables('tmcname'))]",
"apiVersion": "2016-08-01",
"location": "[variables('locations')[copyIndex()]]",
"scale": null,
"properties": {
"siteName": "variables('webappDNSNamePrefix'), '-', variables('locations')[copyIndex()]",
"sslState": "SniEnabled",
"thumbprint": "[reference(resourceId(variables('webappResourceGroup'),'Microsoft.Web/certificates', concat(variables('tmsslcert')['secretname'], copyIndex())),'2016-03-01').Thumbprint]"
},
"dependsOn": [
"[concat(variables('tmsslcert')['secretname'], copyIndex())]",
//"[concat('Microsoft.Web/certificates/', variables('tmsslcert')['secretname'], copyIndex())]"
]
}
]
}
}
}
its impossible to tell where the error is exactly (given the data you provided), but this means either your references or dependsOn are trying to reach the resource that's either not created or in a different resource group. One thing that looks specifically wrong is this:
"dependsOn": [
"[concat(variables('tmsslcert')['secretname'], copyIndex())]",
//"[concat('Microsoft.Web/certificates/', variables('tmsslcert')['secretname'], copyIndex())]"
]
this would not work, because it will work in the context of the nested deployment, so in a different resource group

ResouceGroup().Location in nested template confusion

I have nested template below. It seems that resourcegroup().location for container registry refers to resourcegroup defined in parent template and not the one it's being deployed via nestedTemplate. How do I properly refer to location of resource group in nested template instead?
{
"apiVersion": "2017-05-10",
"name": "nestedTemplate",
"type": "Microsoft.Resources/deployments",
"resourceGroup": "[variables('SharedResourceGroup')]",
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"name": "[variables('ACRName')]",
"type": "Microsoft.ContainerRegistry/registries",
"apiVersion": "2017-10-01",
"location": "[parameters('location')]",
"comments": "Container registry for storing docker images",
"sku": {
"name": "Standard",
"tier": "Standard"
},
"properties": {
"adminUserEnabled": true
}
},
you need to use linked template, not inline template. the thing with inline template it renders it before deploying it. so it renders it as if it was part of the parent template.
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2018-05-01",
"name": "linkedTemplate",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri":"https://mystorageaccount.blob.core.windows.net/AzureTemplates/newStorageAccount.json",
"contentVersion":"1.0.0.0"
},
"parametersLink": {
"uri": "https://mystorageaccount.blob.core.windows.net/AzureTemplates/newStorageAccount.parameters.json",
"contentVersion":"1.0.0.0"
}
}
}
It will work this way. I'd suggest not using nested inline templates.
https://learn.microsoft.com/en-us/azure/azure-resource-manager/resource-group-linked-templates

Resources