diff --git a/docs/user/authentication.rst b/docs/user/authentication.rst index 8f5c019..ef3a03f 100644 --- a/docs/user/authentication.rst +++ b/docs/user/authentication.rst @@ -68,7 +68,41 @@ mTLS - Mutual TLS Authentication (Certificate-Based Authentication) The most ideal form of authentication for machine to machine communication. Follow `KB0993615 `_ then: - >>> client = ServiceNowClient(instance, cert=('/path/to/client.cert', '/path/to/client.key')) + + >>> client = ServiceNowClient(instance, cert=('/path/to/USER_x509.pem', '/path/to/USERPRIVATEKEY.key')) + + +A quick example, using self-signed certificates: + +1. Setup the CA (root) key + +.. code-block:: bash + + # generate a root private key, if for some reason you don't have one already + openssl genrsa -aes256 -out ca.key 2048 + # generate the CA certificate + openssl req -x509 -new -nodes -key ca.key -out cert.pem -sha512 -days 365 -out cacert.pem + +2. Upload `cacert.pem` via `/sys_ca_certificate.do` + +3. Setup the user key and CSR (we just generate them here for a POC example) + +.. code-block:: bash + + openssl req -nodes -newkey rsa:2048 -keyout USERPRIVATEKEY.key -out USERCSR.csr + +.. important:: + + Python requests (the underlying http library) does not directly support keys with passwords! See `requests#2519 `_ for details. + +4. Sign the CSR with the root, creating a X.509 for the user + +.. code-block:: bash + + openssl x509 -req -days 365 -in USERCSR.csr -CA cacert.pem -CAkey ca.key -extfile <(printf "extendedKeyUsage=clientAuth") -out USER_x509.pem + +5. Attach `USER_x509.pem` to a new `/sys_user_certificate.do` record + Requests Authentication ----------------------- diff --git a/pysnc/client.py b/pysnc/client.py index 218dc21..a1df47b 100644 --- a/pysnc/client.py +++ b/pysnc/client.py @@ -29,7 +29,7 @@ class ServiceNowClient(object): :param bool verify: Verify the SSL/TLS certificate OR the certificate to use. Useful if you're using a self-signed HTTPS proxy. :param cert: if String, path to ssl client cert file (.pem). If Tuple, (‘cert’, ‘key’) pair. """ - def __init__(self, instance, auth, proxy=None, verify=None, cert=None, auto_retry=True): + def __init__(self, instance, auth=None, proxy=None, verify=None, cert=None, auto_retry=True): self._log = logging.getLogger(__name__) self.__instance = get_instance(instance) @@ -62,6 +62,7 @@ def __init__(self, instance, auth, proxy=None, verify=None, cert=None, auto_retr elif isinstance(auth, ServiceNowFlow): self.__session = auth.authenticate(self.__instance, proxies=self.__proxies, verify=verify) elif cert is not None: + self.__session = requests.session() self.__session.cert = cert else: raise AuthenticationException('No valid authentication method provided') diff --git a/test/test_snc_auth.py b/test/test_snc_auth.py index d1d2415..0fc0732 100644 --- a/test/test_snc_auth.py +++ b/test/test_snc_auth.py @@ -64,6 +64,19 @@ def nop_test_jwt(self): assert gr.get('6816f79cc0a8016401c5a33be04be441'), "did not jwt auth" ''' + @skip("Requires keys and conf that makes automation hard") + def test_mtls(self): + # e.g. PYSNC_USER_KEY=x PYSNC_USER_CERT=y poetry run pytest test/test_snc_auth.py::TestAuth::test_mtls + path_key = self.c.get_value('USER_KEY') + assert path_key, 'Require user private key' + path_cert = self.c.get_value('USER_CERT') + assert path_cert, 'Require user x509 certificate' + + client = ServiceNowClient(self.c.server, cert=(path_cert, path_key)) + gr = client.GlideRecord('sys_user') + gr.fields = 'sys_id' + self.assertTrue(gr.get('6816f79cc0a8016401c5a33be04be441')) +