|
| 1 | +extension microsoftGraphV1 |
| 2 | + |
| 3 | +// TEMPLATE DESCRIPTION |
| 4 | +// Creates a secret-less client application, using a user-assigned managed identity |
| 5 | +// as the credential (configured as part of the application's federated identity credential). |
| 6 | +// The script optionally |
| 7 | + |
| 8 | +@description('Specifies the name of cloud environment to run this deployment in.') |
| 9 | +param cloudEnvironment string = environment().name |
| 10 | + |
| 11 | +// NOTE: Microsoft Graph Bicep file deployment is only supported in Public Cloud |
| 12 | +@description('Audience uris for public and national clouds') |
| 13 | +param audiences object = { |
| 14 | + AzureCloud: { |
| 15 | + uri: 'api://AzureADTokenExchange' |
| 16 | + } |
| 17 | + AzureUSGovernment: { |
| 18 | + uri: 'api://AzureADTokenExchangeUSGov' |
| 19 | + } |
| 20 | + USNat: { |
| 21 | + uri: 'api://AzureADTokenExchangeUSNat' |
| 22 | + } |
| 23 | + USSec: { |
| 24 | + uri: 'api://AzureADTokenExchangeUSSec' |
| 25 | + } |
| 26 | + AzureChinaCloud: { |
| 27 | + uri: 'api://AzureADTokenExchangeChina' |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +@description('Specifies the resource group location.') |
| 32 | +param location string = resourceGroup().location |
| 33 | + |
| 34 | +@description('Specifies the user-assigned managed identity name to use as an application credential via federated identity credentials') |
| 35 | +param myWorkloadManagedIdentity string = 'myMSI-2024-12-18' |
| 36 | + |
| 37 | +@description('Specified the application display name') |
| 38 | +param applicationDisplayName string = 'myApp-2024-12-18' |
| 39 | + |
| 40 | +@description('Specifies the applications unique name identifier') |
| 41 | +param applicationName string = 'myApp-2024-12-18' |
| 42 | + |
| 43 | +@description('Specifies the Microsoft Graph app roles to be granted to the created application. If set to empty array [], app roles will NOT be granted and no Azure Automation accounts will be created.') |
| 44 | +param graphRoles array = ['Group.Read.All','Application.Read.All'] |
| 45 | + |
| 46 | +@description('Specifies an Azure Automation Account name for a runbook where a PS script can be run. Only created is graphRoles is not an empty array []') |
| 47 | +param automationAccountName string = 'myAutomationAccount-2024-12-18' |
| 48 | + |
| 49 | +// login endpoint and tenant ID and issuer |
| 50 | +var loginEndpoint = environment().authentication.loginEndpoint |
| 51 | +var tenantId = tenant().tenantId |
| 52 | +var issuer = '${loginEndpoint}${tenantId}/v2.0' |
| 53 | + |
| 54 | +// create a user assigned managed identity scoped to a resource group |
| 55 | +resource myManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { |
| 56 | + name: myWorkloadManagedIdentity |
| 57 | + location: location |
| 58 | +} |
| 59 | + |
| 60 | +// Create a (client) application registration with a federated identity credential (FIC) |
| 61 | +// The FIC is configured with the managed identity as the subject |
| 62 | +// NOTE: app is configured with required properties only. Add the properties your app needs |
| 63 | +resource myApp 'Microsoft.Graph/applications@v1.0' = { |
| 64 | + displayName: applicationDisplayName |
| 65 | + uniqueName: applicationName |
| 66 | + |
| 67 | + resource myMsiFic 'federatedIdentityCredentials@v1.0' = { |
| 68 | + name: '${myApp.uniqueName}/msiAsFic' |
| 69 | + description: 'Trust the workload\'s user-assigned MI as a credential for the app' |
| 70 | + audiences: [ |
| 71 | + audiences[cloudEnvironment].uri |
| 72 | + ] |
| 73 | + issuer: issuer |
| 74 | + subject: myManagedIdentity.properties.principalId |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +// Create a service principal for the application |
| 79 | +resource mySP 'Microsoft.Graph/servicePrincipals@v1.0' = { |
| 80 | + appId: myApp.appId |
| 81 | +} |
| 82 | + |
| 83 | +// NOTE: This section (to grant Microsoft Graph permissions) requires an elevated role |
| 84 | +// Grant the application only permission to Microsoft Graph |
| 85 | +// First find the Microsoft Graph service principal |
| 86 | +// Finally assign app roles to the app (if graphRoles is not an empty array) |
| 87 | + |
| 88 | +// 1. find Graph based on well-known appId |
| 89 | +resource msGraphSP 'Microsoft.Graph/servicePrincipals@v1.0' existing = { |
| 90 | + appId: '00000003-0000-0000-c000-000000000000' |
| 91 | +} |
| 92 | + |
| 93 | +// 2. Grant the client app access to Microsoft Graph using the Oauth2 scope |
| 94 | +// which delegates the app to act as the sign-in user, constrained by the Oauth2 scope |
| 95 | +// This step only happens if the oauth2GraphScope is specified. |
| 96 | + |
| 97 | +// would use a for loop but that appears to be busted for some reason |
| 98 | +var graphAppRoles = msGraphSP.appRoles |
| 99 | + |
| 100 | +// Assign multiple app role assignments to MS Graph for the app/SP. |
| 101 | +// This gives the app/SP the necessary permissions to deploy this Bicep file (in app-only mode) |
| 102 | +resource appRoleAssignments 'Microsoft.Graph/appRoleAssignedTo@v1.0' = [for (role, i) in graphRoles: { |
| 103 | + appRoleId: filter(graphAppRoles, graphAppRoles => graphAppRoles.value == role)[0].id |
| 104 | + principalId: mySP.id // Client SP being granted permission to access the resource (API) |
| 105 | + resourceId: msGraphSP.id // Resource here is Microsoft Graph |
| 106 | + } |
| 107 | +] |
| 108 | + |
| 109 | +// Create an automation account and runbook to validate created application |
| 110 | +// can call Microsoft Graph without using a secret |
| 111 | +resource automationAccount 'Microsoft.Automation/automationAccounts@2023-11-01' = if(graphRoles != []) { |
| 112 | + name: automationAccountName |
| 113 | + identity: { |
| 114 | + type:'UserAssigned' |
| 115 | + userAssignedIdentities: { |
| 116 | + '${resourceGroup().id}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/${myWorkloadManagedIdentity}':{} |
| 117 | + } |
| 118 | + } |
| 119 | + location: location |
| 120 | + properties: { |
| 121 | + sku: { |
| 122 | + name: 'Basic' |
| 123 | + } |
| 124 | + } |
| 125 | + resource myRunbook 'runbooks@2023-11-01' = { |
| 126 | + name: 'msi-as-fic-test-runbook' |
| 127 | + location: location |
| 128 | + properties: { |
| 129 | + description: 'Runbook for msi-as-fic testing using Az PowerShell' |
| 130 | + runbookType: 'PowerShell72' |
| 131 | + logProgress: false |
| 132 | + logVerbose: false |
| 133 | + } |
| 134 | + } |
| 135 | +} |
| 136 | + |
| 137 | +// outputs |
| 138 | +output clientAppId string = myApp.appId |
| 139 | +output ficIssuerAudience string = audiences[cloudEnvironment].uri |
| 140 | +output issuerURI string = issuer |
| 141 | +output tenantId string = tenantId |
| 142 | +output assignments array = [ for (role,i) in graphRoles: { |
| 143 | + appRoleIDName: appRoleAssignments[i].appRoleId |
| 144 | +}] |
| 145 | +output miPrincipalId string = myManagedIdentity.properties.principalId |
| 146 | +output miClientId string = myManagedIdentity.properties.clientId |
0 commit comments