Public/oauth2.ps1
function Request-FalconToken { <# .SYNOPSIS Request an OAuth2 access token .DESCRIPTION If successful, your credentials ('ClientId', 'ClientSecret', 'MemberCid' and 'Cloud' or 'Hostname') and token are cached for re-use. If an active OAuth2 access token is due to expire in less than 60 seconds, a new token will automatically be requested using your cached credentials. The 'Collector' parameter allows for the submission of a [System.Collections.Hashtable] object containing the parameters included with a 'Register-FalconEventCollector' command ('Path', 'Token' and 'Enabled') in order to log an initial OAuth2 access token request. .PARAMETER ClientId OAuth2 client identifier .PARAMETER ClientSecret OAuth2 client secret .PARAMETER Cloud CrowdStrike cloud [default: 'us-1'] .PARAMETER Hostname CrowdStrike API hostname .PARAMETER MemberCid Member CID, used when authenticating within a multi-CID environment ('Falcon Flight Control') .PARAMETER Collector A hashtable containing 'Path', 'Token' and 'Enabled' properties for 'Register-FalconEventCollector' .LINK https://github.com/crowdstrike/psfalcon/wiki/Request-FalconToken #> [CmdletBinding(DefaultParameterSetName='Hostname',SupportsShouldProcess)] param( [Parameter(ParameterSetName='Cloud',ValueFromPipelineByPropertyName,Position=1)] [Parameter(ParameterSetName='Hostname',ValueFromPipelineByPropertyName,Position=1)] [Alias('client_id')] [ValidatePattern('^[a-fA-F0-9]{32}$')] [string]$ClientId, [Parameter(ParameterSetName='Cloud',ValueFromPipelineByPropertyName,Position=2)] [Parameter(ParameterSetName='Hostname',ValueFromPipelineByPropertyName,Position=2)] [Alias('client_secret')] [ValidatePattern('^\w{40}$')] [string]$ClientSecret, [Parameter(ParameterSetName='Cloud',ValueFromPipelineByPropertyName,Position=3)] [ValidateSet('eu-1','us-gov-1','us-1','us-2',IgnoreCase=$false)] [string]$Cloud, [Parameter(ParameterSetName='Hostname',ValueFromPipelineByPropertyName,Position=3)] [ValidateSet('https://api.crowdstrike.com','https://api.us-2.crowdstrike.com', 'https://api.laggar.gcw.crowdstrike.com','https://api.eu-1.crowdstrike.com',IgnoreCase=$false)] [string]$Hostname, [Parameter(ParameterSetName='Cloud',ValueFromPipelineByPropertyName,Position=4)] [Parameter(ParameterSetName='Hostname',ValueFromPipelineByPropertyName,Position=4)] [Alias('cid','member_cid')] [ValidatePattern('^[a-fA-F0-9]{32}(-\w{2})?$')] [string]$MemberCid, [Parameter(ParameterSetName='Cloud',ValueFromPipelineByPropertyName,Position=5)] [Parameter(ParameterSetName='Hostname',ValueFromPipelineByPropertyName,Position=5)] [ValidateScript({ @($_.Keys).foreach{ if ($_ -notmatch '^(Enable|Token|Uri)$') { throw "Unexpected key in 'Collector' object. ['$_']" } } foreach ($Key in @('Token','Uri')) { if ($_.Keys -notcontains $Key) { throw "'Collector' requires '$Key'." } else { $true } } })] [System.Collections.Hashtable]$Collector ) begin { if ($PSBoundParameters.MemberCid -match '^[a-fA-F0-9]{32}-\w{2}$'){ $PSBoundParameters.MemberCid = $PSBoundParameters.MemberCid.Split('-')[0] } function Get-ApiCredential ($Inputs) { $Output = @{} @('ClientId','ClientSecret','Hostname','MemberCid').foreach{ # Use input before existing ApiClient value $Value = if ($Inputs.$_) { $Inputs.$_ } elseif ($null -ne $Script:Falcon.$_) { $Script:Falcon.$_ } if (!$Value -and $_ -match '^(ClientId|ClientSecret)$') { # Prompt for ClientId/ClientSecret and validate input $Value = Read-Host $_ $BaseError = 'Cannot validate argument on parameter "{0}". The argument "{1}" does not ' + 'match the "{2}" pattern. Supply an argument that matches "{2}" and try the command again.' $ValidPattern = if ($_ -eq 'ClientId') { '^[a-fA-F0-9]{32}$' } else { '^\w{40}$' } if ($Value -notmatch $ValidPattern) { $InvalidValue = $BaseError -f $_,$Value,$ValidPattern throw $InvalidValue } } elseif (!$Value -and $_ -eq 'Hostname') { # Default to 'us-1' cloud $Value = 'https://api.crowdstrike.com' } if ($Value) { $Output.Add($_,$Value) } } return $Output } } process { if ($PSBoundParameters.Cloud) { # Convert 'Cloud' to 'Hostname' $Value = switch ($PSBoundParameters.Cloud) { 'eu-1' { 'https://api.eu-1.crowdstrike.com' } 'us-gov-1' { 'https://api.laggar.gcw.crowdstrike.com' } 'us-1' { 'https://api.crowdstrike.com' } 'us-2' { 'https://api.us-2.crowdstrike.com' } } $PSBoundParameters['Hostname'] = $Value [void]$PSBoundParameters.Remove('Cloud') } if (!$Script:Falcon) { try { # Initiate ApiClient, set SslProtocol and UserAgent $Script:Falcon = Get-ApiCredential $PSBoundParameters $Script:Falcon.Add('Api',[ApiClient]::New()) if ($Script:Falcon.Api) { try { # Set TLS 1.2 for [System.Net.Http.HttpClientHandler] $Script:Falcon.Api.Handler.SslProtocols = 'Tls12' Write-Verbose "[Request-FalconToken] Set TLS 1.2 via [System.Net.Http.HttpClientHandler]" } catch { if ([Net.ServicePointManager]::SecurityProtocol -notmatch 'Tls12') { # Set TLS 1.2 for PowerShell session [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Write-Verbose "[Request-FalconToken] Set TLS 1.2 via [Net.ServicePointManager]" } } $Script:Falcon.Api.Handler.AutomaticDecompression = [System.Net.DecompressionMethods]::Gzip, [System.Net.DecompressionMethods]::Deflate $Script:Falcon.Api.Client.DefaultRequestHeaders.UserAgent.ParseAdd( "$((Show-FalconModule).UserAgent)") } else { Write-Error "Unable to initialize [ApiClient] object." } } catch { throw $_ } } else { (Get-ApiCredential $PSBoundParameters).GetEnumerator().foreach{ # Update existing ApiClient with new input if ($Script:Falcon.($_.Key) -ne $_.Value) { $Script:Falcon.($_.Key) = $_.Value } } } if ($PSBoundParameters.Collector) { $Collector = $PSBoundParameters.Collector Register-FalconEventCollector @Collector } if ($Script:Falcon.ClientId -and $Script:Falcon.ClientSecret) { $Param = @{ Path = "$($Script:Falcon.Hostname)/oauth2/token" Method = 'post' Headers = @{ Accept = 'application/json' ContentType = 'application/x-www-form-urlencoded' } Body = "client_id=$($Script:Falcon.ClientId)&client_secret=$($Script:Falcon.ClientSecret)" } if ($Script:Falcon.MemberCid) { $Param.Body += "&member_cid=$($Script:Falcon.MemberCid)" } $Request = $Script:Falcon.Api.Invoke($Param) if ($Request.Result) { $Region = $Request.Result.Headers.GetEnumerator().Where({ $_.Key -eq 'X-Cs-Region' }).Value $Redirect = switch ($Region) { # Update ApiClient hostname if redirected 'us-1' { 'https://api.crowdstrike.com' } 'us-2' { 'https://api.us-2.crowdstrike.com' } 'us-gov-1' { 'https://api.laggar.gcw.crowdstrike.com' } 'eu-1' { 'https://api.eu-1.crowdstrike.com' } } if ($Redirect -and $Script:Falcon.Hostname -ne $Redirect) { Write-Verbose "[Request-FalconToken] Redirected to '$Region'" $Script:Falcon.Hostname = $Redirect } $Result = Write-Result $Request if ($Result.access_token) { # Cache access token in ApiClient [string]$Token = $Result.token_type,$Result.access_token -join ' ' if (!$Script:Falcon.Api.Client.DefaultRequestHeaders.Authorization) { $Script:Falcon.Api.Client.DefaultRequestHeaders.Add('Authorization',$Token) } else { $Script:Falcon.Api.Client.DefaultRequestHeaders.Authorization = $Token } $Script:Falcon.Expiration = (Get-Date).AddSeconds($Result.expires_in) Write-Verbose "[Request-FalconToken] Authorized until: $($Script:Falcon.Expiration)" } elseif (@(308,429) -contains $Request.Result.StatusCode.GetHashCode()) { # Retry token request when rate limited or unable to automatically follow redirection & $MyInvocation.MyCommand.Name } } else { @('ClientId','ClientSecret','MemberCid').foreach{ [void]$Script:Falcon.Remove("$_") } [void]$Script:Falcon.Api.Client.DefaultRequestHeaders.Remove('Authorization') throw 'Authorization token request failed.' } } else { throw 'Missing required credentials.' } } } function Revoke-FalconToken { <# .SYNOPSIS Revoke your active OAuth2 access token .DESCRIPTION Revokes your active OAuth2 access token and clears cached credential information ('ClientId', 'ClientSecret', 'MemberCid', 'Cloud'/'Hostname') from the module. .LINK https://github.com/crowdstrike/psfalcon/wiki/Revoke-FalconToken #> [CmdletBinding(DefaultParameterSetName='/oauth2/revoke:post',SupportsShouldProcess)] param() process { if ($Script:Falcon.Api.Client.DefaultRequestHeaders.Authorization.Parameter -and $Script:Falcon.ClientId -and $Script:Falcon.ClientSecret) { # Revoke OAuth2 access token $Param = @{ Path = "$($Script:Falcon.Hostname)/oauth2/revoke" Method = 'post' Headers = @{ Accept = 'application/json' ContentType = 'application/x-www-form-urlencoded' Authorization = "basic $([System.Convert]::ToBase64String( [System.Text.Encoding]::ASCII.GetBytes("$($Script:Falcon.ClientId):$( $Script:Falcon.ClientSecret)")))" } Body = "token=$($Script:Falcon.Api.Client.DefaultRequestHeaders.Authorization.Parameter)" } $Request = $Script:Falcon.Api.Invoke($Param) Write-Result $Request [void]$Script:Falcon.Api.Client.DefaultRequestHeaders.Remove('Authorization') } @('ClientId','ClientSecret','MemberCid').foreach{ if ($Script:Falcon.$_) { [void]$Script:Falcon.Remove($_) } } } } function Test-FalconToken { <# .SYNOPSIS Display OAuth2 access token status .DESCRIPTION Displays a [PSCustomObject] containing token status ('Token') along with cached 'Hostname', 'ClientId' and 'MemberCid' values. .LINK https://github.com/crowdstrike/psfalcon/wiki/Test-FalconToken #> [CmdletBinding()] param() process { if ($Script:Falcon) { [PSCustomObject]@{ Token = if ($Script:Falcon.Api.Client.DefaultRequestHeaders.Authorization -and ($Script:Falcon.Expiration -gt (Get-Date).AddSeconds(60))) { $true } else { $false } Hostname = $Script:Falcon.Hostname ClientId = $Script:Falcon.ClientId MemberCid = $Script:Falcon.MemberCid } } else { Write-Error "No authorization token available. Try 'Request-FalconToken'." } } } |