I can currently manage resources across multiple Azure AD tenants with Azure Resource Manager through PowerShell (client ID 1950a258-227b-4e31-a9cf-717495945fc2), using delegated permissions with a user account who has elevated privileges in both Azure AD’s and all relevant subscriptions. I.e., a SubscriptionClient
with logged in with this user has a method SubscriptionClient.tenants.List()
which will list multiple tenants if I have access to subscriptions in different tenants as this user.
I’m writing cross-tenant automation, and what I’m trying to do is have a .NET function in Azure do the above without delegating it permissions from some sort of cross-tenant God user account (as an old-school service account). Instead, I want to give the function itself the permissions.
First attempt - using an MSI:
I figured to actually get a service principle to exist in a non-native tenant of a service principle is to use (newlines for readability):
https://login.microsoftonline.com/{Target_foreign_tenant}/adminconsent ?client_id={id_of_msi} &redirect_uri=http://localhost/myapp/permissions
Using an admin account and accept the permissions. The issue here is the MSI needs Graph API permissions for this to work. So I then tried:
New-AzureADServiceAppRoleAssignment `#` -ObjectId {object_id_of_msi} `#` -PrincipalId {object_id_of_msi} `#` -ResourceId {object_id_of_graph_SP} `#` -Id {permissions_id_of_relevant_graph_role}
The above command, run with global admin permissions for the service principal of an MSI, gives the "insufficient privileges" error shown below. (Note that on the service principal of an app registration, this works fine, no privileges issue.)
New-AzureADServiceAppRoleAssignment : Error occurred while executing NewServicePrincipalAppRoleAssignment Code: Authorization_RequestDenied Message: Insufficient privileges to complete the operation. HttpStatusCode: Forbidden HttpStatusDescription: Forbidden HttpResponseStatus: Completed At line:1 char:1 + New-AzureADServiceAppRoleAssignment -Id {where id would be here} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [New-AzureADServiceAppRoleAssignment], ApiException + FullyQualifiedErrorId : Microsoft.Open.AzureAD16.Client.ApiException,Microsoft.Open.AzureAD16.PowerShell.NewServicePrincipalAppRoleAssignment
I was following: MSI Permissions for Graph API
So then I gave up on MSI and decided to use a multi-tenant enabled Web app App registration instead. I again used:
https://login.microsoftonline.com/{Target_forgien_ADTenant}/adminconsent ?client_id={id_of_webappreg} &redirect_uri=http://localhost/myapp/permissions
With success! I could now see in my foreign tenant my app registration under "Enterprise apps", and query in the foreign Azure AD tenant and see the service principal – hurrah!
I then excited assigned my enterprise app owner privileges to a subscription in my foreign tenant, and ran my function which lists tenants and subscriptions using the API at the top. Unfortunately, it can only see subscriptions in its home tenant still, unlike a user.
Any help on how to get either of these approaches working? My function looks like this:
public static async Task<SubscriptionClient> GetSubscriptionClient(string clientID, string clientSecret, string tenantName)
{
var serviceCreds = new TokenCredentials(await GetRMAccessToken(clientID, clientSecret, tenantName).ConfigureAwait(false));
return new SubscriptionClient(serviceCreds);
}
public static async Task<string> GetRMAccessToken(string clientID, string clientSecret, string tenantName)
{
var authString = "https://login.microsoftonline.com/" + tenantName;
var resourceUrl = "https://management.azure.com/";
var authenticationContext = new AuthenticationContext(authString, false);
var clientCred = new ClientCredential(clientID, clientSecret);
var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUrl, clientCred);
var token = authenticationResult.AccessToken;
return token;
}
var subClient = await SubscriptionManagement.GetSubscriptionClient(clientID, clientSecret, tenantName);
var tenants = new List<string>();
foreach(var tenant in subClient.Tenants.List())
{
tenants.Add(tenant.TenantId + " - " + tenant.TenantId);
}