Send ASC Recommendations to Azure Resource Stakeholders
Published Mar 10 2020 05:58 AM 6,041 Views
Microsoft

Objectives:

  • Build a Logic App using ASC context to notify folks responsible for fixing security recommendations on Azure Resource with a ASC Deep Link
  • Understand how to use various REST APIs in Azure and Azure AD
  • How to choose filtering options in data

As we continue to explore ways to automate in the domain of security related tasks, we find ourselves wanting to send messages to stake holders in Azure. In many organizations Azure is consumed by many different teams to host and build applications on.

 

In a particular scenario Azure Security Center creates new recommendations when Azure Resources are configured insecurely and become unhealthy. A compliance or security expert must track down the team responsible for the offending alert and help them understand how to implement better security configuration in their Infrastructure as Code (IaC). This will help so that future deployments using IaC do not drive the security score further down causing more remediation efforts.

In both cases it would be nice to inform the folks responsible for the Azure resource. Some organizations utilize Azure tags, naming conventions, or resource groups to indicate separation and responsibilities.  What if there is no tag or the tag is incorrect, information for people responsible for systems can drift over time.

 

One way outside of notifying personnel rather than using Azure tags is using the role assignments on the resource itself to send the message to a wider audience  of Azure Resource Owners and Contributors that are responsible and can remediate the Security Recommendations.

If you want to just deploy the Azure Logic App to notify Azure Resource Owners and Contributors follow these quick steps.

 

  1. Deploy the Logic App
  2. Within the ResourceGroup Authorize the web connections
  3. Assign the Logic App MSI to RBAC 'Reader' role at the Azure Subscription level. Assign Logic App MSI to Azure AD - Directory Role 'Directory readers'
  4. Configure ASC Workflow Automation to the Logic App, filter on recommendations and unhealthy

If you are interested in the technical details in how the Logic App functions please continue reading below.

 

We will build this Logic App to work with Azure Security Center Workflow Automation capabilities. When a new recommendation in Azure Security Center is active due to an unhealthy resource we will email and notify the Azure Resource owners to this security misconfiguration.

 

To begin with we are going to generate a Managed Service Identity for the Logic App so that it can authorized to make certain calls against the Azure and Azure AD APIs.

 

After creating the logic app go to the Identity blade and under the System Assigned identity switch the status to on and click save.

 

2020-03-03_13-58-07.png

 

Once saved you will receive a ObjectID for the Logic App System Assigned MSI

 

2020-03-03_14-00-28.png

 

Go ahead and at the subscription level assign the built in Reader Role to the Logic App, when you choose the assign access to Logic App you will be presented with a list of Logic Apps that have MSI enabled

 

2020-03-03_14-03-09.png

 

We also will have a requirement for the API on Azure AD Graph, so we will also assign the Logic App MSI to a Azure AD Directory Role, in Azure AD go to Roles and Administrators and choose the directory readers role.

 

2020-03-03_14-07-47.png

 

Once in the role go to Add assignments and search the name of the Logic App, it will appear as a ServicePrincipal, choose and click add at the bottom to assign the logic App the AAD directory reader access.

 

2020-03-03_14-09-04.png

 

Go back to the Logic App UI Designer and we will start building the connectors and actions. We will start with a trigger for Azure Security Center – When an Azure Security Center Recommendation is created or triggered.

 

2020-03-03_13-49-30.png

 

Off the trigger we are going to invoke our first API to get the role assignments from the Azure resource to do this. We will create a HTTP action and use the GET method

 

GET https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments?api-version=2...

 

We are going to replace the {Scope} area of the API and pass in Azure Security Centers Resource Details ID which will contain the proper path to pass in. Logic Apps produce Dynamic Content from connectors and actions above. The content can change from each firing and is stored like a parameter with a value at runtime of the Logic App.

 

2020-03-03_13-52-25.png

 

Be sure to add additional parameters like the authentication and switch to the managed Identity context

 

2020-03-03_14-20-30.png

 

For the audience be sure to fill in the root of the API call you are making, in this case https://managemnt.azure.com

 

2020-03-03_14-22-55.png

 

Our next step is to initialize a variable for a Array that will be initially blank, more to come but since are outputs and even inputs for particular APIs require arrays we want to declare an empty one up front to be used later.

 

2020-03-03_14-25-11.png

 

We still have an array of objects outputted from the HTTP call asking for role assignments on the Azure Resource. Thos role assignments may include users or groups with reader or cost billing role assignments that would not be able to do much with the Azure Security Center recommendation. We will use Filter Array to reduce the number of objects so that there are only Owners and Contributors that have access to the Azure Resource.

 

Using the Data Operations – Filter Array action we will put in the HTTP Body Value which is an array of those role assignment objects to be filtered on.

 

2020-03-05_14-20-46.png

 

Within those objects under properties there is a field called RoleDefinitionId which contains a GUID to the actual role definition. We are going to filter on Role Definitions

 

Owner - 8e3af657-a8ff-443c-a75c-2fe8c4bcb635

Contributor - b24988ac-6180-42a0-ab88-20f7382dd24c

 

To do this we will switch to Edit in advanced mode and use the following expression calling dynamic content starting with a or to search for both Role Definition Ids.

 

@or(contains(item()?['properties']?['roleDefinitionId'], '8e3af657-a8ff-443c-a75c-2fe8c4bcb635'), contains(item()?['properties']?['roleDefinitionId'], 'b24988ac-6180-42a0-ab88-20f7382dd24c'))

 

2020-03-05_14-25-17.png

 

In this next step we are going to filter the array down creatively by running a for each loop and passing in only the outputted principalIDs into the array we declared above. The end result will be a array with only principalIDs we can pass into the Azure AD API later.

 

2020-03-05_14-27-22.png

 

Because we only see top level responses and not nested fields in the Dynamic Content we will use the Expression to pass the PrincipalID which is nested in each item’s properties field. Expressions allow us to do functional things on dynamic content or other content were passing in. In this case were expressing on each unique object item in the array from for each and obtaining the value of just a nested property field called principalId from each object.

 

items('For_each')?['properties']?['principalId']

 

2020-03-03_14-32-14.png

 

Now we have an array of principalIDs we can invoke the second API for Azure AD ObjectbyObjectIDs

 

In the next step we will make a HTTP call again to graph.windows.net using a POST method.

 

2020-03-03_14-38-11.png

 

For the Tenant ID seen here Logic Apps will let you declare global parameters that will stay static but will always be used. To do this in the logic app designer go to the top and press the parameters button and create a string parameter called TenantID with your AAD Tenant ID value

 

Picture1.png

 

In the body of the request we will use the simpleassignedarray variable that collected the filtered principalIDs previously and we will also use a optional parameter in the POST Body of the API here to filter on the types so we only receive users and security groups back.

 

Out output of this 2nd HTTP call will be just the user and group’s information against the Azure resource.

 

In our next step we are going to initialize another variable but this one will be a string variable of null, we will be using this null variable in a bit to pass in the email addresses.

 

2020-03-03_14-47-35.png

 

In the next step we will use Filter Array and filter out any returned objects of users and groups that are not mail enabled and if not to check otherMails property field for email addresses. We will use the HTTP Body Value array output and Edit in advanced mode again by using a expression to check if mail not equal to null or otherMails not equal to null then filter the array, basically passing out the null valued fields for mail or otherMails.

 

@or(not(equals(item()?['mail'], null)), not(equals(item()?['otherMails'], null)))

 

2020-03-05_14-57-50.png

 

In our next step we are going to use the filtered array of only mail enabled user and security group objects and pass in to append only the email address into the new string variable we declared a moment ago.

 

Using another for each loop we will use Dynamic Content for the filtered array of objects we are going to use a tree of conditional logic since two different fields may contain the email address.

 

We start by taking each object in the array and ask is mail not equal to null ? If true then append to string variable action – emailstring. If false then for each loop through the othersMail field, this value is an array of email addresses, so we have to use a For Each loop to examine the array and handle if the array is null. We check the current object and it’s otherMails array if not equal to null then each email address in otherMail array append into the email string variable. If false then move to the next object in the parent for each loop of user detail objects and start the condition check again.

 

2020-03-05_14-59-42.png

 

Here is a closer look at the Append to string variable action. We will use an expression to place each objects item nested email address property into the string. Also note the ; semi colon used at the end so that as we add more email addresses in string variable using the for each loop we also use the email separator.

 

2020-03-03_15-02-29.png

 

Our final step here is to create a Logic App connector to a Office 365 Mailbox and send an email. In the To field we will pass the appended emailstring variable we used above. We will also pass in some dynamic content and expressions for the body of the email message and subject so that those receiving the email will have a link to click on.

 

2020-03-05_15-08-48.png

 

To get the ASC deep link we will use an expression to concat some string and the ASC dynamic content of the link

 

concat('https://' , triggerBody()?['properties']?['links']?['azurePortal'])

 

2020-03-05_15-10-20.png

 

We now have a logic app that will trigger on ASC unhealthy resources and send an email to those responsible for the Azure Resource to investigate further with a deep link they can sue to read more.

 

Our last step is go to Azure Security Center – Workflow Automations and create a Workflow Automation for ASC Recommendations be sure to filter the recommendation state to only Unhealthy and optionally Healthy if you want to close the communication loop, REMOVE the filter for Not applicable

 

2020-03-05_15-15-28.png

 

The Azure Security Center workflow automation is available Here: 

 

Concluding the following pattern, you have built is powerful. Beyond notification the pattern could be used to pull down a role assignments report for daily view. Pull down a report and send an email of B2C Guest User Accounts with access to your Azure environments. You could inject the role assignments into a Log Analytics Custom Table AzureRoleAssignments_CL providing additional context to query against when using Azure Sentinel log searching or alerting via privileged accounts.

 

Special thanks to:

@yoavfrancis For collaborative input on Logic App and reviewing this post.

Version history
Last update:
‎Mar 23 2020 07:32 AM
Updated by: