Hello Folks!
Have you ever had a deployment blocked because the behavior you get from the portal is different than the deployment you get from an Azure Resource Manager (ARM) template or a Bicep deployment? Well, I had that issue for a little internal project I was working on. Let me tell you how I resolved it using built-in Bicep functionality.
As you may know Bicep is a domain-specific language (DSL) that uses declarative syntax to deploy Azure resources. It provides concise syntax, reliable type safety, and support for code reuse. And as far as I’m concerned, the authoring experience is far superior to writing ARM templates.
Scenario
Here is an example scenario.
When using the portal to deploy a Key Vault. You can assign an access policy and give yourself access to get/list/set/delete/recover/backup/restore secrets.
But when you deploy any resource in Azure using a Bicep file the identity of the user\service principal executing the deployment is NOT exposed to the deployment environment.
So, to resolve this, I used a Deployment Script, a User Assigned Identities and a role assignment. Basically, in my Bicep deployment file I create a User Assigned Identities, assign the adequate role to that identity so it can execute the Deployment Script and get the result I am looking for.
Here is a sample Bicep file illustrating the process I used.
param logStartMinsAgo int = 50
param now string = utcNow('F')
var keyvaultName = toLower('KeyVault${uniqueString(resourceGroup().id)}')
var keyvaultTenantId = subscription().tenantId
var getDeployObjectIDScript = loadTextContent('./scripts/getDeployObjectID.ps1')
var sshKeyGenScript = loadTextContent('./scripts/sshKeyGen.sh')
//Create User Defined Identity
resource UAIKVAdmin 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
name: '${resourceGroup().name}-UAIKVAdmin'
location: resourceGroup().location
}
// Assign previously assigned identity the contributor role in the current context
var roleDefinitionId2 = 'b24988ac-6180-42a0-ab88-20f7382dd24c'
var roleAssignmentName_var2 = guid(subscription().id, UAIKVAdmin.id, roleDefinitionId2)
resource UAIRoleContributors 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
name: roleAssignmentName_var2
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId2)
principalId: UAIKVAdmin.properties.principalId
principalType: 'ServicePrincipal'
}
dependsOn: [
UAIKVAdmin
]
}
//create a key vault with RBAC access policy enabled
resource keyvault 'Microsoft.KeyVault/vaults@2021-04-01-preview' = {
name: keyvaultName
location: resourceGroup().location
properties: {
enabledForDeployment: true
enabledForTemplateDeployment: true
enableRbacAuthorization: true
enableSoftDelete: false
networkAcls: {
defaultAction: 'Allow'
bypass: 'AzureServices'
}
sku: {
name: 'standard'
family: 'A'
}
tenantId: keyvaultTenantId
}
}
// execute a PowerShell script that will search the Azure Activity log for the userid of the entity that create the Keyvault
// the script uses the user defined identitiy to access the log info
resource deploymentUser 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
name: 'getDeploymentUser'
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${UAIKVAdmin.id}': {}
}
}
location: resourceGroup().location
kind: 'AzurePowerShell'
properties: {
azPowerShellVersion: '6.2.1'
arguments: ' -ResourceGroupName ${resourceGroup().name} -DeploymentName ${deployment().name} -StartTime ${logStartMinsAgo}'
scriptContent: getDeployObjectIDScript
forceUpdateTag: now
cleanupPreference: 'OnSuccess'
retentionInterval: 'P1D'
timeout: 'PT${logStartMinsAgo}M'
}
dependsOn: [
UAIKVAdmin
UAIRoleContributors
keyvault
]
}
// set deployment entity as Key Vault Secrets User
var KVroleDefinitionId = '4633458b-17de-408a-b874-0445c86b69e6'
var KVroleDefinitiontName = guid(subscription().id, deploymentUser.id, KVroleDefinitionId)
resource KVRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
name: KVroleDefinitiontName
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', KVroleDefinitionId)
principalId: deploymentUser.properties.outputs.userAccount
principalType: 'User'
}
dependsOn: [
deploymentUser
]
}
output userAccount string = deploymentUser.properties.outputs.userAccount
some of you will have realized that I’m now using the loadTextContent function to read the script file instead of the more regularly used “inline” format.
So, this article is just to illustrate that IT and Operations can get around deployment blockers by applying some creativity to their deployment and using the proper tools.
If you have a scenario where you are experiencing a blocker. Please, tell me in the comments below. I'd love to try to figure it out. No promisses, but i'll give it a try.
Cheers!
Pierre
Posted at https://sl.advdat.com/3ByEOId