Public/oauth2.ps1
function Request-FalconToken { [CmdletBinding(DefaultParameterSetName = 'Hostname')] param( [Parameter(ParameterSetName = 'Cloud', ValueFromPipelineByPropertyName = $true, Position = 1)] [Parameter(ParameterSetName = 'Hostname', ValueFromPipelineByPropertyName = $true, Position = 1)] [Alias('client_id')] [ValidatePattern('^\w{32}$')] [string] $ClientId, [Parameter(ParameterSetName = 'Cloud', ValueFromPipelineByPropertyName = $true, Position = 2)] [Parameter(ParameterSetName = 'Hostname', ValueFromPipelineByPropertyName = $true, Position = 2)] [Alias('client_secret')] [ValidatePattern('^\w{40}$')] [string] $ClientSecret, [Parameter(ParameterSetName = 'Cloud', ValueFromPipelineByPropertyName = $true, Position = 3)] [ValidateSet('eu-1', 'us-gov-1', 'us-1', 'us-2')] [string] $Cloud, [Parameter(ParameterSetName = 'Hostname', ValueFromPipelineByPropertyName = $true, 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')] [string] $Hostname, [Parameter(ParameterSetName = 'Cloud', ValueFromPipelineByPropertyName = $true, Position = 4)] [Parameter(ParameterSetName = 'Hostname', ValueFromPipelineByPropertyName = $true, Position = 4)] [Alias('cid', 'member_cid')] [ValidatePattern('^\w{32}$')] [string] $MemberCid, [Parameter(ParameterSetName = 'Cloud', ValueFromPipelineByPropertyName = $true, Position = 5)] [Parameter(ParameterSetName = 'Hostname', ValueFromPipelineByPropertyName = $true, 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 { function Get-ApiCredential ($Inputs) { $Output = @{} @('ClientId', 'ClientSecret', 'Hostname', 'MemberCid').foreach{ $Value = if ($Inputs.$_) { # Use input $Inputs.$_ } elseif ($null -ne $Script:Falcon.$_) { # Use ApiClient value $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{ if ($Script:Falcon.($_.Key) -ne $_.Value) { # Update existing ApiClient with new input $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 { [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 { [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'." } } } |