Category Archives: AzureAD

Expanding Microsoft First Party Application Permissions in AzureAD

Connect-AzAccount and my own silent token function use the Microsoft built in client ID of “1950a258-227b-4e31-a9cf-717495945fc2”.

The resulting token has some openID scopes and most backend calls use RBAC, but I wanted to experiment by adding OAuth2 permissions and app roles to it so I can use the context/cached refresh token to also call other Microsoft API’s.

I discovered that this can be done by adding the client to your AzureAD as an SPN (Enterprise Application):

$spn = New-AzureADServicePrincipal -AppId "1950a258-227b-4e31-a9cf-717495945fc2" -DisplayName "Microsoft Azure PowerShell"

Since this is Microsoft owned app, you’ll actually see that show up in AzureAD:

Next, we can add the AuditLog.Read.All permission to the local instance of this app ($spn), this is a Graph permission, so we first need to get the resourceId of graph in our tenant:

$GraphServicePrincipal = Get-AzureADServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"

Then we prepare our post body for the Graph API to add the AuditLog.Read.All permission to Microsoft Azure PowerShell:

        $patchBody = @{
            "clientId"= $spn.ObjectId
            "consentType"= "AllPrincipals"
            "principalId"= $Null
            "resourceId"= $GraphServicePrincipal.ObjectId
            "scope"= "AuditLog.Read.All"
            "expiryTime" = "2022-05-05T09:00:00Z"
        }

Then we’ll grab a token for the Graph API:

$token =  get-azResourceTokenSilentlyWithoutModuleDependencies -userUPN myupn@lieben.nu

And call Graph to add the permission to our local instance of Microsoft Azure PowerShell:

Invoke-RestMethod -Method POST -body ($patchBody | convertto-json) -Uri "https://graph.microsoft.com/beta/oauth2PermissionGrants" -Headers @{"Authorization"="Bearer $token"} -ContentType "application/json"

Any future tokens you grab for graph.microsoft.com using 1950a258-227b-4e31-a9cf-717495945fc2 as clientId will now contain the AuditLog.Read.All scope as well;

You should also be able to add approles, but since (hopefully) only Microsoft has the client credentials, they won’t do much.

Inspired by a question PrzemyslawKlys asked me 🙂

Scheduled migration / group add script

Usually, I want to roll out new features gradually to my users.

I used to do this by creating a security group, assigning new policies/software/patches etc to it, and then telling the IT staff to add users to it in groups and check with them if things went well.

But why? Why is that manual step needed?

Exactly, it is not!

So attached script can be scheduled in an Azure Automation Account (with Managed Identity enabled!) to add an X amount of users from Group A to Group B. This script can be scheduled to e.g. run daily until all users have been moved. Multiple scheduled would also work in case you’re deploying multiple features.

Handling exceptions

When certain users need to be excluded, simply create an exclusion security group and exclude that from your policies. Exclusions take precedence in most systems.

Grouping users

You can group deployment by supplying the groupByProperty, e.g. by Country

The code

https://gitlab.com/Lieben/assortedFunctions/-/blob/master/add-batchToGroup.ps1

Required modules

  • Az.Accounts

Graph Permissions

You’ll have to give the Managed Identity of your automation account sufficient graph permissions. This cannot be done through the GUI, so I’ve added a snippet you can use here:

https://gitlab.com/Lieben/assortedFunctions/-/blob/master/add-roleToManagedIdentity.ps1

Additionals

You’ll probably want to communicate to your users in advance, it’d be fairly easy to generate a report in advance or to add email notifications to the script but that will require additional graph permissions.

Deploying a service principal to (CSP) child tenants

Cloud Solution Providers, or sometimes other types of Managed Service Providers often have to manage a large number of tenants. Ideally, they do their ‘Infrastructure As Code’.

Using various API’s to manage tenants is best done using a Service Principal instead of a user (MFA, lifecycle, etc).

Recently, I was tasked to provide a deployment method of a Service Principal (multi-tenant) to all child tenants of an MSP, including programmatically granting various Graph API permissions. The Graph endpoint for this (oauth2PermissionGrants) is still in Beta, but the other methods I wrote about in the past are not as reliable so we’re using the Beta endpoint.

The linked example script creates an SPN and grants AuditLog.Read.All. If you’re an MSP/CSP, you’ll probably want to capture the tenant ID’s you’re installing into, so you can easily administer these tenants centrally using your main multi-tenant SPN.

Moving forwards, you won’t need an admin user / service account in the tenants you manage anymore, at least for the API’s that support SPN’s.

https://gitlab.com/Lieben/assortedFunctions/-/blob/master/add-servicePrincipalToAllCSPChildTenants.ps1

Note: to completely remove module dependencies / login, check my independent token function.

Sensitive group protection

It is best practise in IT to secure access to resources with Groups.

Membership of a security group means access to whatever resources are secured by that group. Sometimes these groups are self-managed by an owner, sometimes centrally.

In all cases, fairly low privileged users, that are not global admins, can add users to these groups including themselves. Imagine that you have a group called ‘Global Admins’, and your helpdesk user assigns himself to that group. You’d like to know right?

With Privileged Access Groups in Azure AD (Preview) you can protect groups like these actively, but, this requires a P2 license and still lacks some customization features.

An alternative method is to use a simple alerting rule in MCAS (Microsoft Cloud App Security), where you set an alert when ‘someone’ joins a specific group, or if you want to do more than alerting you could also run an automation playbook.

Here’s how to protect a specific Azure AD or Office 365 group with MCAS:

  1. look up its GUID in AzureAD
  2. Create an Activity Policy in the MCAS console
  3. Specify the group GUID as ‘Activity object ID’ in the policy and the correct action type:

Using Azure Function MSI to connect to Azure SQL in Python

Since I had to spend a few hours figuring this out, and all examples/docs are wrong, here’s an example of how to use Python in an Azure Function to connect to an Azure PaaS database without credentials by utilizing the managed identity of the azure function app.

__init__.py:

import logging
import os
import pyodbc
import requests 
import struct
import sys
import azure.functions as func

resource_uri="https://database.windows.net/"
sql_server="XXXXXX.database.windows.net"
sql_database="primary"

def get_bearer_token(resource_uri):
    identity_endpoint = os.environ["IDENTITY_ENDPOINT"]
    identity_header = os.environ["IDENTITY_HEADER"]
    logging.info('identity_endpoint: {}'.format(identity_endpoint))
    logging.info('identity_header : {}'.format(identity_header))
    token_auth_uri = f"{identity_endpoint}?resource={resource_uri}&api-version=2017-09-01"
    head_msi = {'X-IDENTITY-HEADER':identity_header}
    resp = requests.get(token_auth_uri, headers=head_msi)
    access_token = resp.json()['access_token']
    logging.info('response received from token endpoint: {}'.format(access_token))
    return access_token  

def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Function Starting')
    try:
        access_token = get_bearer_token(resource_uri)
        accessToken = bytes(access_token, 'utf-8')
        exptoken = b""
        for i in accessToken:
                exptoken += bytes({i})
                exptoken += bytes(1)
        tokenstruct = struct.pack("=i", len(exptoken)) + exptoken  
        conn = pyodbc.connect("Driver={ODBC Driver 17 for SQL Server};Server=tcp:{},1433;Database={}".format(sql_server,sql_database), attrs_before = { 1256:bytearray(tokenstruct) })
        logging.info('connected to {} on {}'.format(sql_server,sql_database))
        cursor = conn.cursor()
        cursor.execute("select @@version")
        row = cursor.fetchall()
        logging.info('sql data: {}'.format(row[0])) 
        logging.info('finished')              
    except BaseException as error:
        logging.info('An exception occurred: {}'.format(error))   
    return func.HttpResponse("done!")

requirements.txt:

astroid==2.4.2
azure-functions==1.2.1
certifi==2020.6.20
chardet==3.0.4
colorama==0.4.3
idna==2.9
isort==4.3.21
lazy-object-proxy==1.4.3
mccabe==0.6.1
pylint==2.5.3
pyodbc==4.0.30
requests==2.24.0
six==1.15.0
toml==0.10.1
urllib3==1.25.9
wrapt==1.12.1