Overview
One of the first things I found myself needing to do was interact with features of Netwrix via the API. Since our team uses SAML SSO for user login, the provided example code in the github repo did not really apply. That long with some lack of information on using application users for auth lead me to simply throwing some code together to get a valid bearer token for a Netwrix session via SAML SSO. I am working on expanding this to a PowerShell module for handling interactions with the Netwrix API, but figured I’d dump this sample here for anyone else running into similar hurdles as I was. Big shoutout to allynl93 on github for having saved me the work of dealing with the web browser support in Powershell. I have only tested this with Entra SSO, but in theory it should support other SAML IDP’s.
Description
See below for the script block, this does require Powershell 7. To use this script to get a valid SSO token for your user, simply populate your Netwrix URL in the NPSURL variable, and if you have multiple auth providers, enter your providers auth id in the AuthID variable. Then simply running the script will pop up a small browser window to handle the auth to your IDP, then shuttle that back to your Netwrix instance and get it authorized and a valid bearer token returned to you. I make no promises or guarantee that this will work for others, or for what versions of Netwrix this will work on as I have only used this on our own instance (4.2.1631.0) with Entra SAML SSO. Hopefully someone else can make use of this! If any devs check this out, I’d love to hear some feedback (and maybe even some information on this mysterious endpoint that seems to always fail with a 404 api/v1/token/GetLoginMethods).
#Requires -Version 7
#Retrieve SSO auth token for user
$NPSUrl = "https://netwrix"
$AuthID = ''
#Hero allynl93 for having already done the work of dealing with the web browser popup for specific auth flows!
#https://github.com/allynl93/getSAMLResponse-Interactive/blob/main/PowerShell%20New-SAMLInteractive%20Module/PS-SAML-Interactive.psm1
function New-SAMLInteractive{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string] $LoginIDP
)
Begin{
$RegEx = '(?i)name="SAMLResponse"(?: type="hidden")? value=\"(.*?)\"(?:.*)?\/>'
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Web
}
Process{
# create window for embedded browser
$form = New-Object Windows.Forms.Form
$form.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen;
$form.Width = 640
$form.Height = 700
$form.showIcon = $false
$form.TopMost = $true
$web = New-Object Windows.Forms.WebBrowser
$web.Size = $form.ClientSize
$web.Anchor = "Left,Top,Right,Bottom"
$web.ScriptErrorsSuppressed = $true
$form.Controls.Add($web)
$web.Navigate($LoginIDP)
$web.add_Navigating({
if ($web.DocumentText -match "SAMLResponse"){
$_.cancel = $true
if ($web.DocumentText -match $RegEx){
$form.Close()
$Script:SAMLResponse = $(($Matches[1] -replace '+', '+') -replace '=', '=')
}
}
})
# show browser window, waits for window to close
if([system.windows.forms.application]::run($form) -ne "OK") {
if ($null -ne $Script:SAMLResponse){
Write-Output $Script:SAMLResponse
$form.Close()
Remove-Variable -Name SAMLResponse -Scope Script -ErrorAction SilentlyContinue
}
Else{
throw "SAMLResponse not matched"
}
}
}
End{
$form.Dispose()
}
}
#Handles SAML SSO
#AuthID needs to be pulled from Netwrix so we know what authentication provider we should query
Function New-SAMLLogin {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)][string]$URI,
[Parameter(Mandatory=$false)][string]$AuthID
)
#Lets figure out what auth methods are available
$AuthMethods = [array](Invoke-RestMethod -URI "$URI/oidcSignin/Details")
#If we didn't pick an auth method we're taking the first option
if ([String]::IsNullOrEmpty($AuthID)) {
$AuthID = $AuthMethods[0].id
}
#Make sure we match a valid auth method
if ($AuthID -inotin $AuthMethods.id) {
throw "No auth methods matching $AuthID!"
}
#Lets get our initial SAML request
$WebSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$SAMLRequest = Invoke-RestMethod -uri "$URI/samlSignin/Request/$Authid" -WebSession $WebSession -ContentType "application/x-www-form-urlencoded"
if ([String]::IsNullOrEmpty($SAMLRequest)) {
#if we got a blank request something went wrong
throw [System.Net.WebException]::New("Got no data back from saml request endpoint! Check your URI and AuthID!")
}
#Pulls our the parameter from the URL
$SAMLRequestData = [System.Web.HttpUtility]::ParseQueryString(([uri]$SAMLRequest).query)['SAMLRequest']
$SAMLRequestData = [System.Convert]::FromBase64String($SAMLRequestData)
#Now lets decompress the request
$memoryStream = [System.IO.MemoryStream]::new($SAMLRequestData)
$deflateStream = [System.IO.Compression.DeflateStream]::new($memoryStream, [System.IO.Compression.CompressionMode]::Decompress)
$streamReader = [System.IO.StreamReader]::new($deflateStream, [System.Text.Encoding]::UTF8)
$DecompressedRequest = [xml]($streamReader.ReadToEnd())
#cleanup
$streamReader.Close()
$deflateStream.Close()
$memoryStream.Close()
#Finally we can get our request ID
$SAMLRequestID = $DecompressedRequest.AuthnRequest.id
#Pass this request to the IDP now
$SamlToken = New-SAMLInteractive -LoginIDP $SAMLRequest
Invoke-RestMethod -uri "$Uri/samlSigninCallback" -Method Post -WebSession $WebSession -Body @{SAMLResponse = $SamlToken} -ContentType "application/x-www-form-urlencoded" | Out-Null
$Token = Invoke-RestMethod -uri "$URI/samlSignin/$SamlRequestID"
return $Token
}
New-SAMLLogin -URI $NPSUrl -AuthID $AuthID