Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Self-signed certificates in pyaim #53

Open
crispaSLHS opened this issue Jun 28, 2021 · 3 comments
Open

Self-signed certificates in pyaim #53

crispaSLHS opened this issue Jun 28, 2021 · 3 comments
Labels
enhancement New feature or request

Comments

@crispaSLHS
Copy link

My organization uses self-signed certs internally. I would like to be able to pass an argument to the object initializer to specify the cert location.

I verified that if I pass verify=False to the CCPPasswordREST() function, everything works. That's not ideal, though.

This first code block demonstrates what I'm doing to replicate the conditions in the aimccp.py file. I grab the same package imports, set up the app endpoint (names all redacted), and walk through the object creation including SSL setup. I included the relevant part of the error message from the conn.getresponse() call.

import http.client
import ssl

CCP_ENDPOINT = 'https://app.foo.org'
full_url = 'https://app.foo.org/AIMWebService/api/Accounts?AppID=MyAppID&Safe=MySafe&Object=MyObject'

base_uri = CCP_ENDPOINT
base_uri = base_uri.strip('/').replace('https://','')
headers = {'Content-Type': 'application/json'}

context = ssl.create_default_context()

conn = http.client.HTTPSConnection(base_uri, context=context)
conn.request("GET", full_url, headers=headers)

>>> conn.request("GET", full_url, headers=headers)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  ...
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1129)

I did some digging and found the requests package method of handling the verify argument. They allow for passing the location of your local certs bundle. the http.client package has a similar capability. This sample shows the added lines to my example that declare the local cert location. It turns out that the standard (on my distro, anyway) location for certs works fine. It just needs to be expressly declared. I think that is something to do with conda.

...

certs = '/etc/ssl/certs/ca-bundle.crt'
context = ssl.create_default_context()
context.load_verify_locations(cafile=certs)
conn = http.client.HTTPSConnection(base_uri, context=context)
conn.request("GET", full_url, headers=headers)
res = conn.getresponse()
conn.close()
res.status

>>> res.status
200

When the essential line (context.load_verify_locations(cafile=certs)) is added to the non-working example above, the http.client code is able to connect to my app server. To integrate this, I suggest modifying the CCPPasswordREST definition to be something like this:

class CCPPasswordREST(object):

    # Runs on Initialization
    def __init__(self, base_uri, verify=True):

        # Declare Init Variables
        self._base_uri = base_uri.rstrip('/').replace('https://','') # Example: https://pvwa.cyberarkexample.com
        self._context = ssl.create_default_context()
        self._headers = {'Content-Type': 'application/json'} # Build Header for GET Request

        if verify is not True:
            if verify is False:
                self._context = ssl._create_unverified_context()
                self._context.check_hostname = True
            else:
                self._context.load_verify_locations(cafile=verify)

This overloads the verify argument similar to the requests package. The alternate option I see would be to add another argument to the definition, def __init__(self, base_uri, verify=True, cafile='/etc/ssl/certs/ca-bundle.crt'): or some such.

@mlarivie
Copy link

Using verify=True with self-signed certs goes against the validation process... since self-signed, no trusted authority.

Why not get a certificate especially for something like CyberArk to ensure no man in the middle attacks can occur.

@crispaSLHS
Copy link
Author

Perhaps 'self-signed' is not the correct term. We have an internal, trusted certificate authority that provides a trusted CA cert. This enables the enterprise to execute a man in the middle attack on its own traffic for inspection reasons. Accepting that internal CA as trusted is an accepted risk.

The SSL library used in pyaim has a mechanism for allowing custom certificate locations and is referenced in their Security Considerations section. I would like this mechanism surfaced in pyaim.
https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_verify_locations

For those who follow, I unwrapped the pyaim objects and recreated the relevant bits using the requests library something like this:

import requests

CCP_ENDPOINT = 'https://cyberarkserver.my.domain.example.com'
DEFAULT_APPID = 'MyAppID'
DEFAULT_SAFEID = 'MySafeID'

def build_request(object_name, appid=None, safe=None, endpoint=None):
    if object_name is not None:
        if appid is None:
            appid = get_appid()
        if safe is None:
            safe = get_safeid()
        if endpoint is None:
            endpoint = get_ccp_endpoint()
        api_extension = "/AIMWebService/api/Accounts?"
        request_url = (endpoint + api_extension +
            "AppID=" + appid +
            "&" +
            "Safe=" + safe +
            "&" +
            "Object=" + object_name)
        return request_url
        
def fetch_account(object_name):
    appid = get_appid()
    safe = get_safeid()
    endpoint = get_ccp_endpoint()
    request = build_request(object_name,
                            appid=appid,
                            safe=safe,
                            endpoint=endpoint)
    result = get_result(request=request)
    return(result)

def get_appid():
    return(DEFAULT_APPID)

def get_ccp_endpoint():
    return(CCP_ENDPOINT)

def get_result(request, verify='/etc/ssl/certs/ca-bundle.crt'):
    if request is not None:
        result = requests.get(request, verify=verify)
        if result.status_code == 200:
            return(result.json())

def get_safeid():
    return(DEFAULT_SAFEID)

@infamousjoeg
Copy link
Owner

@crispaSLHS,

This is currently a backlog item for me until more requests come through for this support. However, I just opened up contributions so feel free to submit a PR for this feature to be added!

@infamousjoeg infamousjoeg added the enhancement New feature or request label Dec 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants