Thursday, October 7, 2021

Dealing with deployment blockers with Bicep

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.

 

Picture1.png

 

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.

 

Picture2.png

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