7 min read

Azure Lighthouse - How to detect when access is delegated to you and automate your customer onboarding process

Azure Lighthouse - How to detect when access is delegated to you and automate your customer onboarding process

 

You've got your Delegated Resource Management figured out and you're onboarding customers to your Managed Services Practices using Azure Lighthouse technologies. Whether you're doing this by publishing your Managed Services Offering to the Azure Marketplace or by sharing your template with all the authorizations configured - how do you know when someone has delegated access to you?

If you're going with the marketplace offering then chances are you have configured Lead Management and you will get triggered when someone has purchased your offer. But, this does not indicate whether the deployment has succeeded. If you have shared the template with partners, customers or your peers then the first indication that you have received delegated access is by actually checking what Azure tenants and subscriptions you can access (and figure out which are new).

The Challenge

At this moment Azure does not provide a notification when someone has delegated access to you. This can be challenging when your managed services are really taking off and customers are onboarding at scale. You would have to manually inspect all the access and configure your automation to run against those newly received endpoints.

What you really want to do is automatically perform actions on the target subscriptions once you've received access. For example to run an inventory, deploy your monitoring configuration or configure Azure Policy.

Use cases

Let's look at some use cases:

  • You have a Managed Services Offering where you provide monitoring and alerting for customer subscriptions
  • You provide a "Health check" on the environment and require delegated access to perform the assessment
  • You want to know what's running when someone onboards onto your managed services practice
  • You want to inform your peers as soon as possible when a customer provides you with delegated access
  • You want to be (and need to be!) aware of the access you have

In any of the use cases, if you have a large amount of customer adds in a short window, you definitely don't want to burden someone with manually performing any of those tasks manually (and it doesn't scale well).

Automating the process

For now we have to create some kind of automation to detect newly delegated access. Once we have that detection configured we can perform virtually any kind of automated action.

I went about this by using a combination of Azure PowerShell Functions, Table Storage and a Logic App. As an example I went with the scenario where I want to run an inventory on a subscription as soon as we have received delegated access.

On a high level the solution does the following:

  1. Logic App to orchestrate the process (runs every 15 minutes)
  2. Azure Function App with Managed Service Identity configured
    - The managed Service Identity has access to the subscription the components are hosted in (Your MSP Tenant)
    - The objectID of the Managed Service Identity is provided delegated access through the ARM templates or Managed Services Offering
  3. Azure Function "compareSubscriptions"
    - Requests the subscriptions it has access to using the Manged Service Identity
    - Compares the subscriptionIds to the ones already stored in the table storage
    - Returns subscriptionIds that are not already stored in the table storage back to the Logic App
  4. Azure Function "addSubscription"
    - Adds a new subscriptionId to the table storage and sets the "Onboarded" property to "true"
  5. Azure Function "getSubscriptionContents"
    - Runs automation against the new subscriptionIds provided as returned by "compareSubscriptions"
  6. Results are stored in Azure File Storage

 

The Technology

As the above illustrates, I'm using different components here to get the job done. The Logic App is the orchestrator of the process as I wanted the different functions to work independently from each other which allows me to re-use them for other automation purposes in the future.

The Azure Functions
Firstly, the Azure Function App needs be configured to use Managed Service Identity (https://docs.microsoft.com/en-us/azure/app-service/overview-managed-identity). Secondly, the ObjectId of the Managed Service Identity needs to be configured in the Managed Services (delegated resource management) template or Marketplace offering and receive the right level of access you require for your automation. For now, I went with the Role Definition of "Reader" as I'm just requesting a list of resources (How to: https://docs.microsoft.com/en-us/azure/lighthouse/how-to/onboard-customer).

Not that for each function used here, you still need to implement your preferred form of authentication.

 

TLDR; Code for functions can be found at: https://github.com/whaakman/functions-onboard-lighthouse-customers

The first two Azure Functions are pretty similar as they both use the AzTable PowerShell Module to interact with the table storage. The big difference is that the "compareSubscriptions" requests the subscriptions from the Table Storage and Compares them to the result of "Get-AzSubscription" whereas the "addSubscription" function takes a "subscriptionId" as input and adds that to the Table.

Comparing the subscriptions

$storageSubscription = Get-AzSubscription -SubscriptionId "<SubscriptionID StorageAccount>"
Set-AzContext -Subscription $storageSubscription

# Get all subscriptions
$subscriptions = Get-AzSubscription

# Table storage details
# Edit when required
$resourceGroup = "rg-Lighthouse" 
$storageAccountName = "adrmsubscriptions"
$tableName = "subscriptions"
$storageAccount = Get-AzStorageAccount -ResourceGroupName $resourceGroup -Name $storageAccountName
$ctx = $storageAccount.Context
$cloudTable = (Get-AzStorageTable –Name $tableName –Context $ctx).CloudTable
$partitionKey1 = "partition1"

# Get current subscriptions stored in Table
$currentSubscriptions = Get-AzTableRow -table $cloudTable

if ($currentSubscriptions) { 
# Compare stored subscriptions with current access and store differences
$comparison = Compare-Object -ReferenceObject $currentSubscriptions.RowKey -DifferenceObject $subscriptions.Id

# Only returns first result. Next subscription will be added on next runtime of Logic App 
# Temporary situation to prevent bulk onboarding

$result = $comparison[0].InputObject

}
else
{
    $result = "No subscriptions found in table, adding (context)MSP Tenant"
    $subscriptionId = (get-azcontext).subscription.id
    Add-AzTableRow `
    -table $cloudTable `
    -partitionKey $partitionKey1 `
    -rowKey ("$subscriptionId") -property @{"Onboarded"="False"}
}

As you can see, I'm only returning the first "subscriptionId"  through "$result = $comparison[0].InputObject" You can just as easily return everything and configure your Logic App with a For Each loop to onboard in bulk, but for now I'd like to be careful and have control over what happens.

Adding the subscription
"addSubscription" is similar to the comparison function but instead of comparing, it adds the "subscriptionId" to the table storage.

$subscriptionId = $Request.Query.subscriptionId
if (-not $subscriptionId) {
    $subscriptionId = $Request.Body.subscriptionId
}

# Change to the subscriptionID your Managed Service Identity has permissions too
# (Storage account should be located in this subscription as well)
$storageSubscription = Get-AzSubscription -SubscriptionId "<SubscriptionID StorageAccount>"
Set-AzContext -Subscription $storageSubscription


# Table storage details
$resourceGroup = "rg-Lighthouse"
$storageAccountName = "adrmsubscriptions"
$tableName = "subscriptions"
$storageAccount = Get-AzStorageAccount -ResourceGroupName $resourceGroup -Name $storageAccountName
$ctx = $storageAccount.Context
$cloudTable = (Get-AzStorageTable –Name $tableName –Context $ctx).CloudTable
$partitionKey1 = "partition1"

# Add Subscription To Table Storage
Add-AzTableRow `
-table $cloudTable `
-partitionKey $partitionKey1 `
-rowKey ("$subscriptionId") -property @{"Onboarded"="true"}

This could've just as easily been a part of the previous function but that would imply that all newly detected subscriptions are always added to the Table Storage. At some point in time I would like to build some logic that determines which subscriptions can be added.  

Running the inventory
Last but not least, we're running the inventory by triggering "getSubscriptionContents" and providing the "subscriptionId" in the body of the request. The function is setting the "AzContext" to that of the "subscriptionId" as we have already determined it has the appropriate access.

$subscriptionId = $Request.Query.subscriptionId
if (-not $subscriptionId) {
    $subscriptionId = $Request.Body.subscriptionId
}

Set-AzContext -SubscriptionId $subscriptionId   


$resources = get-azresource
$resources = ($resources.ResourceType |group)
$result = $resources |ConvertTo-Json

The results are returned tot he Logic App which will eventually create an HTML table and store it.

Logic App
The Logic App consists of a pretty straight forward workflow. The first step is to invoke the "compareSubscriptions" Function which returns one new subscription. As mentioned before, I limited this to one subscription to prevent automating in bulk. If multiple subscriptions are added within the 15 minute window, the next subscription will be added on the next run of the Logic App. If you're onboarding multiple customers per 15 minutes (good job!), then adjust the timing of the Logic App or trigger it manually.

As a second step it catches when no new subscriptions are detected and terminates the Logic App.

 

When a new subscription is returned we'll add it to the Table Storage using the "addSubscription" Function. Right now we have the "detection" part figured out and automated. Anything after this step is up to you.

To illustrate the concept, I've configured an additional step and Function to retrieve a list of all the resources from the new subscription and return the Resource Types and the occurrences (count) of the resource types. So.. pretty much a basic inventory. As a last step I've stored the contents to an HTML table and stored the file on Azure File Storage.

     

Result:

Wrap-up

As illustrated it is not that difficult to configure some kind of automation to automate your onboarding process. Once Azure provides a notification when delegation is configured, comparing the subscriptions to trigger automation is no longer necessary but, a large bit of a code can still be used for your automation process. If you're progressing with Azure Lighthouse and Delegated Resource Management then this is definitely worth looking into and will make your life easier and not burden you or your peers with manual tasks to onboard customers.