Skip to content

Commit

Permalink
Kerberos support (#103)
Browse files Browse the repository at this point in the history
* testing Kerberos integration
* add kerberos information into readme
  • Loading branch information
denisenkom authored Apr 12, 2020
1 parent ff97972 commit 2996f23
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 1 deletion.
7 changes: 7 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Features
* Bulk insert
* Table-valued parameters
* TLS connection encryption
* Kerberos support on non-Windows platforms (requires kerberos package)

Installation
------------
Expand All @@ -49,6 +50,12 @@ For a better performance install bitarray package too:
$ pip install bitarray
To use Kerberos on non-Windows platforms (experimental) install kerberos package:

.. code-block:: bash
$ pip install kerberos
Documentation
-------------
Documentation is available at https://python-tds.readthedocs.io/en/latest/.
Expand Down
16 changes: 15 additions & 1 deletion src/pytds/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,7 @@ def connect(dsn=None, database=None, user=None, password=None, timeout=None,
cafile=None, validate_host=True, enc_login_only=False,
disable_connect_retry=False,
pooling=False,
use_sso=False,
):
"""
Opens connection to the database
Expand Down Expand Up @@ -1204,8 +1205,12 @@ def connect(dsn=None, database=None, user=None, password=None, timeout=None,
anyone who can observe traffic on your network will be able to see all your SQL requests and potentially modify
them.
:type enc_login_only: bool
:keyword use_sso: Enables SSO login, e.g. Kerberos using SSPI on Windows and kerberos package on other platforms.
Cannot be used together with auth parameter.
:returns: An instance of :class:`Connection`
"""
if use_sso and auth:
raise ValueError('use_sso cannot be used with auth parameter defined')
login = _TdsLogin()
login.client_host_name = socket.gethostname()[:128]
login.library = "Python TDS Library"
Expand Down Expand Up @@ -1256,7 +1261,6 @@ def connect(dsn=None, database=None, user=None, password=None, timeout=None,
login.connect_timeout = login_timeout
login.query_timeout = timeout
login.blocksize = blocksize
login.auth = auth
login.readonly = readonly
login.load_balancer = load_balancer
login.bytes_to_unicode = bytes_to_unicode
Expand Down Expand Up @@ -1284,6 +1288,16 @@ def connect(dsn=None, database=None, user=None, password=None, timeout=None,
raise ValueError("Both instance and port shouldn't be specified")
parsed_servers.append((host, port, instance))

if use_sso:
spn = "MSSQLSvc@{}:{}".format(parsed_servers[0][0], parsed_servers[0][1])
from . import login as pytds_login
try:
login.auth = pytds_login.SspiAuth(spn=spn)
except ImportError:
login.auth = pytds_login.KerberosAuth(spn)
else:
login.auth = auth

login.servers = _get_servers_deque(tuple(parsed_servers), database)

# unique connection identifier used to pool connection
Expand Down
39 changes: 39 additions & 0 deletions src/pytds/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,42 @@ def handle_next(self, packet):

def close(self):
pass


class KerberosAuth(object):
def __init__(self, server_principal):
try:
import kerberos
except ImportError:
import winkerberos as kerberos
self._kerberos = kerberos
res, context = kerberos.authGSSClientInit(server_principal)
if res < 0:
raise RuntimeError('authGSSClientInit failed with code {}'.format(res))
logger.info('Initialized GSS context')
self._context = context

def create_packet(self):
import base64
res = self._kerberos.authGSSClientStep(self._context, '')
if res < 0:
raise RuntimeError('authGSSClientStep failed with code {}'.format(res))
data = self._kerberos.authGSSClientResponse(self._context)
logger.info('created first client GSS packet %s', data)
return base64.b64decode(data)

def handle_next(self, packet):
import base64
res = self._kerberos.authGSSClientStep(self._context, base64.b64encode(packet).decode('ascii'))
if res < 0:
raise RuntimeError('authGSSClientStep failed with code {}'.format(res))
if res == self._kerberos.AUTH_GSS_COMPLETE:
logger.info('GSS authentication completed')
return b''
else:
data = self._kerberos.authGSSClientResponse(self._context)
logger.info('created client GSS packet %s', data)
return base64.b64decode(data)

def close(self):
pass
9 changes: 9 additions & 0 deletions tests/connected_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -997,3 +997,12 @@ def test_no_metadata_request(cursor):
assert cursor.fetchall() == [(2,)]
while cursor.nextset():
pass


def test_with_sso():
if not LIVE_TEST:
pytest.skip('LIVE_TEST is not set')
with pytds.connect(settings.HOST, use_sso=True) as conn:
with conn.cursor() as cursor:
cursor.execute('select 1')
cursor.fetchall()

0 comments on commit 2996f23

Please sign in to comment.