splunk / splunk-sdk-python

Splunk Software Development Kit for Python
http://dev.splunk.com
Apache License 2.0
698 stars 370 forks source link

Parameter for additional verifiy locations for Server Certificates during connect #380

Closed AndyXan closed 2 years ago

AndyXan commented 3 years ago

Is your feature request related to a problem? Please describe. The Splunk Python SDK lacks the possibility to provide a custom ca certificate that is not installed in the local system certificates storage for server certificate validation.

The possible arguments (key_file, cert_file) are for client authentifaction - not for server certificate verficitation.

The problematic Code snippet is (current upstream): binding.py::1354

def handler(key_file=None, cert_file=None, timeout=None, verify=False):
    """This class returns an instance of the default HTTP request handler using
    the values you provide.

    :param `key_file`: A path to a PEM (Privacy Enhanced Mail) formatted file containing your private key (optional).
    :type key_file: ``string``
    :param `cert_file`: A path to a PEM (Privacy Enhanced Mail) formatted file containing a certificate chain file (optional).
    :type cert_file: ``string``
    :param `timeout`: The request time-out period, in seconds (optional).
    :type timeout: ``integer`` or "None"
    :param `verify`: Set to False to disable SSL verification on https connections.
    :type verify: ``Boolean``
    """

    def connect(scheme, host, port):
        **kwargs = {} <-----**
        if timeout is not None: kwargs['timeout'] = timeout
        if scheme == "http":
            return six.moves.http_client.HTTPConnection(host, port, **kwargs)
        if scheme == "https":
            if key_file is not None: kwargs['key_file'] = key_file
            if cert_file is not None: kwargs['cert_file'] = cert_file

            if not verify:
                kwargs['context'] = ssl._create_unverified_context()

            return six.moves.http_client.HTTPSConnection(host, port, **kwargs) <-----------------
        raise ValueError("unsupported scheme: %s" % scheme)

So, if verification is enabled - a default SSL Context is created and CA's .pem file have to be present in the system certificate storage. Otherwise it's an error like that:

import http.client
c = http.client.HTTPSConnection("my-splunk-host", "8000")
c.request("GET", "/")

[Out] SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] 

What's actually desired is to provide additional ssl lookup definitions:

import ssl
import http.client
context = ssl.create_default_context()
context.load_verify_locations("my-root-ca.pem")
c = http.client.HTTPSConnection("my-splunk-host", "8000", context=context)
test = c.request("GET", "/")

Or in the above snippet from the original code in binding.py

    def connect(scheme, host, port):
        kwargs = {}
        if timeout is not None: kwargs['timeout'] = timeout
        if scheme == "http":
            return six.moves.http_client.HTTPConnection(host, port, **kwargs)
        if scheme == "https":
            if key_file is not None: kwargs['key_file'] = key_file
            if cert_file is not None: kwargs['cert_file'] = cert_file

            if not verify:
                kwargs['context'] = ssl._create_unverified_context()
            else: 
                   context = ssl.create_default_context() <--------------------
                   context.load_verify_locations("my-root-ca.pem") <--------------------
                   kwargs['context'] = context <--------------------

            return six.moves.http_client.HTTPSConnection(host, port, **kwargs)
        raise ValueError("unsupported scheme: %s" % scheme)

Describe the solution you'd like

With the pull request, a User can optionally provide the entire SSLContext object with verify locations and all other settings, such as TLS-Version. A user can fully control the transport channel, which a user should be able to do.

import ssl
import splunklib.client as client
context = ssl.create_default_context()
context.load_verify_locations("my-root-ca-cert.pem")
self.service = client.connect(host=self.host, app='my-app', port=8089,
       username=self.username, scheme='https', password=self.password, verify=True, context=context)

The following parts of the code are slightly changes (details, see Pull Request).

Simply add a new argument that either allows the passing of a entire SSL-Context object or the CA-Verify-Locations at:

It's not a big change - I am going to create a pull request tomorrow with the option to pass an entire SSLContext object to HTTPSConnection(...), thus enabling the user to specify custom ca certficates, tls versions and so on. I think the user should have the possibility to fully control the transport channel, if desired.

Best Regards

Describe alternatives you've considered The only alternative is to install custom ca certificates on the local system beforehand. This is suboptimal for certificates shipped with a packaged application and varies depending on the Operating System.

Additional context Add any other context or screenshots about the feature request here.

AndyXan commented 3 years ago

The context can now be supplied with

This would enable new user-land code when using the splunk-sdk-python after PR-Accept:

import ssl
import splunklib.client as client
context = ssl.create_default_context()
context.load_verify_locations("my-root-ca-cert.pem")
self.service = client.connect(host=self.host, app='my-app', port=8089,
       username=self.username, scheme='https', password=self.password, verify=True, context=context)

And with optionally controlling the SLLContext, verifiy locations and tls-version specs etc. can be configured. The context object is optional, so it shouldn't break anything.

akaila-splunk commented 2 years ago

Thanks for PR @AndyXan , We have merged the PR and released a SDK version 1.6.17 with the changes.