Azure VM Custom script extension identity access to Storage Account - azure

In the custom script extension of a VM I want to execute this command:
#download azcopy from http://aka.ms/downloadazcopy
c:\azcopy login --identity
C:\azcopy copy https://mystorage.blob.core.windows.net/software C:\Temp --recursive
But for this to work the identity of the VM need to be added as "Storage Blob Data Contributor". In terraform we could do it this way
resource"azurerm_role_assignment""role" {​​​​​​​​
scope= data.azurerm_storage_account.vault.id
role_definition_name="Storage Blob Data Contributor"
principal_id= azurerm_windows_virtual_machine.vm.identity.0.principal_id
}​​​​​​​​
But if we do not use terraform and instead use Azure DevOps and ARM templates, how would you execute it ? Because the VM is not created yet to give identity access. Custom script extension is part of the creation.

You can enable a system-assigned managed identity using an Azure Resource Manager template. Reference here.
Step1
To enable system-assigned managed identity, locate the Microsoft.Compute/virtualMachines resource of interest within the resources section and add the "identity" property at the same level as the "type": "Microsoft.Compute/virtualMachines" property. Use the following syntax:
"identity": {
"type": "SystemAssigned"
},
Step2
When you're done, the following sections should be added to the resource section of your template and it should resemble the following:
"resources": [
{
//other resource provider properties...
"apiVersion": "2018-06-01",
"type": "Microsoft.Compute/virtualMachines",
"name": "[variables('vmName')]",
"location": "[resourceGroup().location]",
"identity": {
"type": "SystemAssigned",
},
},
//The following appears only if you provisioned the optional VM extension (to be deprecated)
{
"type": "Microsoft.Compute/virtualMachines/extensions",
"name": "[concat(variables('vmName'),'/ManagedIdentityExtensionForWindows')]",
"apiVersion": "2018-06-01",
"location": "[resourceGroup().location]",
"dependsOn": [
"[concat('Microsoft.Compute/virtualMachines/', variables('vmName'))]"
],
"properties": {
"publisher": "Microsoft.ManagedIdentity",
"type": "ManagedIdentityExtensionForWindows",
"typeHandlerVersion": "1.0",
"autoUpgradeMinorVersion": true,
"settings": {
"port": 50342
}
}
}
]
Step3
Grant it a role "Storage Blob Data Contributor" access to the resource group in which it was created.
Under the parameters section add the following:
"builtInRoleType": {
"type": "string",
"defaultValue": "StorageBlobDataContributor"
},
"rbacGuid": {
"type": "string"
}
Under the variables section add the following:
"StorageBlobDataContributor": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]"
Under the resources section add the following:
{
"apiVersion": "2017-09-01",
"type": "Microsoft.Authorization/roleAssignments",
"name": "[parameters('rbacGuid')]",
"properties": {
"roleDefinitionId": "[variables(parameters('builtInRoleType'))]",
"principalId": "[reference(variables('vmResourceId'), '2017-12-01', 'Full').identity.principalId]",
"scope": "[resourceGroup().id]"
},
"dependsOn": [
"[concat('Microsoft.Compute/virtualMachines/', parameters('vmName'))]"
]
}
Update
To grant the identity with an RBAC role to access a specific storage account. Refer to this answer.
{
"apiVersion": "2018-01-01-preview",
"type": "Microsoft.Storage/storageAccounts/providers/roleAssignments",
"name": "[concat(variables('storageAccountName'), '/Microsoft.Authorization/',parameters('rbacGuid'))]",
"properties": {
"roleDefinitionId": "[variables(parameters('builtInRoleType'))]",
"principalId": "[reference(resourceId('Microsoft.Compute/virtualMachines',parameters('vmName')), '2017-12-01', 'Full').identity.principalId]"
},
"dependsOn": [
"[concat('Microsoft.Compute/virtualMachines/', parameters('vmName'))]"
]
}

Related

Creating User managed identity and server administrator using ARM Template

I want to add a user managed identity as admin to a sql server resource in azure. I can create the user identity using ARM Templates like this:
{
"type": "Microsoft.ManagedIdentity/userAssignedIdentities",
"name": "[variables('identityName')]",
"apiVersion": "2018-11-30",
"location": "[resourceGroup().location]"
},
and I can create a administrator resource from ARM template as well. But how do I reference the above created identity in the administrator resource? :
{
"name": "[concat(variables('serverName'),'/ActiveDirectory')]",
"type": "Microsoft.Sql/servers/administrators",
"dependsOn": [
"[resourceId('Microsoft.Sql/servers', variables('serverName'))]"
],
"apiVersion": "2019-06-01-preview",
"properties": {
"administratorType": "ActiveDirectory",
"login": "[parameters('identityName')]",
"sid": "<How do I add reference here>",
"tenantId": "<How do I add reference here>"
}
}
I tried something and the following seems to be working fine:
{
"name": "[concat(variables('serverName'),'/ActiveDirectory')]",
"type": "Microsoft.Sql/servers/administrators",
"dependsOn": [
"[resourceId('Microsoft.Sql/servers', variables('serverName'))]",
"[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', variables('identityName'))]"
],
"apiVersion": "2019-06-01-preview",
"properties": {
"administratorType": "ActiveDirectory",
"login": "[variables('identityName')]",
"sid": "[reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('identityName'))).clientId]",
"tenantId": "[reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('identityName'))).tenantId]"
}
},

How to create a Key Vault Managed Storage Account in an ARM template?

I have an ARM template that creates a storage account and a key vault:
...
{
"apiVersion": "2019-04-01",
"type": "Microsoft.Storage/storageAccounts",
"name": "mystorageaccountname",
"location": "canadacentral",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2",
"properties": {
"supportsHttpsTrafficOnly": true
}
}
...
...
{
"apiVersion": "2019-09-01",
"type": "Microsoft.KeyVault/vaults",
"name": "mykeyvault",
"location": "canadacentral",
"properties": {
"tenantId": "[subscription().tenantId]",
"sku": {
"family": "A",
"name": "standard"
},
"accessPolicies": []
}
}
...
I would like for my Key Vault to manage the storage account. Unfortunately for me, the examples I have found so far are Powershell scripts.
In the ARM template documentation, it is not clear to me how I could implement such a thing.
Question
How can I configure the key vault in the ARM template to manage the storage account?
Until some time, there was no possibility to do such a thing as ARM Templates didn't support creating Azure KV keys directly from them. Recently there was an update posted, that it's finally available:
{
"type": "Microsoft.KeyVault/vaults/keys",
"name": "[concat(parameters('vaultName'), '/', parameters('keyName'))]",
"apiVersion": "2019-09-01",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults', parameters('vaultName'))]"
],
"properties": {
"kty": "[parameters('keyType')]",
"keyOps": "[parameters('keyOps')]",
"keySize": "[if(equals(parameters('keySize'), -1), json('null'), parameters('keySize'))]",
"curveName": "[parameters('curveName')]"
}
}
You see the full example here - https://learn.microsoft.com/en-us/azure/key-vault/keys/quick-create-template?tabs=CLI. With that you are able to just reference a Storage Account key value and put it directly into Azure Key Vault. You can try to do that using that structure:
[listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value]

Azure: Assign Roles via ARM Template to storage container

I'm trying to assign the role "Storage Blob Data Contributor (Preview)" to a specific storage container via arm template. But I just can't figure out the correct syntax.
This is what I have:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"principalId": {
"type": "string",
"metadata": {
"description": "The principal to assign the role to"
}
},
"builtInRoleType": {
"type": "string",
"allowedValues": [
"Contributor",
"Reader",
"StorageBlobDataContributor"
],
"metadata": {
"description": "Built-in role to assign"
}
}
},
"variables": {
"apiVersion": "2017-05-01",
"Owner": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
"Contributor": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
"Reader": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
"StorageBlobDataContributor": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]",
"TestVariable": "[concat('STORAGEACCOUNTNAME','/Microsoft.Authorization/',guid(subscription().subscriptionId))]"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts/providers/roleAssignments",
"apiVersion": "[variables('apiVersion')]",
"name": "[variables('TestVariable')]",
"properties": {
"roleDefinitionId": "[variables('Reader')]",
"principalId": "[parameters('principalId')]"
}
},
{
"type": "Microsoft.Storage/storageAccounts/STORAGEACCOUNTNAME/blobServices/containers/blobCONTAINERNAME/providers/Microsoft.Authorization/roleAssignments",
"apiVersion": "[variables('apiVersion')]",
"name": "STORAGEACCOUNTNAME/blobServices/containers/default/blobCONTAINERNAME/Microsoft.Authorization/NEW-GUID",
"properties": {
"roleDefinitionId": "[variables('StorageBlobDataContributor')]",
"principalId": "[parameters('principalId')]"
}
}
],
"outputs": {}
}
I can attach the reader role to the storage account itself succesfully.
But for the container I get the following error:
new-AzResourceGroupDeployment : 09:21:24 - Error: Code=InvalidTemplate; Message=Deployment template validation failed: 'The template resource
'STORAGEACCOUNTNAME/blobServices/containers/CONTAINERNAME/Microsoft.Authorization/GUID' for type
'Microsoft.Storage/storageAccounts/STORAGEACCOUNTNAME/blobServices/default/containers/CONTAINERNAME/providers/Microsoft.Authorization/roleAssignments' at line '44' and column '9' has incorrect
segment lengths. A nested resource type must have identical number of segments as its resource name. A root resource type must have segment length one greater than its resource name. Please see
https://aka.ms/arm-template/#resources for usage details.'.
I have tried so many ways trying to attach the role, that I out of idea's.
Can someone help me?
you need to construct something like this:
resourceId/Microsoft.Authorization/roleAssignments/NEW-GUID
and resourceId is normally being constructed as
type: provider/namespace
name: name
provider/namespace/name
for example, for subnet it would be (notice it takes 1 segment from each line in turn, except for the first one, first one is always 2 segments):
type: microsoft.network/virtualnetworks/subnets
name: vnetName/subnetName
microsoft.network/virtualnetworks/vnetName/subnets/subnetName
if that is even possible it would look like something like this:
"type": "Microsoft.Storage/storageAccounts/blobServices/containers/providers/roleAssignments",
"name": "STORAGEACCOUNTNAME/default/CONTAINERNAME/Microsoft.Authorization/NEW-GUID"
Microsoft.Storage/storageAccounts/STORAGEACCOUNTNAME/containers/CONTAINERNAME/providers/Microsoft.Authorization/roleAssignments/NEW-GUID
Made some little adjustments:
"type": "Microsoft.Storage/storageAccounts/blobServices/containers/providers/roleAssignments",
"name": "STORAGEACCOUNTNAME/default/CONTAINERNAME/Microsoft.Authorization/NEW-GUID"
This way I can assign roles on the container itself. Thanks 4c74356b41 for pointing me in the right direction
Using Erik's answer above (which I've up-voted of course, thx Erik!), I was able to solve the similar issue for RBAC permissions on a Queue of a Storage Account using ARM templates.
Here is an example ARM template for adding Sender role to a single Queue of a Storage Account...
<..snip..>
"parameters": {
"PrincipalId": {
"type": "string",
"minLength": 36,
"maxLength": 36
}
},
"variables": {
"SubscriptionId": "[concat('/subscriptions/', subscription().subscriptionId)]",
"RoleDefinitions": "[concat(variables('SubscriptionId'), '/providers/Microsoft.Authorization/roleDefinitions/')]",
"QueueSenderRole": "c6a89b2d-59bc-44d0-9896-0f6e12d7b80a"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts/queueServices/queues/providers/roleAssignments",
"name": "mystorageaccount/default/myqueue/Microsoft.Authorization/00000000-1234-0000-5678-000000000000", // NB example only; pick an idempotent but unique value
"apiVersion": "2018-09-01-preview",
"properties": {
"roleDefinitionId": "[concat(variables('RoleDefinitions'), variables('QueueSenderRole'))]",
"principalId": "[parameters('PrincipalId')]"
}
}
]

Can a "Microsoft.Resources/deployments" dependsOn another "Microsoft.Resources/deployments" in Azure ARM Templates?

I want to 1st deploy a resource group, next deploy a bunch of resources (say a storage account and a keyvault), and finally create some Key Vault secrets (secret with the key to the storage account created in previous step).
this is how i define my resources
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2018-05-01",
"location": "[parameters('RgLocation')]",
"name": "[variables('RgName')]",
"properties": {},
"tags": "[variables('ResourceTags')]"
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2018-05-01",
"name": "resourcesDeployment",
"resourceGroup": "[variables('RgName')]",
"dependsOn": [
"[resourceId('Microsoft.Resources/resourceGroups', variables('RgName'))]"
],
"properties": {
...
}
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2018-05-01",
"name": "secretsDeployment",
"resourceGroup": "[variables('RgName')]",
"dependsOn": [
"[resourceId('Microsoft.Resources/deployments', 'resourcesDeployment')]"
],
"properties": {
...
}
}
]
the resourcesDeployment "dependsOn" the resource group, which works perfectly/as expected.
the secretsDeployment "dependsOn" resourcesDeployment and this is the part that fails. I'm getting this error:
Deployment template validation failed: 'The resource 'Microsoft.Resources/deployments/resourcesDeployment' is not defined in
the template.
How do I ensure that these deployments happen in order.
Any help is much appreciated

How to create a new resource in Azure ARM templates?

Ok, I've done everything what described here with ARM template - https://azure.microsoft.com/en-us/documentation/articles/web-sites-integrate-with-vnet. Except one thing - Enabling VNET Integration with a pre-existing VNET.
Can this be done in ARM templates?
Thanks!
Here is a sample template that might help you. It's modified from this quickstart sample in GitHub
{
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"hostingPlanName": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "Name of the hosting plan to use in Azure."
}
},
"webSiteName": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "Name of the Azure Web app to create."
}
},
"vnetName": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "Name of an existing Azure VNet which has a Gateway Subnet already, and is in the resource group you are going to deploy."
}
},
"skuName": {
"type": "string",
"defaultValue": "S1",
"allowedValues": [
"S1",
"S2",
"S3",
"P1",
"P2",
"P3",
"P4"
],
"metadata": {
"description": "Describes plan's pricing tier and instance size. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/"
}
},
"skuCapacity": {
"type": "int",
"defaultValue": 1,
"minValue": 1,
"metadata": {
"description": "Describes plan's instance count"
}
}
},
"resources": [
{
"apiVersion": "2015-08-01",
"name": "[parameters('hostingPlanName')]",
"type": "Microsoft.Web/serverfarms",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "HostingPlan"
},
"sku": {
"name": "[parameters('skuName')]",
"capacity": "[parameters('skuCapacity')]"
},
"properties": {
"name": "[parameters('hostingPlanName')]"
}
},
{
"apiVersion": "2015-08-01",
"name": "[parameters('webSiteName')]",
"type": "Microsoft.Web/sites",
"location": "[resourceGroup().location]",
"tags": {
"[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "Resource",
"displayName": "Website"
},
"dependsOn": [
"[concat('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]"
],
"properties": {
"name": "[parameters('webSiteName')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]"
},
"resources": [
{
"apiVersion": "2015-08-01",
"name": "web",
"type": "config",
"dependsOn": [
"[concat('Microsoft.Web/sites/', parameters('webSiteName'))]"
],
"properties": {
"pythonVersion": "3.4"
}
},
{
"apiVersion": "2015-08-01",
"name": "[parameters('vnetName')]",
"type": "virtualNetworkConnections",
"location": "[resourceGroup().location]",
"dependsOn": [
"[concat('Microsoft.Web/sites/', parameters('webSiteName'))]"
],
"properties": {
"vnetResourceId": "[concat(resourceGroup().id, '/providers/Microsoft.Network/virtualNetworks/', parameters('vnetName'))]"
}
}
]
}
]
}
Here are 3 things you should be careful with.
The template starts with a python web app template, and adds a "Microsoft.Web/sites/virtualNetworkConnections" resource. So, if you are using other programing language, you can start with some other template.
The pre-existing VNet should be in the same resource group you are deploying. If the VNet you are using are not in the same resource group, you should modify the "vnetResourceId" in the "properties" of the "Microsoft.Web/sites/virtualNetworkConnections".
The VNet you are using should have a Gateway with a Point-to-Site address already. Otherwise, you will not be able integrate you web app to the VNet. For more details, see Configure a Point-to-Site connection to a virtual network using PowerShell
Update: About how I get this info, well, there is not much about this on the net. This template is constructed based on the PowerShell solution and my knowledge about ARM template. The PowerShell solution is available in this article. Another possible way to get an ARM template is to create those resources in one resource group, and export the template of the resource group in the portal. But, for this case, that is not going to work, because resource type "Microsoft.Web/sites/virtualNetworkConnections" is not supported yet. However, you can still get a look at the REST API by the PowerShell Command Get-AzureRmResource with option -debug.
Get-AzureRmResource -ResourceGroupName <resource group> -ResourceType Microsoft.Web/sites/virtualNetworkConnections -Name <web app>/<VNet> -debug -ApiVersion 2015-08-01
You will get the following REST API.
Uri:
https://management.azure.com/subscriptions/<subscription id>/resourceGroups/<resource group>/providers/Microsoft.Web/sites/<web app>/virtualNetworkConnections/<VNet>?api-version=2015-08-01
Body:
{
"id": "/subscriptions/<subscription id>/resourceGroups/<resource group>/providers/Microsoft.Web/sites/<web app>/virtualNetworkConnections/<VNet>",
"name": "<VNet>",
"type": "Microsoft.Web/sites/virtualNetworkConnections",
"location": "<Location>",
"tags": null,
"properties": {
"vnetResourceId": "/subscriptions/<subscription id>/resourceGroups/<resource group>/providers/Microsoft.Network/virtualNetworks/<VNet>"
"certThumbprint": "<Thumbprint>",
"certBlob": "<cert>",
"routes": null,
"resyncRequired": false,
"dnsServers": null
}
}
Skipping some automatically generated values, you will get the template which is quite similar to the one I write:
{
"name": "<VNet>",
"type": "Microsoft.Web/sites/virtualNetworkConnections",
"location": "<Location>",
"properties": {
"vnetResourceId": "/subscriptions/<subscription id>/resourceGroups/<resource group>/providers/Microsoft.Network/virtualNetworks/<VNet>"
}
}

Resources