Automating Azure AD


https://pixabay.com/en/machine-factory-automation-1651014/

In the previous article, we explored how to interact (read / write) to an Azure AD tenant using Microsoft Graph API.

In the article before that, we looked at how to authenticate a user without using Azure AD web flow.

Those were motivated by a specific scenario:  replacing a LDAP server by Azure AD while migrating a SaaS application to Azure.

Now a SaaS application will typically have multiple tenants or instances.  Configuring Azure AD by hand, like any other Azure service, can be tedious and error prone.  Furthermore, once we’ve onboarded say 20 tenants, making a configuration change will be even more tedious and error prone.

This is why we’ll look at automation in this article.

We’ll look at how to automate the creation of Azure AD applications we used in the last two articles.  From there it’s pretty easy to generalize (aka exercise to the reader!).

Azure AD Tenant creation

From the get go, bad news, we can’t create the tenant by automation.

No API is exposed for that, we need to go through the portal.

Sorry.

Which PowerShell commands to use?

Automating Azure AD is a little confusing.  Too many options is like not enough.

The first approach should probably be to use the Azure PowerShell package like the rest of Azure services.  For instance, to create an application, we would use New-AzureRmADApplication.

The problem with that package for our scenario is that the Azure AD tenant isn’t attached to a subscription. This is typical for a SaaS model:  we have an Azure AD tenant to manage internal users on all subscriptions and then different tenants to manage external users.  Unfortunately, at the time of this writing, the Azure PowerShell package is tied around the Add-AzureRmAccount command to authenticate the user ; that command binds a subscription (or via the Select-AzureRmSubscription).  But in our case we do not have a subscription:  our Azure AD tenant isn’t managing a subscription.

The second approach would then be to use the MSOnline Module.  That’s centered around Azure AD, but it is slowly being deprecated for…

The third approach, Azure Active Directory V2 PowerShell module.  This is what we’re going to use.

I want to give a big shout to Chris Dituri for tapping the trail here.  His article Azure Active Directory: Creating Applications and SPNs with Powershell was instrumental to write this article.  As we’ll see, there are bunch of intricacies about the application permissions that aren’t documented and that Chris unraveled.

The first thing we’ll need to do is to install the PowerShell package.  Easy:

Install-Module AzureADPreview

If you read this from the future, this might have changed, so check out the documentation page for install instructions.

Connect-AzureAD

We need to connect to our tenant:

connect-azuread -TenantId bc7d0032…

You can see the documentation on the Connect-AzureAD command here.

Where do we take our tenant ID?

image

Now we can go and create applications.

Service-Client

Here we’ll replicate the applications we built by hand in the Authenticating to Azure AD non-interactively article.

Remember, those are two applications, a service and a client one.  The client one has permission to access the service one & let users sign in to it.  As we’ll see, giving those permissions are a little tricky.

Let’s start by the final PowerShell code:

#  Grab the Azure AD Service principal
$aad = (Get-AzureADServicePrincipal | `
    where {$_.ServicePrincipalNames.Contains("https://graph.windows.net")})[0]
#  Grab the User.Read permission
$userRead = $aad.Oauth2Permissions | ? {$_.Value -eq "User.Read"}

#  Resource Access User.Read + Sign in
$readUserAccess = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]@{
  ResourceAppId=$aad.AppId ;
  ResourceAccess=[Microsoft.Open.AzureAD.Model.ResourceAccess]@{
    Id = $userRead.Id ;
    Type = "Scope"}}

#  Create Service App
$svc = New-AzureADApplication -DisplayName "MyLegacyService" `
    -IdentifierUris "uri://mylegacyservice.com"
# Associate a Service Principal to the service Application 
$spSvc = New-AzureADServicePrincipal -AppId $svc.AppId
#  Grab the user-impersonation permission
$svcUserImpersonation = $spSvc.Oauth2Permissions | `
    ?{$_.Value -eq "user_impersonation"}
 
#  Resource Access 'Access' a service
$accessAccess = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]@{
  ResourceAppId=$svc.AppId ;
  ResourceAccess=[Microsoft.Open.AzureAD.Model.ResourceAccess]@{
    Id = $svcUserImpersonation.Id ;
    Type = "Scope"}}
#  Create Required Access 
$client = New-AzureADApplication -DisplayName "MyLegacyClient" `
  -PublicClient $true `
  -RequiredResourceAccess $readUserAccess, $accessAccess

As promised, there is ample amount of ceremony.  Let’s go through it.

  • Line 1:  we grab the service principal that has a https://graph.windows.net for a name ; you can check all the service principals living in your tenant with Get-AzureADServicePrincipal ; I have 15 with a clean tenant.  We’ll need the Graph one since we need to give access to it.
  • Line 5:  we grab the specific user read permission inside that service principal’s Oauth2Permissions collection.  Basically, service principals expose the permission that other apps can get with them.  We’re going to need the ID.  Lots of GUIDs in Azure AD.
  • Line 8:  we then construct a user-read RequiredResourceAccess object with that permission
  • Line 15:  we create our Legacy service app
  • Line 16:  we associate a service principal to that app
  • Line 20:  we grab the user impersonation permission of that service principal.  Same mechanism we used for the Graph API, just a different permission.
  • Line 24:  we build another RequiredResourceAccess object around that user impersonation permission.
  • Line 30:  we create our Legacy client app ; we attach both the user-read & user impersonation permission to it.

Grant Permissions

If we try to run the authentication piece of code we had in the article, we’ll first need to change the “clientID” value for $client.AppId (and make sure serviceUri has the value of “uri://mylegacyservice.com”).

Now if we run that, we’ll get an error along the line of

The user or administrator has not consented to use the application with ID ‘…’. Send an interactive authorization request for this user and resource.

What is that?

There is one manual step we have to take, that is to grant the permissions to the application.  In Azure AD, this must be performed by an admin and there are no API exposed for it.

We could sort of automate it with code via an authentication workflow (which is what the error message is suggesting to do), which I won’t do here.

Basically, an administrator (of the Azure AD tenant) needs to approve the use of the app.

We can also do it, still manually, via the portal as we did in the article.  But first, let’s throw the following command:

Get-AzureADOAuth2PermissionGrant

On an empty tenant, there should be nothing returned.  Unfortunately, there are no Add/New-AzureADOAuth2PermissionGrant at the time of this writing (this might have changed if you are from the future so make sure you check out the available commands).

So the manual step is, in the portal, to go in the MyLegacyClient App, select Required Permissions then click the Grant Permissions button.

image

Once we’ve done this we can run the same PowerShell command, i.e.

Get-AzureADOAuth2PermissionGrant

and have two entries now.

image

We see the two permissions we attached to MyLegacyClient.

We should now be able to run the authentication code.

Graph API App

Here we’ll replicate the application we created by hand in the Using Microsoft Graph API to interact with Azure AD article.

This is going to be quite similar, except we’re going to attach a client secret on the application so that we can authenticate against it.

#  Grab the Azure AD Service principal
$aad = (Get-AzureADServicePrincipal | `
    where {$_.ServicePrincipalNames.Contains("https://graph.windows.net")})[0]
#  Grab the User.Read permission
$userRead = $aad.Oauth2Permissions | ? {$_.Value -eq "User.Read"}
#  Grab the Directory.ReadWrite.All permission
$directoryWrite = $aad.Oauth2Permissions | `
  ? {$_.Value -eq "Directory.ReadWrite.All"}


#  Resource Access User.Read + Sign in & Directory.ReadWrite.All
$readWriteAccess = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]@{
  ResourceAppId=$aad.AppId ;
  ResourceAccess=[Microsoft.Open.AzureAD.Model.ResourceAccess]@{
    Id = $userRead.Id ;
    Type = "Scope"}, [Microsoft.Open.AzureAD.Model.ResourceAccess]@{
    Id = $directoryWrite.Id ;
    Type = "Role"}}

#  Create querying App
$queryApp = New-AzureADApplication -DisplayName "QueryingApp" `
    -IdentifierUris "uri://myqueryingapp.com" `
    -RequiredResourceAccess $readWriteAccess

#  Associate a Service Principal so it can login
$spQuery = New-AzureADServicePrincipal -AppId $queryApp.AppId

#  Create a key credential for the app valid from now
#  (-1 day, to accomodate client / service time difference)
#  till three months from now
$startDate = (Get-Date).AddDays(-1)
$endDate = $startDate.AddMonths(3)

$pwd = New-AzureADApplicationPasswordCredential -ObjectId $queryApp.ObjectId `
  -StartDate $startDate -EndDate $endDate `
  -CustomKeyIdentifier "MyCredentials"

You need to “grant permissions” for the new application before trying to authenticate against it.

Two big remarks on tiny bugs ; they might be fixed by the time you read this and they aren’t critical as they both have easy work around:

  1. The last command in the script, i.e. the password section, will fail with a “stream property was found in a JSON Light request payload. Stream properties are only supported in responses” if you execute the entire script in one go.  If you execute it separately, it doesn’t.  Beat me.
  2. This one took me 3 hours to realize, so use my wisdom:  DO NOT TRY TO AUTHENTICATE THE APP BEFORE GRANTING PERMISSIONS.  There seems to be some caching on the authentication service so if you do try to authenticate when you don’t have the permissions, you’ll keep receiving the same claims after even if you did grant the permissions.  Annoying, but easy to avoid once you know it.

An interesting aspect of the automation is that we have a much more fine grained control on the duration of the password than in the Portal (1 year, 2 years, infinite).  That allows us to implement a more aggressive rotation of secrets.

Summary

Automation with Azure AD, as with other services, helps reduce the effort to provision and the human errors.

There are two big manual steps that can’t be automated in Azure AD:

  • Azure AD tenant creation
  • Granting permissions on an application

That might change in the future, but for now, that limits the amount of automation you can do with human interactions.

Advertisements

One thought on “Automating Azure AD

  1. Pingback: Azure Weekly: Feb 6, 2017 – Build Azure

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s