Public/oauth2.ps1
function Request-FalconToken { <# .SYNOPSIS Request an OAuth2 access token .DESCRIPTION Requests an OAuth2 access token. If successful,your credentials ('ClientId','ClientSecret','MemberCid' and 'Cloud'/'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/Authentication #> [CmdletBinding(DefaultParameterSetName='Hostname')] param( [Parameter(ParameterSetName='Cloud',ValueFromPipelineByPropertyName,Position=1)] [Parameter(ParameterSetName='Hostname',ValueFromPipelineByPropertyName,Position=1)] [Alias('client_id')] [ValidatePattern('^\w{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('^\w{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 '^\w{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') { '^\w{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 $Request if ($Result.access_token) { # Cache access token in ApiClient $Token = "$($Result.token_type) $($Result.access_token)" 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/Authentication #> [CmdletBinding(DefaultParameterSetName='/oauth2/revoke:post')] 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 $Request [void]$Script:Falcon.Api.Client.DefaultRequestHeaders.Remove('Authorization') } @('ClientId','ClientSecret','MemberCid').foreach{ [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/Authentication #> [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'." } } } |