Skip to main content

Integrating Frame with Splunk using Frame Admin API

· 9 min read
David Horvath
Thang Nguyen

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. Use a Frame Admin API call to get the audit data from Frame
  2. Format that data to something easily digestible by Splunk
  3. 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

Figure 1. Customer Auditor Credentials
  • Name: A name for your API Provider
  • Roles: Customer Auditor
  • Entity: By default, select your Frame customer entity.

Once the API provider is created, you can obtain a new API key and secret by going to the three dots on the far right for your API provider and choosing Manage.

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 the Update button.

Figure 2. Select Update Customer

Figure 2. Select Update Customer

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

https://console.nutanix.com/frame/customer/9096416d-c243-48de-950d-f40352231990/basic-info

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
<#
.NOTES
=======================================================================
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 Frame 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 $JSONEvent.inserted_at,$JSONEvent.id,$JSONEvent.account_id,$JSONEvent.organization_id,$JSONEvent.customer_id,$JSONEvent.kind,$JSONEvent.user_email,$JSONEvent.user_first_name,$JSONEvent.user_last_name,$JSONEvent.user_idp

# 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 = "https://api.console.nutanix.com/v1/customers/" + $cust_id +"/audit-trails?to_date="+ $to_Date + "T00:00:00.000000Z"
#Write-Output $req_string
}
else
{
$req_string = "https://api.console.nutanix.com/v1/customers/" + $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 evironment 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
}

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.
    • 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.
    • 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.
    • 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.
    • 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.
    • 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.

Results

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

Figure 3. Splunk Entries

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.

Conclusion

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.

Authors

David Horvath

More content created by

David Horvath
David Horvath is a senior solutions architect with Frame. He has been a part of the Frame team for almost five years and prior to that spent 20 years consulting on various information technology projects with the U.S. intelligence community.
Thang Nguyen

More content created by

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

© 2024 Dizzion, Inc. All rights reserved. Frame, the Frame logo and all Dizzio product, feature and service names mentioned herein are registered trademarks of Dizzion, Inc. in the United States and other countries. All 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 Dizzion. Dizzion 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.