Assign Graph API App Role to Azure Managed Identity
9K views
Feb 5, 2024
Learn how to assign Graph API Application Roles to a managed identity using PowerShell! Chapters: 00:00 Introduction 00:16 Enable Function App Managed Identity 02:03 Assign Graph API App Role using PowerShell 09:43 Azure Function Code Example 23:10 Summary
View Video Transcript
0:00
What's up, everyone? Today we're going to continue talking about the Microsoft Graph API
0:04
and how you can use it with managed identities. In this case, we're going to look at an example of an Azure function
0:11
My name is Jeff Brown. This is Jeff Brown Tech. Let's go ahead and get started. Up here on my screen right now, I have a function app named jbt-graph-api-demo
0:21
I'm not going to dive into exactly what a function app is. Just know it is a serverless option out in Azure
0:27
that allows you to run small pieces of code. It could be .NET, Python, Node.js. In this case, we're going to have a PowerShell example
0:35
But what I'm mainly interested in is we can enable a managed identity with this Azure resource
0:41
and then give that managed identity access to the Graph API to perform specific things
0:47
and then our application code can perform those actions. Out here in the function app, let's scroll down under Settings and go to Identity
0:56
And here we have an option of enabling a system-assigned managed identity or a user-assigned
1:01
I'm going to stick with the system-assigned. That way, it is associated with the resource
1:05
If I ever delete this resource, the managed identity associated with it will also be removed
1:11
So it's very simple to enable. We just select this to on and go ahead and save our settings
1:16
just letting you know it's going to register an enterprise application, and then we can grant it permissions. Let's go ahead and say yes
1:26
Okay, and that was successful. We now have a managed identity associated with our Azure function
1:32
You can see we have an object or principal ID here, and then we can also give it Azure role assignments
1:38
What I'm going to do is go ahead and copy this to my clipboard, this object ID
1:43
We'll switch over to another tab. Here I am out in intra-ID
1:47
and I'm just going to go ahead and search my tenant for that object ID
1:53
We can see we have an enterprise application now with the exact same name as the Azure function
1:59
We'll go ahead and select it. Then here we have some more information. We have the application ID. Again, we have the object ID
2:07
What I'm primarily interested in right now is we need to assign this enterprise application
2:12
or the managed identity for our Azure function. We need to give it some permissions out to the Graph API
2:18
Under security, if we click on permissions, we can see right now it does not have any permissions associated with it
2:25
Now, if you've ever worked with application registrations, you know we can go in there
2:29
and easily select different permissions that we need to give to our application registration
2:35
However, that's a little harder to do with enterprise applications. There's nowhere to do it inside the portal here. We have to do it out in Microsoft PowerShell
2:43
There's probably commands to do it inside of the Azure CLI also, but I'm just more familiar with PowerShell
2:49
so that's what I'm going to be using. Let's switch over to Visual Studio Code, where I have an example of using PowerShell
2:55
to grant this managed identity permissions inside of the Graph API. Here we are out in Visual Studio Code, and I have some PowerShell commands here
3:03
I've already ran the first one where I've connected out to Microsoft Graph
3:07
and I've put a specific scope here of directory read-write-all because I'm going to be making changes inside of my directory out in Azure
3:15
Now, the first thing we need to do is get the managed identity object that we just created
3:21
We just need to get its information and save it to a variable here
3:24
You can do that two different ways. The first example uses the object ID along with service principal ID parameter to find the managed identity
3:33
or you can do a filter based on the display name. I still have copied in my clipboard here the object ID, so let's go ahead and put that in here
3:43
I'll press F8, and that ran successfully. Let's just go ahead and double-check what it's pulled back here, make sure we've got the right one
3:53
We can see the display name there is dbt-graph-api-demo. That's the name of our managed identity, which reflects the name of our Azure function
4:01
Next, we need to get another service principal, and this one is going to be the Microsoft Graph Enterprise app object
4:08
Now, you can see I already have an app ID here that equals this specific GWT
4:13
This GWT here is going to be the same inside of every Azure tenant
4:17
This is not specific to my tenant, so this is hard-coded, and you don't need to worry about looking it up or anything
4:24
This will be the same for your tenant. I'll go ahead and select line 10 here and run it, and that was successful
4:32
Let's also go take a look at what we have in here. You can see the display name is Microsoft Graph
4:38
and that represents Microsoft Graph inside of our tenant. Now, we have our managed identity for our Azure function
4:46
and then we have the Microsoft Graph Enterprise app object. Let's go ahead and scroll down a little bit more
4:51
What we need to do next is, what permissions do we want to assign to the managed identity
4:58
In this case, we're going to be updating user profile information on user accounts
5:03
We have user read-write-all. We want to be able to read and write all the user profiles inside of our tenant
5:10
Let's go ahead and run this and save that to a variable
5:14
Let me try that again. Now, what we need to do is find an application role that has that same permission
5:21
so we can assign that application role to our managed identity. We're going to do that by looking at the app roles that are associated with our graph service principle
5:32
We're going to find one where the value of that app role equals the permission we want
5:36
and our allowed member types we were looking for an application type permission
5:42
The other type we would look at is delegated, but delegated acts on behalf of a signed-in user
5:47
In this case, we want a permission that works without a signed-in user inside of an application
5:51
That's why we're only looking at allowed member types containing application. To give you a better idea of what we're looking for
5:57
let's actually take a look at the app roles that are associated with the Microsoft Graph Enterprise object
6:03
I'm just going to highlight Graph SPN and look at the app roles property
6:08
You can see it gives a description of what these different app roles do
6:12
We can manage workforce integration. We can read-write virtual appointments. A lot of these bottom ones here are for Microsoft Teams
6:20
It allows the app to read user profiles without a signed-in user. That almost sounds pretty close to what we're looking for
6:25
Let's actually take a look at just one of these. I'm going to pipe it out to format listing
6:31
We're just going to take a look at the first one in the list
6:35
Again, we have allowed member types application. That's the type of one we're going to be searching for later in our filter
6:40
Look at the description. It allows the app to read access reviews, reviewers, decisions, settings in the organization without a signed-in user
6:46
That's great. But look down here at the value, accessreview.read.all. That's why we have our permission set to user read-write-all
6:55
That's the permission we're looking for. We're going to be filtering on that value and finding an application role that has that same permission
7:03
Hopefully you can see here we're going to search all of our application roles for graph API
7:07
We're going to find one where the value equals the type of permission that we want
7:11
Then we just want to make sure it's application type. Let's go ahead and highlight all of these lines here
7:21
Go ahead and execute it. Looks like we got a response. Let me go ahead and clear out the screen
7:26
Let's look at app role. I'm going to pipe that to format listing so we can just read it a little easier
7:33
Here we go. Let's look at the description. Allows the app to read and update user profiles without a signed-in user
7:39
Sounds exactly like what we're looking for. That matches our value, user.readwrite.all
7:45
This role here is exactly what we want to assign to our managed identity
7:50
Our function app can read and update user profiles. Now that we have our app role, we're going to create a hash table here that we're going to pass to our next command
8:01
The principal ID is our managed identity, the ID of it. Our resource ID is our graph service principal, the ID of it
8:09
Then we have our app role ID that we just found. We're going to reference its ID
8:15
Let me go ahead and execute this so we have that hash table saved
8:19
Then we're going to splat it onto our command here on line 26
8:24
We have to reference the service principal ID again and the managed identity
8:28
and then pass it our hash table in the body parameter. Go ahead and execute this
8:36
That looks like that was successful. Actually, let's switch back to our list here
8:43
Here is our managed identity. We just granted it some permissions inside a graph API
8:48
We'll go ahead and refresh this. There it is right there. We have Microsoft Graph
8:53
It can read and write user profiles. We just granted that out inside of PowerShell
9:01
If you ever need to remove permissions in here, you can go into Review Permissions
9:06
It gives you a couple of options. I want to control access to this application. Maybe it's suspicious, I want to investigate, or it's malicious and I'm compromised
9:14
This may not actually represent what you need, but there is commands here. If you choose one of these, it will show you how to remove all the permissions
9:22
associated with this enterprise app. Then you can get more granular and maybe just remove one or two
9:27
that you don't need inside the application anymore. Again, all this is done inside of Azure PowerShell
9:32
Hopefully, they incorporate something here inside the portal to where you can manage it that way
9:38
It would be so much easier, just like you see in application registrations. Let's go ahead and switch back to our Function app
9:44
Our Function app has a managed identity. That managed identity has permissions out to the Graph API
9:50
to read and update user profiles. Let's go ahead and start jumping into the code in order to do that
9:58
First thing I want to show you, though, is let's go to App Files
10:02
Let's take a look at some of the application files associated with this Function app
10:06
Just like when you use PowerShell on your local system, this Function app also has a profile that loads
10:13
every time the Function app is restarted. Let's change this dropdown here and go into Profile
10:20
What I want to show you here first is on line 14. Basically, it's checking to see if this Function app
10:27
in the environment variables has a managed identity secret or MSI, managed service instance or managed service identity
10:35
That's, I think, an old acronym for it. What it's going to do is automatically connect out to your Azure tenant
10:42
using the managed identity. That's why you just, on line 16, it just has dash identity
10:47
It's saying, hey, connect out to Azure using this Function app's managed identity
10:52
If you're using PowerShell inside your Function app and you're not going to be using a managed identity
10:56
or connecting out to Azure PowerShell, you can remove line 14 through 17, just comment it out or delete it
11:02
But in our case, we do need that, and I'll show you why inside of our code here in just a second
11:08
Just showing here that inside the profile, it's automatically going to connect out to our Azure tenant
11:14
using managed identity. Let's go back to Overview. I already have a Function created inside of this Function app
11:23
called Update User Profile. Let's go ahead and select it. Then we're going to go to Code and Test
11:32
Let's go take a look at the code inside of this Function. I've changed this a little bit from the default Function
11:38
that's created when you create a PowerShell 1. First, on line 7, I'm just outputting to the log stream
11:44
that the Function is processing a request, just basically showing that it's started
11:49
We have a couple of parameters coming in here on line 4, request trigger metadata
11:54
The request is incoming information that's passed when the Function is called
11:59
We'll see that in a minute here when we go to test this. The first thing on line 9 is we're going to be passing
12:05
into this Function the user principal name in the request body. We're going to save that to the variable UPN
12:13
Which specific profile property we want to update. Think about on user profiles, we have things like job title
12:19
or department, maybe office location, things like that. Then on line 11, we have a new value
12:25
That's the new value that we want to update for that property. So say department, we're going to update that
12:30
to something else. Then on line 13, we're just outputting to the log stream
12:35
exactly what it's going to do. We're going to update this user principal name or UPN
12:40
this profile property, and what new value we're going to set it to
12:46
Line 16 is where the automatic connection out to our Azure tenant inside the profile comes into play
12:52
We need a token to the Graph API in order to call the Graph API
12:59
We need that token that says what permissions we have. You may have seen this in other videos I did
13:05
if you've been following my series on Graph API. We can get that token a lot easier than what I did
13:12
in previous examples by using the get az-access-token command. We can only run that if we're automatically connected
13:20
out to our Azure tenant, which is what I just showed you earlier inside the profile script
13:26
It's connecting automatically to our Azure tenant using the managed identity. We put a resource URL in here to get out to
13:33
graph.microsoft.com, and I'm only pulling back the token that we get back when we call this
13:40
I don't need the rest of the information that's in there, so that's why it's in parentheses .token
13:45
and we've saved that to a variable here. Next we need to build our request header
13:51
This contains our access token and what content types we're expecting back or maybe even passing to it
13:58
where we're using JSON. Then we also need to build our request body
14:03
We're going to say what profile property we're going to update and its new value
14:09
We're passing that to the Graph API for a specific user. Then we're going to convert that hash table there
14:16
into JSON when we pass it. Finally, after all of that, we've got our token
14:22
we've got our request headers. With our access token, we're building the body
14:26
of our request, what we're trying to do when we call the Graph API
14:30
Now here on line 32, we're actually going to use invoke rest method
14:35
In this case, we're doing a patch because we're updating an existing object
14:39
We've got our URI. Now, noticing our URI contains our UPN variable
14:44
so that's the user we're going to be updating. We're passing in our headers and our body
14:49
The headers have our authorization token and the body says what we're wanting to update
14:54
for this user. Now, I could introduce some more error checking here
14:59
If this did fail to update, the function app would fail and it would show the result of that
15:06
However, in this case, we're just going to say successfully updated this user's profile property
15:12
to this new value. We'll save it to body. And down here on line 37, so on function apps
15:20
what we can do is it can take things in and then it can also push responses
15:26
So it's pushing back out the output binding of saying, you know, this worked, HTTP status okay
15:32
and the body message. All right, so that's what our function app is doing
15:38
We're getting a token and then we're calling the Graph API to update someone's user profile
15:45
So real quick, I'm going to change this. We're not using App Insights on this
15:49
Let's switch this over to file system logs and let's run a test of this
15:58
So you can see our HTTP method is going to be post. The key is just authorization to call this Azure function
16:05
We have our query and our headers. We don't need to add anything here. But here is our body
16:09
So this is what we're passing to this function app. And let me scroll back up here
16:14
This lines up with everything we see on lines 9, 10, and 11
16:18
We're passing at the UPN of the user account we want to update. The profile property we're going to update this user's department
16:24
and what the new value is is going to be IT. So it's actually, I always like to double check
16:30
and make sure that this is working as expected. So let's actually switch back out to here
16:36
We'll go back to intra ID. Let's search for my name. We've got my user account here
16:45
Let's go under properties. And we can see my current department is accounting
16:51
So that's not correct. We need to change that. So we have our body
16:56
Our new value is going to be IT. Now keep an eye right down here on our output window, the file stream
17:03
So let's go ahead and run this. Okay, so this tab switched over from input to output
17:11
And we can see the response we got back was successfully updated user
17:16
their department to IT. This matches up with line 34. And then that output actually happens in line 37 through 40
17:26
So that's the same message there. I want to correlate those two
17:31
So let's go ahead and close this. And let's take a look at our output log here
17:36
Do you see some red? So let's see what's going on there
17:40
So we can see right here this is where it's connecting out to our Azure tenant. That's happening in our profile
17:45
We see update user profiles processing a request. So we see that lines up with line 7 here
17:53
So this is our output telling us we're going to update Jeff, the department to IT
17:58
That lines up with line 13. Then we get an unauthorized. Let's see what's going on here
18:05
Okay, after spending a little bit of time troubleshooting this and trying to figure out what was going on
18:10
and why I was getting unauthorized, jumping out to PowerShell, trying to graph explore everything else
18:16
finally realized what was going on. So here on line 16 I'm saving the result of my token request to $token
18:25
But in my header here I was referencing a variable that does not have a token in it
18:31
So that's why it was coming back unauthorized. Perils of copy and paste
18:35
I probably had this as an example from a blog post or something like that
18:39
and just wasn't paying attention. So kudos to you if you saw that and were yelling at the computer screen
18:46
as to why it wasn't working before I spent the last half hour or so trying to figure it out
18:51
So let's go ahead and save our updated code here. I haven't tested this
18:56
This should work now that we're actually referencing the token that we're getting back
19:01
So let's go back to test and run. I'm going to clear this out right here
19:07
Let's go back to input. Let's change. I was testing with another user
19:13
So let's actually go back to my original user here. I changed the value to information technology
19:19
I was just trying some different things here, experimenting. So let's go ahead and run this
19:23
And hopefully now that we're using the correct variable reference this will actually work
19:31
Again, this is what I was mentioning earlier. Even though it was failing, our success message, our output here
19:36
showed it was successful. So that's why you probably want to put some try-catches in there
19:40
do some actual error handling in order to show if it actually failed
19:44
instead of just blasting out that it was successful. So let's close this
19:49
Hey, there's no red here. The update user file is processing a request showing we're updating
19:55
the property or the department to information technology. It succeeded. And no red text this time
20:02
So let's go back out to my user account. I always show the department was accounting
20:07
We will go ahead and refresh here. Back to properties. And looky there, information technology
20:16
It helps when you reference the correct things and what you're trying to do
20:20
in order to make it work. So good thing it was something small like that
20:25
I was going back out to the managed identity. I granted it more permissions because it kept coming back unauthorized
20:31
So I was just trying to figure out what was going on. All right, so now that that is working
20:38
hopefully this shows you how we use the managed identity for an Azure resource
20:45
We granted it Graph API permissions. We got an access token here in order to use that to call out to Graph API
20:52
all that fun stuff. You might be saying, there are native commands in PowerShell
20:57
to do these same things. That's absolutely true. This is just me using PowerShell to provide examples of how to do this
21:06
The same principles applies if you're using JavaScript, Java, Python. You will want to get a token and make a URL request like this
21:16
The last thing I want to cover, if you try to run this yourself
21:21
you may run into an issue coming back saying the getAzAccessToken is not a valid command
21:28
So inside of functions, if you're using commands from other PowerShell modules
21:33
you have to include those modules inside your Azure function. And I'll show you real quick how to add those
21:41
So if we go back to the function app, let's go into app files. We were here earlier looking at our profile
21:48
There is another file here called Requirements. And inside of this one is where you can specify the names
21:56
of PowerShell modules that are out in the PowerShell gallery and what version you want to include inside your function app
22:04
I don't want to download the entire AZ PowerShell module and all the different modules and everything that are included in it
22:11
So that specific command to get a token is inside the az.accounts module
22:18
The latest version is 2.something right now, so I just said 2.star to always give me the latest version
22:23
So that's included in here. I do have a blog post on how to add these into your function apps
22:30
I'll include that down in the comments. Once you include that in here, you have to go back to Overview
22:37
and either do a restart or a stop and start of your function app
22:41
because it will run the requirements script at that time and pull down the modules needed that you included in there
22:49
So just if you're following along and you run into that saying
22:53
that that command to get a token was not valid, it's included in another module and you have to include that module
22:59
inside your function app so it can download and use it. Pretty much like Azure Automation accounts, if you've ever used those
23:06
you can include modules in there. So that does it for this video. A little bit longer
23:12
Apologies for the small snafu there of the wrong reference, but I had to go back and fix that
23:18
But hopefully this helped you out in what you're doing and can give you some more tools on how to use function apps
23:24
managed identities, Graph API, all that fun stuff. Thank you for watching, and we'll see you next time
23:36
Microsoft Mechanics www.microsoft.com www.microsoft.com www.microsoft.com www.microsoft.com www.microsoft.com www.microsoft.com www.microsoft.com www.microsoft.com www.microsoft.com www.microsoft.com www.microsoft.com www.microsoft.com www.microsoft.com
#Computers & Electronics
#Programming