Context
Not so long ago, i had to come up with a way to create a custom email alert whenever a specific event triggered within Azure.
In this particular case, the event looked at any update on every Network Security Group held within the client Az subscription(s).
There is currently an already built-in alerting feature for most Azure resources, however this method is limited in 2 major ways :
- The content of the alert body is fixed
- The trigger condition is limited to what Azure let’s you select
In order to circumvent these limitations, Microsoft offers an other feature within Azure, known as Logic apps.
Logic apps provide a mean to integrate between different applications by running a set of operations once an input has been received.
In our case we want to bridge between the Azure monitoring service which is responsible for observing and logging all the events related to a specific resource, and a custom Webhook that will listen for any incoming request, before sending an email based off the informations included within that initial body request.
How does it work ?
So this is what the final product looked like :
Nothing better than a straight line !
The process of actually building the Logic app is as straightforward as it can get, abstracting away all the ad-hoc programming one would normally have to go though in order to connect such heterogeneous systems. Yey !
The first component is the trigger, this is what dictates when and how your Logic app will activate. In my case this is a simple HTTP trigger. A crucial step about this component however, was the definition of the schema. I’ve spent some time trying to look for the most ideal schema to use scrolling through Microsoft documentation, around this link : https://learn.microsoft.com/en-us/azure/azure-monitor/alerts/alerts-common-schema
My goal was really to find a dedicated NSG alert body request schema, which i would then use to extract the exact parameters i want like priority, destination, name of the rule etc. but there didn’t seem to be a specific schema for this, at least not from the documentation itself.
So I’ve decided to analyze the properties of an HTTP request body resulting from the monitoring of an NSG modifications in Azure, then i injected the properties i wanted inside the Administrative template schema, and it ended up looking something like this, i wish i kept track of this evolution to better showcase the differences :/ :
JSON HTTP body request schema
``` { "properties": { "data": { "properties": { "alertContext": { "properties": { "authorization": { "properties": { "action": { "type": "string" }, "scope": { "type": "string" } }, "type": "object" }, "caller": { "type": "string" }, "category": { "properties": { "localizedValue": { "type": "string" }, "value": { "type": "string" } }, "type": "object" }, "channels": { "type": "string" }, "claims": { "properties": { "_claim_names": { "type": "string" }, "_claim_sources": { "type": "string" }, "aio": { "type": "string" }, "appid": { "type": "string" }, "appidacr": { "type": "string" }, "aud": { "type": "string" }, "e_exp": { "type": "string" }, "exp": { "type": "string" }, "http://schemas.microsoft.com/2012/01/devicecontext/claims/identifier": { "type": "string" }, "http://schemas.microsoft.com/claims/authnclassreference": { "type": "string" }, "http://schemas.microsoft.com/claims/authnmethodsreferences": { "type": "string" }, "http://schemas.microsoft.com/identity/claims/objectidentifier": { "type": "string" }, "http://schemas.microsoft.com/identity/claims/scope": { "type": "string" }, "http://schemas.microsoft.com/identity/claims/tenantid": { "type": "string" }, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname": { "type": "string" }, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": { "type": "string" }, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": { "type": "string" }, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname": { "type": "string" }, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn": { "type": "string" }, "iat": { "type": "string" }, "ipaddr": { "type": "string" }, "iss": { "type": "string" }, "name": { "type": "string" }, "nbf": { "type": "string" }, "onprem_sid": { "type": "string" }, "puid": { "type": "string" }, "uti": { "type": "string" }, "ver": { "type": "string" } }, "type": "object" }, "correlationId": { "type": "string" }, "eventDataId": { "type": "string" }, "eventName": { "properties": { "localizedValue": { "type": "string" }, "value": { "type": "string" } }, "type": "object" }, "eventTimestamp": { "type": "string" }, "id": { "type": "string" }, "level": { "type": "string" }, "operationId": { "type": "string" }, "operationName": { "properties": { "localizedValue": { "type": "string" }, "value": { "type": "string" } }, "type": "object" }, "properties": { "properties": { "requestbody": { "type": "string" }, "responseBody": { "type": "string" }, "serviceRequestId": { "type": "string" }, "statusCode": { "type": "string" } }, "type": "object" }, "relatedEvents": { "type": "array" }, "resourceGroupName": { "type": "string" }, "resourceId": { "type": "string" }, "resourceProviderName": { "properties": { "localizedValue": { "type": "string" }, "value": { "type": "string" } }, "type": "object" }, "resourceType": { "properties": { "localizedValue": { "type": "string" }, "value": { "type": "string" } }, "type": "object" }, "status": { "properties": { "localizedValue": { "type": "string" }, "value": { "type": "string" } }, "type": "object" }, "subStatus": { "properties": { "localizedValue": { "type": "string" }, "value": { "type": "string" } }, "type": "object" }, "submissionTimestamp": { "type": "string" }, "subscriptionId": { "type": "string" } }, "type": "object" }, "customProperties": { "properties": { "Key1": { "type": "string" }, "Key2": { "type": "string" } }, "type": "object" }, "essentials": { "properties": { "alertContextVersion": { "type": "string" }, "alertId": { "type": "string" }, "alertRule": { "type": "string" }, "alertTargetIDs": { "items": { "type": "string" }, "type": "array" }, "configurationItems": { "items": { "type": "string" }, "type": "array" }, "description": { "type": "string" }, "essentialsVersion": { "type": "string" }, "firedDateTime": { "type": "string" }, "monitorCondition": { "type": "string" }, "monitoringService": { "type": "string" }, "originAlertId": { "type": "string" }, "resolvedDateTime": { "type": "string" }, "severity": { "type": "string" }, "signalType": { "type": "string" } }, "type": "object" } }, "type": "object" }, "schemaId": { "type": "string" } }, "type": "object" } ```Let’s look at the second component which provides a perfect example on how to utilize a body response parameter and apply a function over it :
And just like that we are now able to process the output of that function anywhere else on the chain which i do on the component 8 “Set variable”. Ok it’s embedded in a pile of HTML >_<”, but it’s really not all that complicated, you just need to find where you wanna insert the stuff :
One highlight of this particular architecture was the Javascript inclusion on the 5th component, as of the time i’m writing this, Logic apps did not allow any built-in component for the integration of other types of scripting languages such as Python or Powershell.
As for the code itself don’t ask me why it had too look like this … Javascript being javascript, sometimes things simply won’t work as they should which led to this weird chain of substring matching instead of a singular regex only to extract the NSG name, which wasn’t readily available from the body parameters either.
An other highlight of this job was to figure out a way to customize the default Microsoft alerting email template. For this, i just extracted the HTML body of a default alert, stripped it from any PII and unnecessary CSS / JS bloat due to the outlook encapsulation then i injected my parameters inside the HTML.
Due to logic app variable size limitations, i also had to split the HTML email body variable in two.
When it comes to variables in Logic app you need to go through a 2 step process :
- Initialize variable
- Set variable
Using a custom SMTP server
Initially this architecture was tested using the built-in outlookv2 component which requires the provisioning of an O365 account with a valid Exchange Online license. This requirement didn’t meet the client’s need, meaning I had to shift to a dedicated SMTP relay configuration.
Thankfully the Send Email component ended up working just fine, granted access to the information below :
- SMTP server adress
- SMTP port (usually 587)
- username
- password
You then wanna specify these info in your connection from the email component :
Obviously make sure that the account is also enabled to send SMTP requests from the server account management console.