CrowdStrike / psfalcon

PowerShell for CrowdStrike's OAuth2 APIs
The Unlicense
362 stars 67 forks source link

[ BUG ] Exception setting "SslProtocols": "Operation is not supported on this platform." #154

Closed Minty123 closed 2 years ago

Minty123 commented 2 years ago

Describe the bug When using PSFalcon 2.1.6 (same happens with 2.1.5), I get the exception:

Exception setting "SslProtocols": "Operation is not supported on this platform."
At C:\Program Files\WindowsPowerShell\Modules\PSFalcon\2.1.6\Public\oauth2.ps1:85 char:21
+                     $Script:Falcon.Api.Handler.SslProtocols = 'Tls12'
+                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], SetValueInvocationException
    + FullyQualifiedErrorId : ExceptionWhenSetting

To Reproduce

  1. Open a new PS session
  2. Import-Module PSFalcon (here: imports 2.1.6)
  3. Request-FalconToken -Cloud eu-1 -ClientId
  4. Enter client secret
  5. Error appears

Line 85 in oauth2.ps1 runs $Script:Falcon.Api.Handler.SslProtocols = 'Tls12', however according to https://stackoverflow.com/a/48832145 the property doesn't exist in .NET 4.6.2, which is the default for Server 2016 apparently. [Source]

Workaround

  1. run [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 before running PSFalcon commands
  2. rerun Request-FalconToken
  3. Now it works, which can be confirmed by Test-FalconToken

Even when doing step 1 first, requesting the token will fail once.

Expected behavior Error doesn't appear.

Environment (please complete the following information):

Additional context I'm afraid I'm not sure if the problem is related to the server config, PSFalcon or .NET. Still thought it might be a good idea to bring it up here, in case others experience the same.

Thanks for any help or feedback! Your work on the module is highly appreciated as always!

bk-cs commented 2 years ago

Thank you for the report and your in-depth analysis.

When Request-FalconToken is first run, it creates a hashtable containing a [System.Net.Http.HttpClientHandler] object, stored under $Falcon.Api.Handler. This handler is used to make requests with an accompanying [System.Net.Http.HttpClient] object stored under $Falcon.Api.Client.

The SslProtocols property in the [System.Net.Http.HttpClientHandler] class is used to enforce TLS 1.2 (required by the Falcon APIs) for all request by PSFalcon, without setting it for the entire PowerShell session (which is what is happening when you set [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12).

Given that you can set this for the session, but not for the particular class, I suspect that this is a .NET bug. Unfortunately, I don't know where to begin to even attempt to get that fixed.

If you run the steps in this order, does it allow you to request the token without any errors?

[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
Import-Module -Name PSFalcon
Request-FalconToken
Minty123 commented 2 years ago

Thank you for the fast response and the great explanation! I tried your steps, though I get the same error. The article you've referenced doesn'lt list .NET Framework 4.6.2, which is why I assume the property just doesn't exist there (as stated by that StackOverflow response too).

Out of spite, I installed .NET Framework 4.8 on the server, and it worked right out of the box, which would also confirm the assumption the property is just not available in .NET 4.6.2. I wonder what would be the best way to go about it. Finding a workaround that works for all or just make it a requirement for PSFalcon and call it a day...

bk-cs commented 2 years ago

In earlier versions of PSFalcon, I used the [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 command to set TLS. This worked as long as the device had TLS 1.2 available (pretty much any device with PowerShell 5.1 or above), but I would like to avoid making a change that impacts the entire PowerShell session. Using the [System.Net.Http.HttpClientHandler] method seemed like a good alternative.

I should be able to run a check before changing the setting to verify that the property is available, and if not, change it for the PowerShell session. I'll test and update if I think I can create a workaround.

Minty123 commented 2 years ago

Sounds great! Thanks so much for your help. If there's anything I can test to help, just let me know. :-)

bk-cs commented 2 years ago

Can you replace your local copy of Public\oauth2.ps1 with the contents of this file? https://raw.githubusercontent.com/CrowdStrike/psfalcon/master/Public/oauth2.ps1

I added a check for the SslProtocols property before attempting to set it, and if not present, it uses [System.Net.ServicePointManager] instead. I expect that it should eliminate the error message for you, without impacting other .NET versions that have the property available.

Minty123 commented 2 years ago

Interesting! The property "SslProtocols" exists, so it still tries to set a value, but fails while doing so. I'm getting the same error.

I can confirm the System.Net.Http.HttpClientHandler object which gets created by [ApiClient]::New() shows the property:

PS C:\Users\redacted> ([ApiClient]::New()).Handler

CheckCertificateRevocationList            :
ClientCertificates                        : {}
DefaultProxyCredentials                   :
MaxConnectionsPerServer                   : 2
MaxResponseHeadersLength                  : 64
Properties                                : {}
ServerCertificateCustomValidationCallback :
SslProtocols                              :
... 

Edit: What works is replacing your check with:

try {
    $Script:Falcon.Api.Handler.SslProtocols = 'Tls12'
} catch {
    # Set TLS 1.2 for PowerShell session
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
}

Though that's pretty broad. Unfortunately the exception thrown is not very specific, so I don't think I could narrow down the catch.

bk-cs commented 2 years ago

I'd appreciate it if .NET bugs were a little more logically consistent. 😆

I like your try/catch method--that's not a way that I ever really use try/catch but it makes sense and I think it's a more elegant solution.

Would you like to commit the change, with appropriate comments? You've got a great idea and I'd love to properly record your contribution.

Maybe something like:

            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'
                    } catch {
                        if ([Net.ServicePointManager]::SecurityProtocol -notmatch 'Tls12') {
                            # Set TLS 1.2 for PowerShell session
                            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                        }
                    }
                    $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 $_
            }

EDIT: Added an if to the catch, since the session setting isn't required unless it's not present.

bk-cs commented 2 years ago

I can confirm the System.Net.Http.HttpClientHandler object which gets created by [ApiClient]::New() shows the property

Thinking more about this, I wonder if it's a case of the property being present, but Tls12 not existing as an accepted value within that particular .NET version. I've seen weirdness like this before between .NET versions.

Your try/catch mentioned above should resolve it either way.

Minty123 commented 2 years ago

I'll test it on one or two other OS first, then I'll gladly make the commit. :-) Regarding your if-addition, couldn't an if-clause also be added to $Script:Falcon.Api.Handler.SslProtocols = 'Tls12' or will that one always be initialized without a value?

Edit: Just some small tests with combinations I could find: OS .NET Result
Server 2016 .NET Framework 4.6.2 Works. Uses [Net.ServicePointManager]
Server 2012 R2 .NET Framework 4.7.2 Works. Uses [System.Net.Http.HttpClientHandler]
Server 2016 .NET Framework 4.8 Works. Uses [System.Net.Http.HttpClientHandler]
bk-cs commented 2 years ago

I'll test it on one or two other OS first, then I'll gladly make the commit. :-)

Awesome!

Regarding your if-addition, couldn't an if-clause also be added to $Script:Falcon.Api.Handler.SslProtocols = 'Tls12' or will that one always be initialized without a value?

Yes, this is always initialized without a value.

OS .NET Result Server 2016 .NET Framework 4.6.2 Works. Uses [Net.ServicePointManager] Server 2012 R2 .NET Framework 4.7.2 Works. Uses [System.Net.Http.HttpClientHandler] Server 2016 .NET Framework 4.8 Works. Uses [System.Net.Http.HttpClientHandler]

My guess is that 4.6.2 is the only one you'll find that requires [System.Net.ServicePointManager]. Nothing brings me more joy than finding these weird differences between .NET and PowerShell. I hope that came across appropriately dripping with sarcasm.

Minty123 commented 2 years ago

Alright, so I made a PR. Hopefully everything was correct. I've also added two Write-Verbose lines to the code to indicate the method used. Might be relevant in the future, though if you want, feel free to remove it again. Thanks for your help and the effort you put in this project! :-)

bk-cs commented 2 years ago

Merged, but I'm going to re-open this issue in case anyone else runs into this problem before the v2.1.7 release.

For those following along, if you're experiencing this issue, you can replace Public\oauth2.ps1 with the contents of this file to fix the problem before v2.1.7 is released: https://raw.githubusercontent.com/CrowdStrike/psfalcon/master/Public/oauth2.ps1

bk-cs commented 2 years ago

Closing due to v2.1.7 release. Thank you for your contribution!