Pythonic way to assign global administrator roles for Azure Active Directory

2024/11/20 12:22:10

What specifically needs to be changed in the Python 3 code below in order to successfully assign the Global Administrator role for an Azure Active Directory Tenant to a given service principal?

We tried to adjust the code from the answer to this other posting so that it can run in Python 3, but we are getting the error below. This has to be agnostic with respect to operating system, so we cannot use the bash code given in the other posting. We also do not want to add a dependency on PowerShell.

And we also tried to do this with an ARM template, but @Philip pointed out that ARM templates are not allowed to assign Active Directory tenant roles.

Note that 62e90394-69f5-4237-9190-012177145e10 is the role definition id for Global Administrator of an Azure Active Directory tenant.

COMMAND AND RESULTING ERROR:

C:\path\to\directory> python .\assignADRoles.py             
ERROR: unrecognized arguments: 'valid-service-principal-object-id', 'roleDefinitionId': '62e90394-69f5-4237-9190-012177145e10', 'directoryScopeId': '/'}Examples from AI knowledge base:
az rest --method get --url https://graph.microsoft.com/beta/auditLogs/directoryAudits 
Get Audit log through Microsoft Graphhttps://docs.microsoft.com/en-US/cli/azure/reference-index#az_rest
Read more about the command in reference docs
C:\path\to\directory> 

Note that valid-service-principal-object-id refers to an actual valid service principal object id that is redacted here for security reasons.

Also note that the user running the command is also global administrator of the same Azure Active Directory tenant and is also owner of the only in-scope subscription.

CURRENT CODE:

# coding: utf-8import subprocess
import reansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')def callTheAPI():URI="https://graph.microsoft.com/beta/roleManagement/directory/roleAssignments"BODY={"principalId": "valid-service-principal-object-id","roleDefinitionId": "62e90394-69f5-4237-9190-012177145e10","directoryScopeId": "/"}assignGlobalAdminCommand='az rest --method POST --uri '+URI+' --header Content-Type=application/json --body '+str(BODY)proc = subprocess.Popen(assignGlobalAdminCommand,cwd=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)while True:line = proc.stdout.readline()if line:thetext=ansi_escape.sub('', line.decode('utf-8').rstrip('\r|\n'))print(thetext)else:breakcallTheAPI()

Note that the python 3 requests module might be good to use here, but the supposedly working starting point in the other posting linked above uses the az rest cli command, so it seemed like a good idea to get a version of that working in Python 3 first before trying to put the same API call into the requests module.

POWERSHELL VERSION THAT WORKS:

Here are the successful results of running the az rest cli command using PowerShell from the answer to this other posting:

PS C:\path\to\directory> $Body = @{
>>     "roleDefinitionId" = "62e90394-69f5-4237-9190-012177145e10";
>>     "principalId"      = "valid-service-principal-object-id";
>>     "directoryScopeId" = "/"
>> } | ConvertTo-Json -Compress
PS C:\path\to\directory> $Body = $Body.Replace('"', '\"')
PS C:\path\to\directory> az rest -m post -u "https://graph.microsoft.com/beta/roleManagement/directory/roleAssignments" -b "$Body"
{"@odata.context": "https://graph.microsoft.com/beta/$metadata#roleManagement/directory/roleAssignments/$entity","directoryScopeId": "/","id": "long-alpha-numeric-hash-id","principalId": "valid-service-principal-object-id","principalOrganizationId": "valid-ad-tenant-id","resourceScope": "/","roleDefinitionId": "62e90394-69f5-4237-9190-012177145e10"
}
PS C:\path\to\directory>

But even though this PowerShell invocation of the az rest cli command works, the Python code above still gives an error. When we paste the string result of the PowerShell $Body = @{ "roleDefinitionId" = "62e90394-69f5-4237-9190-012177145e10"; "principalId" = "valid-service-principal-object-id"; "directoryScopeId" = "/" } | ConvertTo-Json -Compress command from above into the Python 3 code given above, we get the same error.

How can this working Powershell example be translated into Python 3 starting with the Python 3 code that is given above in the OP?

Answer

Assigning a Azure Active Directory role isn't possible with an ARM template.

The Azure documentation "Tenant deployments with ARM templates", clearly states that only "Azure role-based access control (Azure RBAC)" can be done using a tenant "roleAssignments" deployment.

For more information about Role Assignment deployments your can have a look over HERE.


The argument failure:

ERROR: unrecognized arguments: 'valid-service-principal-object-id', 'roleDefinitionId': '62e90394-69f5-4237-9190-012177145e10', 'directoryScopeId': '/'}

in above answer is caused by an invalid formatting of the required JSON object in the body. (I've added double quotes in the working example below)

The "https://graph.microsoft.com/beta/roleManagement/directory/r‌​oleAssignments" endpoint only works for user role assignments. The code below works with users, but fails for Applications with the error "Objects of type Application cannot be assigned to roles.":

ERROR: Bad Request({"error":{"code":"Request_BadRequest","message":"Objects of type Application cannot be assigned to roles.","innerError":{"date":"2023-04-24T09:25:57","request-id":"bdf05644-f7ff-49f5-b9a0-cdceac5f4242","client-request-id":"bdf05644-f7ff-49f5-b9a0-cdceac5f4242"}}})

(replace 'valid-user-principal-object-id' whith the object id of the user)

# coding: utf-8import subprocess
import reansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')def callTheAPI():URI="https://graph.microsoft.com/beta/roleManagement/directory/roleAssignments"BODY={"principalId": "valid-user-principal-object-id","roleDefinitionId": "62e90394-69f5-4237-9190-012177145e10","directoryScopeId": "/"}assignGlobalAdminCommand='az rest --method POST --uri '+URI+' --header Content-Type=application/json --body "'+str(BODY)+'"'proc = subprocess.Popen(assignGlobalAdminCommand,cwd=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)while True:line = proc.stdout.readline()if line:thetext=ansi_escape.sub('', line.decode('utf-8').rstrip('\r|\n'))print(thetext)else:breakcallTheAPI()

Here's a working Python script to assign the "Global Administrator" role for an App registration (service principal (SPN)) using the

https://graph.microsoft.com/v1.0/directoryRoles/roleTemplateId=62e90394-69f5-4237-9190-012177145e10/members/$ref

endpoint as documented over HERE:

(replace 'valid-service-principal-object-id' whith the object id of the user)

It's the object id of the service principal you need, not the application. You can find the service principal under Enterprise Applications in Azure portal's Azure AD blade. In its Properties you'll find the object id.

# coding: utf-8import subprocess
import reansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')def callTheAPI(): URI="https://graph.microsoft.com/v1.0/directoryRoles/roleTemplateId=62e90394-69f5-4237-9190-012177145e10/members/\$ref" BODY=  {"@odata.id": "https://graph.microsoft.com/v1.0/directoryObjects/valid-service-principal-object-id"}assignGlobalAdminCommand='az rest --method POST --uri '+URI+' --header Content-Type=application/json --body "'+str(BODY)+'"'proc = subprocess.Popen(assignGlobalAdminCommand,cwd=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
while True:
line = proc.stdout.readline()
if line:thetext=ansi_escape.sub('', line.decode('utf-8').rstrip('\r|\n'))print(thetext)
else:breakcallTheAPI()
https://en.xdnf.cn/q/118417.html

Related Q&A

Pandas calculating age from a date

I really need help with this one. My previous post was very bad and unclear - Im sorry - I wish I could delete but hopefully this one will be better.I need to calculate the age based off of a date (se…

Create new folders within multiple existing folders with python

I am looking for a way to create new folders within multiple existing folders. For example I have folders a,b,c.. etc and I want to create a new folder inside each of these existing folders and name th…

extract a column from text file

I have a a text file (huge amount of float numbers) with 25 columns. I want to extract column 14 and divide it by column 15. I could not extract this two columns. Codes:with open(sample for north.txt) …

kivy buildozer Compile Error pythonforandroid.toolchain

Compile platformCommand failed: /usr/bin/python3 -m pythonforandroid.toolchain create --dist_name=main -- bootstrap=sdl2 --requirements=kivy,python3 --arch armeabi- v7a --copy-libs --color=always --…

Django Error: No FlatPage matches the given query

SITE_ID = 1and (r, include(django.contrib.flatpages.urls)), is in urls.py.What can I do to fix this error? Django is still displaying this error - I have googled and I cant find anything.File urls.pyf…

I need to automate the filling of a HTML form in a web browser, how?

I am trying to build a python script that captures my screen (a website will be opened), finds the coordinates of a text entry box on the displayed web site, and then clicks in that text entry box. I a…

Page not found (404) at /user_signup in Django

Getting 404 error on signup and some more URLs. /login, /logout, /admin is working perfectly. Im making a web app that lets a user login, logout, search a flight, book a flight and view the bookings ma…

tensorflow:Your input ran out of data when using custom generator

I am using custom generator to pass my data. But i keep encountering an error which says i have run out of data and to use repeat() when passing the dataset. i am using plain generator therefore it is …

script to get the max from column based on other column values

I need a script to read in a csv file(orig.csv) and output a reformatted csv file(format.csv) The orig csv file will look like this: Time,Label,frame,slot,SSN,Board,BT,SRN,LabelFrame,SRNAME,LabelID,Int…

Selenium code is not able to scrape ofashion.com.cn

I was building a web scraper by using python selenium. The script scraped sites like amazon, stack overflow and flipcart but wasnt able to scrape ofashion. It is always returning me a blank .csv file.H…