Skip to content

Commit b9c608f

Browse files
committed
fix: Properly support mTLS
1 parent 4a4651a commit b9c608f

File tree

4 files changed

+49
-2
lines changed

4 files changed

+49
-2
lines changed

docs/user/authentication.rst

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,37 @@ mTLS - Mutual TLS Authentication (Certificate-Based Authentication)
6868

6969
The most ideal form of authentication for machine to machine communication. Follow `KB0993615 <https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB0993615>`_ then:
7070

71-
>>> client = ServiceNowClient(instance, cert=('/path/to/client.cert', '/path/to/client.key'))
71+
>>> client = ServiceNowClient(instance, cert=('/path/to/USER_x509.pem', '/path/to/USERPRIVATEKEY.key'))
72+
73+
74+
A quick example, using self-signed certificates:
75+
76+
1. Setup the CA (root) key
77+
78+
```bash
79+
# generate a root private key, if for some reason you don't have one already
80+
$ openssl genrsa -aes256 -out ca.key 2048
81+
# generate the CA certificate
82+
$ openssl req -x509 -new -nodes -key ca.key -out cert.pem -sha512 -days 365 -out cacert.pem
83+
```
84+
85+
2. Upload cacert.pem via /sys_ca_certificate.do
86+
87+
3. Setup the user key and CSR (we just generate them here for a POC example)
88+
89+
```bash
90+
# note: python requests (the underlying library) does not directly support keys with passwords!
91+
$ openssl req -nodes -newkey rsa:2048 -keyout USERPRIVATEKEY.key -out USERCSR.csr
92+
```
93+
94+
4. Sign the CSR with the root, creating a X.509 for the user
95+
96+
```
97+
$ openssl x509 -req -days 365 -in USERCSR.csr -CA cacert.pem -CAkey ca.key -extfile <(printf "extendedKeyUsage=clientAuth") -out USER_x509.pem
98+
```
99+
100+
5. Attach `USER_x509.pem` to a new `/sys_user_certificate.do` record
101+
72102

73103
Requests Authentication
74104
-----------------------

pysnc/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class ServiceNowClient(object):
2929
:param bool verify: Verify the SSL/TLS certificate OR the certificate to use. Useful if you're using a self-signed HTTPS proxy.
3030
:param cert: if String, path to ssl client cert file (.pem). If Tuple, (‘cert’, ‘key’) pair.
3131
"""
32-
def __init__(self, instance, auth, proxy=None, verify=None, cert=None, auto_retry=True):
32+
def __init__(self, instance, auth=None, proxy=None, verify=None, cert=None, auto_retry=True):
3333
self._log = logging.getLogger(__name__)
3434
self.__instance = get_instance(instance)
3535

@@ -62,6 +62,7 @@ def __init__(self, instance, auth, proxy=None, verify=None, cert=None, auto_retr
6262
elif isinstance(auth, ServiceNowFlow):
6363
self.__session = auth.authenticate(self.__instance, proxies=self.__proxies, verify=verify)
6464
elif cert is not None:
65+
self.__session = requests.session()
6566
self.__session.cert = cert
6667
else:
6768
raise AuthenticationException('No valid authentication method provided')

test/test_snc_auth.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,19 @@ def nop_test_jwt(self):
6464
assert gr.get('6816f79cc0a8016401c5a33be04be441'), "did not jwt auth"
6565
'''
6666

67+
@skip("Requires keys and conf that makes automation hard")
68+
def test_mtls(self):
69+
# e.g. PYSNC_USER_KEY=x PYSNC_USER_CERT=y poetry run pytest test/test_snc_auth.py::TestAuth::test_mtls
70+
path_key = self.c.get_value('USER_KEY')
71+
assert path_key, 'Require user private key'
72+
path_cert = self.c.get_value('USER_CERT')
73+
assert path_cert, 'Require user x509 certificate'
74+
75+
client = ServiceNowClient(self.c.server, cert=(path_cert, path_key))
76+
gr = client.GlideRecord('sys_user')
77+
gr.fields = 'sys_id'
78+
self.assertTrue(gr.get('6816f79cc0a8016401c5a33be04be441'))
79+
6780

6881

6982

test/test_snc_element.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,6 @@ def test_changes(self):
217217
self.assertFalse(element.changes())
218218
element.set_value('4')
219219
self.assertTrue(element.changes())
220+
221+
def test_non_ref_string_field(self):
222+
element = GlideElement('')

0 commit comments

Comments
 (0)