Integrating Frame with Splunk using Frame Admin API

by David Horvath and Thang Nguyen

September 14, 2021 | min

The Nutanix Frame™ Platform records session and audit log information on what actions users and administrators are doing in the Frame Desktop-as-a-Service (DaaS). This session and audit log information is available for download from the Frame Console. Enterprises often want to combine this session and audit event data with information from other sources within their Security Information and Event Management (SIEM) solution in order to obtain a more comprehensive view of what is occurring in their enterprise. In this blog, we will demonstrate how Frame Admin API can be used within a PowerShell script to retrieve audit data from Frame and insert it into the Splunk® event manager, one of the more popular SIEM’s on the market.

Concept of Operations

The concept of operations is pretty simple:

  1. Using a Frame Admin API call to get the audit data from Frame
  2. Format that data to something easily digestible by Splunk
  3. Then put that data into Splunk using the Splunk HTTP Event Collector (HEC)

To implement this integration, you will need credentials for both Frame and Splunk. For Frame, the credentials can be obtained by following the Frame documentation "How to Provision API Credentials". For Splunk, getting a HEC token can be found at Splunk's documentation page.

For my example, we wanted to collect data at the Frame customer entity so we provisioned an API provider with the “Customer Auditor” role within Frame Console.

Figure 1. Customer Auditor Credentials

Once the API provider is created, we then create a new API key and secret.

Customer ID: You will also need the Frame Customer ID which can be found by going to the Frame Console for your Frame Customer entity; clicking on the three dots on the far right of your customer entity and choosing “Update”.

Figure 2. Select Update Customer

The web page you are taken to will have a URL that looks similar to the following:

The information between “customer” and “basic-info” (in this case 9096416d-c243-48de-950d-f40352231990 ) is the Customer ID.

You should now have the five values you need for the PowerShell script.

$clnt_id = "" $clnt_secret = ""
$cust_id = ""
$SplunkServer = ""
$HEC_Token = ""

The PowerShell Script

Once you have the 5 required values, you can plug those values into the following script:

#Requires -Version 5



    =======================================================================      Created on:   07/21/2021      Organization: Nutanix Frame      Filename:     GetAuditTrail.ps1     =======================================================================     .DESCRIPTION     Get the Audit Trail from an Frame Customer and put it into Splunk

   Frame Client credentials needed:         Client ID, Client Secret, and Customer ID     Splunk Configuration:         URL to Splunk Server, HTTP Event Collector (HEC) Token

#> Param (


$clnt_id = "<ClientID>" $clnt_secret = "<ClientSecret>" $cust_id = "<CustomerID>" $SplunkServer = "<SplunkServerURL>" $HEC_Token = "<HECToken>" $SourceType = "FrameAuditData"


  Generalized Function to create a signed Frame 'GET' API Call #>

function Get-FrameATCall {

    Param     (         [parameter(Mandatory = $true)]         [String]         $client_id,         [parameter(Mandatory = $true)]         [String]         $client_secret,         [parameter(Mandatory = $true)]         [String]         $api     )     try {

# Create a signature based on the Frame API credentials

        $timestamp = [Math]::Floor([decimal](Get-Date -UFormat "%s"))         $to_sign = "$($timestamp)$($client_id)"         $hmacsha = New-Object System.Security.Cryptography.HMACSHA256         $hmacsha.key = [Text.Encoding]::ASCII.GetBytes($client_secret)         $signature = $hmacsha.ComputeHash([Text.Encoding]::ASCII.GetBytes($to_sign))         $signature = [System.BitConverter]::ToString($signature).Replace('-', '').ToLower()

        # Set HTTP request headers         $headers = @{             'X-Frame-ClientId'  = $client_id             'X-Frame-Timestamp' = $timestamp             'X-Frame-Signature' = $signature             'Content-Type'      = 'application/json'         }

        # Set TLS1.2 (PS uses 1.0 by default) and make HTTP request         [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

        # Call the API and Process the return code         $response = Invoke-RestMethod -Method Get -Uri $api -Headers $headers     }

    catch {         # HTTP 401         if ($_.Exception.Response.StatusCode -eq "Unauthorized") {             Write-Error "Check your ClientId and ClientSecret - Unauthorized"         }         # HTTP 404         elseif ($_.Exception.Response.StatusCode -eq "NotFound") {             Write-Error "Check your ClientID - Not Found"         }         # HTTP 403         elseif ($_.Exception.Response.StatusCode -eq "BadRequest") {             Write-Error "Check your AccountId - BadRequest"         }

        # Unhandled exception         else {             Write-Error "Unknown error: $($_). For more info contact Nutanix Support"         }     }     # Return the Response from the API call     return $response  }


  Generalized Function to PUT a Single JSON element into a line that SPLUNK can parse #>

function Post-SplunkData {     Param

(         $JSONEvent     )

   try {         # Convert the time from the Frame Audit data to "EpochTime"         $EventTime = $([Int64](get-date $JSONEvent.inserted_at -UFormat %s))

        # Create a single row of Splunk parsable JSON         $row =

'{{"Time":"{0}","ID":"{1}","AccountID":"{2}","OrganizationID":" {3}","CustomerID":"{4}","Event":"{5}","Email":"{6}","FirstName":" {7}","LastName":"{8}","IdentityProvider":"{9}"}}' -f


       # Set HTTP request headers with the Splunk HEC token         $headers = @{             Authorization = "Splunk $HEC_Token"         }         # Create the request body including epoch time and event json         $body = '{             "time":' + $EventTime + ',             "sourcetype":"'+ $SourceType + '",             "event":' + $row + '         }'

        # Make the POST to Splunk and process the return code         $response = Invoke-RestMethod -Method Post -Uri $SplunkServer -Headers $headers -Body $body     }

    catch {        # HTTP 401         if ($_.Exception.Response.StatusCode -eq "Unauthorized") {

            Write-Error "No permission"

        }         # HTTP 404         elseif ($_.Exception.Response.StatusCode -eq "NotFound") {             Write-Error "Not Found"         }         # HTTP 403         elseif ($_.Exception.Response.StatusCode -eq "BadRequest") {             Write-Error "BadRequest. BodyText is $body."         }         # Unhandled exception         else {             Write-Error "Unknown error: $($_). Body text was $body."         }     }     # Return the response from the POST request     return $response  }

# Set a variable with the most recent Midnight UTC. The API call will get events

prior to this time.

$to_Date = Get-Date -Format "yyyy-MM-dd"

# If the "LastLogDate" environment variable does not exist. Get all log data upto the most recent Midnight UTC. If the variable is set. Get data # from that time until the most recent Midnight UTC

$from_Date = [Environment]::GetEnvironmentVariable('LastLogDate', 'Machine');

# If necessary, create it. if ($from_Date -eq $null) { $req_string = "" + $cust_id +"/audit-trails?to_date="+ $to_Date + "T00:00:00.000000Z" #Write-Output $req_string } else { $req_string = "" + $cust_id +"/audit-trails?from_date=" + $from_Date + "T00:00:00.000000Z&to_date="+ $to_Date + "T00:00:00.000000Z" }

# Call the Frame API $res = Get-FrameATCall -client_id $clnt_id -client_secret $clnt_secret -api $req_string # set the last log date environment variable [Environment]::SetEnvironmentVariable('LastLogDate', $to_Date, 'Machine');

# Process each event in the returned JSON into Splunk foreach ($i in $res) { Write-Output $i.inserted_at $postResponse = Post-SplunkData $i }

Download Link

For simplicity sake, we wanted a script that could be run without command arguments so we chose to set up the script to collect logs up to the most recent UTC Midnight and then store that date in a system environment variable. Subsequent executions only collect data from the previously set date until the most recent UTC midnight. This means that events that occurred after midnight UTC of the current day would not be sent to splunk until the script is run again.

This script can be run without any command line arguments and will do the following:

  1. Checks if the “LastLogDate” environment variable is set.
    1. If it is not set, the script will retrieve the Frame Audit data from the Customer entity creation date up until 00:00:00 UTC of the current day.
    2. If it is set, the script will collect the Frame Audit data from 00:00:00 of the “LastLogDate” until 00:00:00 UTC of the current day.
  2. Set the LastLogDate environment variable to the current day.
  3. Loop through the audit data creating a Splunk entry for each Frame audit log entry.
    1. For the Splunk entry, convert the “inserted_at” time to Epoch Time. This allows the entries to reflect the time they occurred in the Frame Platform.
    2. Map each Frame value to the field that will be used in Splunk. This provides an opportunity to rename values to something more appropriate for your enterprise if desired.
    3. Insert the entry into Splunk.

The first time through the script can take a bit of time especially if the Frame customer has been around for a while. Subsequent executions should be faster since less audit data will be retrieved. If the script is executed from the command line, it will show you the time of each event inserted into Splunk.


Below is a sample of what an audit entry looks like in the Splunk console.

Figure 3. Splunk Entries

In this example, we were able to search within Splunk for all activity associated with the email address in our Frame customer entity “Nutanix-Demo” once the PowerShell script was executed.


The use of the Frame Admin API to retrieve audit entries and insert them into Splunk is fairly easy to do, assuming you have authorization to access Frame and Splunk via the Frame Admin API and Splunk HEC. The PowerShell script can be run manually or be set up via a Windows scheduled process since the script keeps track of the last run date as a system environment variable and only collects the “newer” Frame audit data.

Additionally, you don’t have to collect the data at the Frame customer level. You can configure the script to use an API provider at the Organization or Account level if you wanted. You could also look up the Frame account name (using the Frame Admin API) and use the Frame account name in Splunk instead of the Frame Account ID since that might be more meaningful for the Splunk and Frame administrators.


You can also customize the row data or field names to meet your enterprise’s needs and you don’t have to push all of the data into Splunk. The PowerShell script is set up to be pretty flexible and you can replace the Splunk HEC Posts with calls to your SIEM’s API endpoints.

David Horvath is a Senior Solutions Architect with Nutanix Frame. He has been a part of the Frame team for almost 5 years and prior to that spent 20+ years consulting on various Information Technology projects with the US Intelligence Community.

Thang Nguyen is a Senior Security Engineer with Nutanix Frame. Prior to joining Nutanix, he worked as a Cyber Security consultant for the US Department of the Treasury.


© 2021 Nutanix, Inc. All rights reserved. Nutanix, the Nutanix logo and all Nutanix product, feature and service names mentioned herein are registered trademarks or trademarks of Nutanix, Inc. in the United States and other countries. Other brand names mentioned herein are for identification purposes only and may be the trademarks of their respective holder(s). This post may contain links to external websites that are not part of Nutanix does not control these sites and disclaims all responsibility for the content or accuracy of any external site. Our decision to link to an external site should not be considered an endorsement of any content on such a site. Certain information contained in this post may relate to or be based on studies, publications, surveys and other data obtained from third-party sources and our own internal estimates and research. While we believe these third-party studies, publications, surveys and other data are reliable as of the date of this post, they have not independently verified, and we make no representation as to the adequacy, fairness, accuracy, or completeness of any information obtained from third-party sources.

This post may contain express and implied forward-looking statements, which are not historical facts and are instead based on our current expectations, estimates and beliefs. The accuracy of such statements involves risks and uncertainties and depends upon future events, including those that may be beyond our control, and actual results may differ materially and adversely from those anticipated or implied by such statements. Any forward-looking statements included herein speak only as of the date hereof and, except as required by law, we assume no obligation to update or otherwise revise any of such forward-looking statements to reflect subsequent events or circumstances.