Integrating Frame with Splunk using Frame Admin API
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.
WRITTEN BY
TABLE OF CONTENT
Concept of Operations
The concept of operations is pretty simple:
- Use a Frame Admin API call to get the audit data from Frame
- Format that data to something easily digestible by Splunk
- 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.
- 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.
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:
- 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.
- Set the LastLogDate environment variable to the current day.
- 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.
- For the Splunk entry, convert the
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.
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.
Subscribe to our newsletter
Register for our newsletter now to unlock the full potential of Dizzion's Resource Library. Don't miss out on the latest industry insights – sign up today!
Dizzion values your privacy. By completing this form, you agree to the processing of your personal data in the manner indicated in the Dizzion Privacy Policy and consent to receive communications from Dizzion about our products, services, and events.