Azure Sentinel Sync with ServiceNow using REST API

One of the important SIEM use case is incident management and Azure Sentinel offers robust features that help the organizations manage the life cycle of security incidents and alerts.

Since ServiceNow is one of the most widely used tools in IT and is growing rapidly, organizations need to integrate ServiceNow with Azure for asset discovery, incident management and alert management as a central location especially if the organization has a multi cloud and/or hybrid cloud environment.

Recently I was tasked to design the ServiceNow integration with Azure Sentinel to ensure incidents happening in multiple client tenancies can be replicated into a single corporate ServiceNow environment. Without this integration it is not feasible for any operations team to be able to go to all the different tools and environments to monitor and manage incidents and alerts. The following Microsoft Article was presented as a starting point

Azure Sentinel Incident Bi-directional sync with ServiceNow – Microsoft Tech Community

While working with the article I realized it required creation of resources like API connections between Azure Sentinel, Logic App and ServiceNOW and there are some unique challenges with this approach. The first one being that the API connections need to accounts setup in Azure and Service NOW and API connections need to be manually authorized. Creation of logic apps and then setting each analytics rule from the sentinel to point to the playbook. This isn’t a big deal if we are doing everything in your environment via clickops, if we have only a handful sentinel analytics rules, you have enough team members willing to spend time doing clickops and if you have a single environment. Working at a CSP brings unique challenges such as getting things automated as much as possible, building the customer environments quickly, being able to monitor multiple environments simultaneously. The biggest issue I see is that there are multiple permissions that need to be setup. Firstly, we need to allocate permissions to a user to authorize API connections with ServiceNow and Azure Sentinel. Secondly, Create an account to be used with Sentinel API and the Service Now API. More users, credentials means more security overhead and chances of a breach.

The Microsoft Tech Community article creates resource connectivity as shown below

To make ServiceNow and Sentinel Integration easier we thought of using REST API and allow ServiceNow to directly interact with Azure Sentinel and Log analytics workspace. Fetching data from Log Analytics Workspace using Kusto query is fairly easy and very well documented. It can be used for fetching security incident and alert data as well as other information for building reports. The part that is not well documented as of now is the process to use REST API within ServiceNow. The connectivity will be as shown in the below diagram

Here is the fully functional Powershell code. I will explain later in this article how to do the same thing within ServiceNow. The Azure Application or the client id can be created to provide just enough read only access to pull data and do resource discovery from Azure. There is a resource discovery capability available out of the box for ServiceNow to discover resources in Azure. The same clientID and Secret used for the capability can be used for the below script. (Note: The Client ID’s, Secrets, tenant ID, Subscription ID, Workspace ID’s are all fake so do not test the code with these. Insert details for your own environment for testing)

#========================== Copy lines below this line  ==========================

#Azure Subscription/Tenant, Application and secret to fetch the data

        $subscriptionId = "2de9c9db-8929-4a51-a5e8-64a6330c6fb9"
        $tenantId       = "36446030-d1e1-43e7-9e9e-f885a754ae2f" 
        $clientID       = "2673fc27-aa16-4825-951f-51a82b4f5641"      
        $clientSecret   = "SYuT_o88e6V2.vDpf6.-6-BlfhmFc16~N6"  

    #Connecting to the log analytics workspace  using its global unique id
        $WorkspaceId = "0363abfe-1651-4edc-9096-72cb14fb79d3"

#Authorization URl to build an Auth token
$loginURL       = "https://login.microsoftonline.com/$TenantId/oauth2/token"
$resource       = "https://api.loganalytics.io"         
$body           = @{
                        grant_type    = "client_credentials";
                        resource      = $resource;
                        client_id     = $clientID;
                        client_secret = $clientSecret

#Authentication call
$oauth          = Invoke-RestMethod -Method Post -Uri $loginURL -Body $body

#Building header using the access token
$header = @{
                    Authorization = "$($oauth.token_type) $($oauth.access_token)"

$uri = "https://api.loganalytics.io/v1/workspaces/$WorkspaceId/query"

#A formatted kusto query that will fetch the data,
$bodyHash = @{ 
                query = 'SecurityIncident | where TimeGenerated > ago(1d)'

#We will send a body in Rest API call in JSON format hence the conversion         
$body =  $bodyHash | ConvertTo-Json

#Make Rest API call and store result in the variable
$result = Invoke-RestMethod -UseBasicParsing -Headers $header -Uri $uri -Method Post -Body $body -ContentType "application/json"

#Check the number of log entries received from REST API call

#Convert data to a tabular format that can be used for CSV export
$headerRow = $null
$headerRow = $result.tables.columns | Select-Object name
$columnsCount = $headerRow.Count

$logData = @()

foreach ($row in $result.tables.rows) {
    $data = New-Object PSObject

    for ($i = 0; $i -lt $columnsCount; $i++) {
        $data | Add-Member -membertype NoteProperty -name $headerRow[$i].name -value $row[$i]
    $logData += $data
    $data = $null

# Export to CSV
$csvPath = (Join-Path -path $env:TEMP -ChildPath "loganalyticsresult.csv")
$logData | Export-CSV -path $csvPath -NoTypeInformation

#Open file in Excel
Invoke-Item $csvPath

#========================== Copy lines above this line  ==========================

For ServiceNow to fetch the data ServiceNow works good with JavaScript. Even though Powershell can be used I thought it would be easier to translate the above code to JavaScript for the ServiceNow team to use and avoid having to make the ServiceNow team learn Powershell. It was a good exercise to translate Powershell code to JavaScript. Honestly, I didn’t write any JavaScript code in atleast last 16 years but it wasn’t too difficult. there are plenty of documents available for JavaScript objects and coding.

//Request oAuth Token

var oAuthClient = new GlideOAuthClient();
var params ={grant_type:"client_credentials", resource:"https://api.loganalytics.io", client_id:"2673fc27-aa16-4825-951f-51a82b4f5641", client_secret:"SYuT_o88e6V2.vDpf6.-6-BlfhmFc16~N6"};
var json = new JSON();
var text = json.encode(params);
var tokenResponse = oAuthClient.requestToken('Azure TEST', text);
var token = tokenResponse.getToken();

// define variables and set them in the rest call
var header = {Authorization: "Bearer " + token.getAccessToken()}
var requestBody = {"query":"SecurityIncident | where TimeGenerated > ago(1d)"}
var body_json = json.encode(requestBody);   
var uri = "https://api.loganalytics.io/v1/workspaces/0363abfe-1651-4edc-9096-72cb14fb79d3/query"

var r = new sn_ws.RESTMessageV2(); //Make rest call
r.setEndpoint(uri); //endpoint defined from var uri
r.setHttpMethod('POST'); // post method

r.setRequestHeader("Authorization", "Bearer " + token.getAccessToken()); //set auth header
r.setRequestBody(body_json); //set request body

var response = r.execute(); //execute rest call

var responseBody = response.getBody(); //capture the response
var httpResponseStatus = response.getStatusCode(); // capture the status code 
var httpResponseContentType = response.getHeader('Content-Type');
var parser = new JSONParser();
var parsed = {};
var httpResponseBody;

gs.log("http response status_code: " + httpResponseStatus); 
// Create an empty object that would contain the processed data
var finaldata = [];
//  if request is successful then parse the response body

if (httpResponseStatus == 200 && httpResponseContentType == 'application/json; charset=utf-8') {

    // Getting the data from the REST Call
    httpResponseBody = response.getBody();
    // Parse the Data into a Object that can be processed
    const obj = JSON.parse(httpResponseBody)
    // Create empty column array for use later
    var u_cols=[];
    // Loop through the columns and add them to the column array, we need this to build the final object structure
    for (var i = 0; i < obj.tables[0].columns.length; i++) {

    // Now we process the Rest Data Row elements
    for (var j = 0; j < obj.tables[0].rows.length; j++) {
       //Create a Temporary structure so that we can add a column and row value to it
       var temp = new Object()
       for (var k = 0; k < obj.tables[0].rows[j].length; k++) {
          // Build the object table with column values from each column array element and add to it the value from the row structure
          temp[u_cols[k]] = obj.tables[0].rows[j][k]

      //Push each object into the array object
   //Your final data is in the final data object and convert it to string to show. this is for demo only, you should be able to use the finaldata table as is

//Inspect the finaldata object, it would be empty if the call failed for there was no data found

If you are keen to play around with REST API running from ServiceNow against the Azure Log Analytics, Sentinel etc there is beautiful documentation available from Microsoft for REST API capabilities for Azure and Azure resources here and here. We can interact with Azure from ServiceNow and close Incidents through REST API when a ServiceNow ticket is closed. It is simple, it is fun and involves a lot of new things to learn. My current organization has some amazing ServiceNow and Azure devops engineers who were quick to jump to this opportunity to learn.

Here are some cool screenshots as an evidence that it works very well.

Reach out to me if you need any help or if you have any queries related with this article.