Using Azure Bicep to deploy MS Graph resources
Tags: azure, iac, bicep, microsoftgraph
Published: 2024-06-06 16:36:47

In infrastructure as Code, we focus on deploying and configuring Azure resources. Sometimes you must define Entra ID (AzureAD) objects such as group, application, and Service principal. Until now the only way was to have two systems, one in IaC for Azure Resource and one with script (PowerShell or Azure CLI) to manage Entra ID objects.
Bicep comes with a solution to deploy MS Graph resources and Azure resources using the same code; Bicep templates support for Microsoft Graph.
Be careful, Microsoft Graph Bicep is currently in preview.
First, we need to define a scenario, you need to Create a Service Principal, deploy a VM, and give the reader role to the service principal. 
You need to configure Bicep to use Microsoft Graph. In a new folder create a main.bicep file and bicepconfig.json file. In the last file type:

```json
{
    "experimentalFeaturesEnabled": {
        "extensibility": true
    }
}
```

This will allow your bicep engine to use this feature in preview. 
In the main.bicep file, the first thing you need to do is to write: `provider microsoftGraph`, it instructs Bicep that MicrosoftGraph type should be included. If you forget to do that you will not be able to deploy any MS Graph resource and if you use Visual Studio Code, every MS Graph type will be shown as unknow. 

The bicep code to deploy the Service Principal, a VM, and assign the SP to the VM with the reader role. 

```
provider microsoftGraph

var entraIDRole = 'f2ef992c-3afb-46b9-b7cf-a126ee74c451'
resource resourceApp 'Microsoft.Graph/applications@v1.0' = {
  uniqueName: 'ExampleResourceApp'
  displayName: 'Example Resource Application'
  appRoles: [
    {
      id: entraIDRole
      allowedMemberTypes: [ 'User', 'Application' ]
      description: 'Read access to resource app data'
      displayName: 'ResourceAppData.Read.All'
      value: 'ResourceAppData.Read.All'
      isEnabled: true
    }
  ]
}

resource resourceSp 'Microsoft.Graph/servicePrincipals@v1.0' = {
  appId: resourceApp.appId
}
param adminUsername string = 'demoadmin'

@allowed([
  'sshPublicKey'
  'password'
])
param authenticationType string = 'password'
param location string = resourceGroup().location

@secure()
param adminPasswordOrKey string

var vmName  = 'demoLinuxVM'
var ubuntuOSVersion  = 'Ubuntu-2004'
var vmSize  = 'Standard_D2s_v3'
var virtualNetworkName  = 'vNet'
var subnetName  = 'Subnet'
var securityType  = 'TrustedLaunch'
var imageReference = {
  'Ubuntu-2204': {
    publisher: 'Canonical'
    offer: '0001-com-ubuntu-server-jammy'
    sku: '22_04-lts-gen2'
    version: 'latest'
  }
}
var publicIPAddressName = '${vmName}PublicIP'
var networkInterfaceName = '${vmName}NetInt'
var osDiskType = 'Standard_LRS'
var subnetAddressPrefix = '10.1.0.0/24'
var addressPrefix = '10.1.0.0/16'
var linuxConfiguration = {
  disablePasswordAuthentication: true
  ssh: {
    publicKeys: [
      {
        path: '/home/${adminUsername}/.ssh/authorized_keys'
        keyData: adminPasswordOrKey
      }
    ]
  }
}
var securityProfileJson = {
  uefiSettings: {
    secureBootEnabled: true
    vTpmEnabled: true
  }
  securityType: securityType
}
var extensionName = 'GuestAttestation'
var extensionPublisher = 'Microsoft.Azure.Security.LinuxAttestation'
var extensionVersion = '1.0'
var maaTenantName = 'GuestAttestation'
var maaEndpoint = substring('emptystring', 0, 0)

resource networkInterface 'Microsoft.Network/networkInterfaces@2023-09-01' = {
  name: networkInterfaceName
  location: location
  properties: {
    ipConfigurations: [
      {
        name: 'ipconfig1'
        properties: {
          subnet: {
            id: virtualNetwork.properties.subnets[0].id
          }
          privateIPAllocationMethod: 'Dynamic'
          publicIPAddress: {
            id: publicIPAddress.id
          }
        }
      }
    ]
  }
}

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-09-01' = {
  name: virtualNetworkName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        addressPrefix
      ]
    }
    subnets: [
      {
        name: subnetName
        properties: {
          addressPrefix: subnetAddressPrefix
          privateEndpointNetworkPolicies: 'Enabled'
          privateLinkServiceNetworkPolicies: 'Enabled'
        }
      }
    ]
  }
}

resource publicIPAddress 'Microsoft.Network/publicIPAddresses@2023-09-01' = {
  name: publicIPAddressName
  location: location
  sku: {
    name: 'Basic'
  }
  properties: {
    publicIPAllocationMethod: 'Dynamic'
    publicIPAddressVersion: 'IPv4'
    idleTimeoutInMinutes: 4
  }
}

resource vm 'Microsoft.Compute/virtualMachines@2023-09-01' = {
  name: vmName
  location: location
  properties: {
    hardwareProfile: {
      vmSize: vmSize
    }
    storageProfile: {
      osDisk: {
        createOption: 'FromImage'
        managedDisk: {
          storageAccountType: osDiskType
        }
      }
      imageReference: imageReference['Ubuntu-2204']
    }
    networkProfile: {
      networkInterfaces: [
        {
          id: networkInterface.id
        }
      ]
    }
    osProfile: {
      computerName: vmName
      adminUsername: adminUsername
      adminPassword: adminPasswordOrKey
      linuxConfiguration: ((authenticationType == 'password') ? null : linuxConfiguration)
    }
    securityProfile: (securityType == 'TrustedLaunch') ? securityProfileJson : null
  }
}

resource vmExtension 'Microsoft.Compute/virtualMachines/extensions@2023-09-01' = if (securityType == 'TrustedLaunch' && securityProfileJson.uefiSettings.secureBootEnabled && securityProfileJson.uefiSettings.vTpmEnabled) {
  parent: vm
  name: extensionName
  location: location
  properties: {
    publisher: extensionPublisher
    type: extensionName
    typeHandlerVersion: extensionVersion
    autoUpgradeMinorVersion: true
    enableAutomaticUpgrade: true
    settings: {
      AttestationConfig: {
        MaaSettings: {
          maaEndpoint: maaEndpoint
          maaTenantName: maaTenantName
        }
      }
    }
  }
}

resource roleAssignement  'Microsoft.Authorization/roleAssignments@2022-04-01' = { 
  name: guid('roleAssignment')
  scope: vm
  properties: {
    principalId: resourceSp.id
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions/', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')
  }
}
```

The code creates an Enterprise application and its SP, deploys the VM with virtual NIC and virtual network resources, and then assigns the reader role to the VM.
To deploy the SP and the VM you want, use the New-AzResourceGroupDeployment cmdlet like any other resource group deployment

```powershell
New-AzResourceGroupDeployment -ResourceGroupName bicep-azgraph -TemplateFile ./Bicep/main.bicep
```

PowerShell will ask for a password and deploy the SP and the VM

Bicep support for Microsoft Graph can be used to deploy 
- Applications and Service Principals
- Federated Identity
- Entra ID Groups
- OAuth Permission grant
- App role assigned to (but this feature does not work with personal account)

The support of Microsoft Graph is in preview, and not suitable for production. You can check the project GitHub page [here](https://github.com/microsoftgraph/msgraph-bicep-types)

You can find the code presented here on [GitHub](https://github.com/omiossec/bicep-MsGraph-demo)