Resources

Arrow Image

Blog & News

Arrow Image

Integrating Frame with Splunk using Frame Admin API

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.

News & Blog

WRITTEN BY

David Horvath

Senior Solutions Architect

Thang Nguyen

Senior Security Engineer

September 14, 2021

TABLE OF CONTENT

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.

About the Author

Dizzion

Dizzion was founded in 2011 with a visionary mission to redefine the way the world works.

In an era of legacy Virtual Desktop Infrastructure (VDI), Dizzion set out to challenge the status quo by making it simple for all customers to transform their workspace experience. By building a powerful automation and services platform on top of the VMware stack, Dizzion delivered virtual desktops as a service before Desktop as a Service (DaaS) even existed.

David Horvath

Senior Solutions Architect

William Wong is the VP of Service Delivery for Dizzion, responsible for service delivery (professional and managed services), solutions architecture, and support. He works actively with customers to transform their business and operations leveraging DaaS in a hybrid and multi-cloud world. Before joining Dizzion as part of the Frame spinout from Nutanix, William was Head of Enterprise Solutions at Frame and following Nutanix's acquisition of Frame in 2018, Director of Solutions Architecture (Frame) at Nutanix. Prior to his work in DaaS, William led the development and adoption of innovative Internet software solutions and services, including Internet-based credit card and check processing and eCommerce platforms. William spent over 30 years at Cancer Commons, NetDeposit, Hewlett-Packard, VeriFone, and multiple Internet, payment, and eCommerce startups in executive management, program management, engineering management, and executive advisory positions. William received his B.S., M.S., and Ph.D. in Electrical Engineering from Stanford University.

More about the author
Thang Nguyen

Senior Security Engineer

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.

More about the author

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!