Forum Discussion

Cole_McDonald's avatar
Cole_McDonald
Icon for Professor rankProfessor
4 months ago

Change Auditing for Group Hierarchy

We had a couple of folders move on us (erroneous clicks by our users).  Tracking down where they came from was difficult.  I've written a solution using a configSource.  AppliesTo() targets a collector we've set aside for doing RestAPI queries with longer timeouts for scripting:

$company   = "<Your Company From the URL Here>"
$URLBase   = "https://$company.logicmonitor.com/santaba/rest"

$accessID  = "##ApiAccessID.key##"
$accessKey = "##ApiAccessKey.key##"

#region Initialization and Functions
#-------- The Functions ----------

function Send-Request {
    param (
        $cred               ,
        $URL                ,
        $accessid    = $null,
        $accesskey   = $null,
        $data        = $null,
        $version     = '3'  ,
        $httpVerb    = "GET"
    )
    if ( $accessId -eq $null) { exit 1 }
        
    <# Use TLS 1.2 #>
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    <# Get current time in milliseconds #>
    $epoch           = [Math]::Round(
        ( New-TimeSpan `
            -start (Get-Date -Date "1/1/1970") `
            -end (Get-Date).ToUniversalTime()).TotalMilliseconds
        )
    <# Concatenate Request Details #>
    $requestVars     = $httpVerb + $epoch + $data + $resourcePath
    
    <# Construct Signature #>
    $hmac            = New-Object System.Security.Cryptography.HMACSHA256
    $hmac.Key        = [Text.Encoding]::UTF8.GetBytes( $accessKey )
    $signatureBytes  = $hmac.ComputeHash( [Text.Encoding]::UTF8.GetBytes( $requestVars ) )
    $signatureHex    = [System.BitConverter]::ToString( $signatureBytes ) -replace '-'
    $signature       = [System.Convert]::ToBase64String( [System.Text.Encoding]::UTF8.GetBytes( $signatureHex.ToLower() ) )
    <# Construct Headers #>
    $auth            = 'LMv1 ' + $accessId + ':' + $signature + ':' + $epoch
    $headers         = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $headers.Add(      "Authorization", $auth              )
    $headers.Add(      "Content-Type" , 'application/json' )
    # uses version 2 of the API
    $headers.Add(      "X-version"    , $version           )
    
    <# Make Request #>
    $response        = Invoke-RestMethod  `
        -Uri             $URL             `
        -Method          $httpVerb        `
        -Body            $data            `
        -Header          $headers         `
        -erroraction     SilentlyContinue `
        -warningaction   SilentlyContinue
        
    Return $response
}
function Get-LMRestAPIObjectListing {
    param (
        $URLBase          ,
        $resourcePathRoot , # "/device/devices"
        $size = 1000      ,
        $accessKey        ,
        $accessId         ,
        $version = '2'
    )

    $output  = @()
    $looping = $true
    $counter = 0

    while ($looping) {
        #re-calc offset based on iteration
        $offset = $counter * $size
        $resourcePath    = $resourcePathRoot
        $queryParam      = "?size=$size&offset=$offset"
        $url             = $URLBase + $resourcePath + $queryParam
        # Make Request
        $response        = Send-Request `
            -accesskey    $accessKey    `
            -accessid     $accessId     `
            -URL          $url          `
            -version      $version
        if ( $response.items.count -eq $size ) {
            # Return set is full, more items to retrieve
            $output     += $response.items
            $counter++
        } elseif ( $response.items.count -gt 0 ) {
            # Return set is not full, store date, end loop
            $output     += $response.items
            $looping     = $false
        } else {
            # Return set is empty, no data to store, end loop
            $looping     = $false
        }
    }
    
    write-output $output
}
#endregion

#region Get Groups
$resourcePath         = "/device/groups"
$Groups               = Get-LMRestAPIObjectListing `
    -resourcePathRoot $resourcePath                `
    -accessKey        $accessKey                   `
    -accessId         $accessID                    `
    -URLBase          $URLBase
#endregion

$groups | sort id | select id, parentid, fullpath | format-table -autosize

 

  • Anonymous's avatar
    Anonymous

    We did something similar in Groovy except that we also output the non-obfuscated properties. That way we can also tell when someone makes a change to a group property breaking something.

  • mray's avatar
    mray
    Icon for LM Conqueror rankLM Conqueror

    This is super dope! Thoughts on publishing to the Exchange?

    • Cole_McDonald's avatar
      Cole_McDonald
      Icon for Professor rankProfessor

      I don't generally interface with the Exchange much at all.  Most of what I end up writing is part of our IP and I'm not allowed to share.  So I come here with the neat little ones I think would be useful for others to have.  I'm not averse to it, I just haven't really dug into the exchange much as it's traditionally had issues for us and how we have deployed/tuned things.