diff options
author | Michał Górny <mgorny@gentoo.org> | 2013-08-25 13:51:13 -0700 |
---|---|---|
committer | Michał Górny <mgorny@gentoo.org> | 2013-08-25 13:51:13 -0700 |
commit | a9d6a4b1baad577691a1f0b8eb7a8531b9de5086 (patch) | |
tree | 4e26d4dc3e76dd7ab5b72ba08f43358e5479298b | |
parent | Merge pull request #79 from tampakrap/tests_v3 (diff) | |
parent | Add tests for SSH handlers. (diff) | |
download | identity.gentoo.org-a9d6a4b1baad577691a1f0b8eb7a8531b9de5086.tar.gz identity.gentoo.org-a9d6a4b1baad577691a1f0b8eb7a8531b9de5086.tar.bz2 identity.gentoo.org-a9d6a4b1baad577691a1f0b8eb7a8531b9de5086.zip |
Merge pull request #76 from mgorny/ssh-auth
SSH authentication support
-rw-r--r-- | okupy/accounts/ssh.py | 30 | ||||
-rw-r--r-- | okupy/accounts/views.py | 12 | ||||
-rw-r--r-- | okupy/common/auth.py | 46 | ||||
-rw-r--r-- | okupy/common/ssh.py | 112 | ||||
-rw-r--r-- | okupy/settings/__init__.py | 5 | ||||
-rw-r--r-- | okupy/settings/local.py.sample | 9 | ||||
-rw-r--r-- | okupy/templates/login.html | 7 | ||||
-rw-r--r-- | okupy/tests/settings.py | 19 | ||||
-rw-r--r-- | okupy/tests/unit/test_auth.py | 51 | ||||
-rw-r--r-- | okupy/tests/unit/test_ssh.py | 189 | ||||
-rw-r--r-- | okupy/tests/vars.py | 6 | ||||
-rw-r--r-- | okupy/wsgi.py | 14 | ||||
-rw-r--r-- | requirements.txt | 1 | ||||
-rwxr-xr-x | setup.py | 1 |
14 files changed, 498 insertions, 4 deletions
diff --git a/okupy/accounts/ssh.py b/okupy/accounts/ssh.py new file mode 100644 index 0000000..4e5028e --- /dev/null +++ b/okupy/accounts/ssh.py @@ -0,0 +1,30 @@ +# vim:fileencoding=utf8:et:ts=4:sts=4:sw=4:ft=python + +from django.contrib.auth import authenticate, login + +from ..common.ssh import ssh_handler +from ..common.test_helpers import set_request +from ..crypto.ciphers import sessionrefcipher +from ..otp import init_otp + + +ssh_handlers = {} + + +@ssh_handler +def auth(session_id, key): + try: + session = sessionrefcipher.decrypt(session_id) + except ValueError: + return None + + request = set_request('/') + + user = authenticate(ssh_key=key) + if user and user.is_active: + login(request, user) + init_otp(request) + session.update(request.session) + session.save() + return 'Authenticated.' + return None diff --git a/okupy/accounts/views.py b/okupy/accounts/views.py index 103b267..96fc5d3 100644 --- a/okupy/accounts/views.py +++ b/okupy/accounts/views.py @@ -173,6 +173,7 @@ def login(request): if is_otp or strong_auth_req: ssl_auth_form = None ssl_auth_uri = None + ssh_auth_command = None else: encrypted_id = sessionrefcipher.encrypt(request.session) @@ -189,12 +190,23 @@ def login(request): ssl_auth_path = reverse(ssl_auth) ssl_auth_uri = urljoin('https://' + ssl_auth_host, ssl_auth_path) + if settings.SSH_BIND[1] == 22: + ssh_port_opt = '' + else: + ssh_port_opt = '-p %d ' % settings.SSH_BIND[1] + + ssh_auth_command = 'ssh %sauth+%s@%s' % ( + ssh_port_opt, + encrypted_id, + request.get_host().split(':')[0]) + return render(request, 'login.html', { 'login_form': login_form, 'openid_request': oreq, 'next': next, 'ssl_auth_uri': ssl_auth_uri, 'ssl_auth_form': ssl_auth_form, + 'ssh_auth_command': ssh_auth_command, 'is_otp': is_otp, }) diff --git a/okupy/common/auth.py b/okupy/common/auth.py index d7a7f95..9d4b205 100644 --- a/okupy/common/auth.py +++ b/okupy/common/auth.py @@ -8,6 +8,10 @@ from ..accounts.models import LDAPUser from OpenSSL.crypto import load_certificate, FILETYPE_PEM +import paramiko + +import base64 + class SSLCertAuthBackend(ModelBackend): """ @@ -47,3 +51,45 @@ class SSLCertAuthBackend(ModelBackend): user = UserModel.objects.get(**attr_dict) return user return None + + +class SSHKeyAuthBackend(ModelBackend): + """ + Authentication backend that uses SSH keys stored in LDAP. + """ + + def authenticate(self, ssh_key=None): + for u in LDAPUser.objects.all(): + for k in u.ssh_key: + spl = k.split() + if len(spl) < 2: + continue + + form, user_key = spl[:2] + if form == 'ssh-rsa': + key_class = paramiko.RSAKey + elif form == 'ssh-dss': + key_class = paramiko.DSSKey + else: + # key format not supported + continue + + try: + user_key = key_class(data=base64.b64decode(user_key)) + except (TypeError, paramiko.SSHException): + continue + + # paramiko reconstructs the key, so simple match should be fine + if ssh_key == user_key: + UserModel = get_user_model() + attr_dict = { + UserModel.USERNAME_FIELD: u.username + } + + user = UserModel(**attr_dict) + try: + user.save() + except IntegrityError: + user = UserModel.objects.get(**attr_dict) + return user + return None diff --git a/okupy/common/ssh.py b/okupy/common/ssh.py new file mode 100644 index 0000000..1c4a49a --- /dev/null +++ b/okupy/common/ssh.py @@ -0,0 +1,112 @@ +# vim:fileencoding=utf8:et:ts=4:sts=4:sw=4:ft=python + +from django.conf import settings + +import paramiko + +from io import BytesIO + +import asyncore +import inspect +import socket +import threading + + +LISTEN_BACKLOG = 20 + + +def ssh_handler(f): + if not hasattr(settings, 'SSH_HANDLERS'): + settings.SSH_HANDLERS = {} + settings.SSH_HANDLERS[f.__name__] = f + return f + + +class SSHServer(paramiko.ServerInterface): + def __init__(self): + paramiko.ServerInterface.__init__(self) + self._message = None + + def get_allowed_auths(self, username): + return 'publickey' + + def check_auth_publickey(self, username, key): + # for some reason, this is called twice... therefore, we need + # to cache the result since token will be revoked on first use + if self._message: + return paramiko.AUTH_SUCCESSFUL + + spl = username.split('+') + cmd = spl[0] + args = spl[1:] + + try: + h = settings.SSH_HANDLERS[cmd] + # this is an easy way of checking if we have correct args + inspect.getcallargs(h, *args, key=key) + except (KeyError, TypeError) as e: + pass + else: + ret = h(*args, key=key) + if ret is not None: + self._message = ret + return paramiko.AUTH_SUCCESSFUL + return paramiko.AUTH_FAILED + + def check_channel_request(self, kind, chanid): + if kind == 'session': + return paramiko.OPEN_SUCCEEDED + return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED + + def check_channel_subsystem_request(self, channel, name): + return False + + def check_channel_exec_request(self, channel, command): + channel.send('%s\r\n' % self._message) + channel.shutdown(2) + channel.close() + self._message = None + return True + + def check_channel_shell_request(self, channel): + channel.send('%s\r\n' % self._message) + channel.shutdown(2) + channel.close() + self._message = None + return True + + def check_channel_pty_request(self, channel, term, width, height, + pixelwidth, pixelheight, modes): + return True + + +class SSHDispatcher(asyncore.dispatcher): + def __init__(self, server_key): + asyncore.dispatcher.__init__(self) + self._server_key = server_key + + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.set_reuse_addr() + self.bind(settings.SSH_BIND) + self.listen(LISTEN_BACKLOG) + + def handle_accepted(self, conn, addr): + t = paramiko.Transport(conn) + t.add_server_key(self._server_key) + # we need a dummy Event to make it non-blocking + # but we don't really need to play with it + t.start_server(event=threading.Event(), server=SSHServer()) + + # python<3.2 compat + def handle_accept(self): + ret = self.accept() + if ret is not None: + self.handle_accepted(*ret) + + +def ssh_main(): + server_key = paramiko.RSAKey(file_obj=BytesIO(settings.SSH_SERVER_KEY)) + + disp = SSHDispatcher(server_key) + asyncore.loop() + raise SystemError('SSH server loop exited') diff --git a/okupy/settings/__init__.py b/okupy/settings/__init__.py index 767bb22..bdada0a 100644 --- a/okupy/settings/__init__.py +++ b/okupy/settings/__init__.py @@ -28,6 +28,7 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' AUTHENTICATION_BACKENDS = ( 'django_auth_ldap.backend.LDAPBackend', 'okupy.common.auth.SSLCertAuthBackend', + 'okupy.common.auth.SSHKeyAuthBackend', ) MIDDLEWARE_CLASSES = ( @@ -143,6 +144,10 @@ LOGGING = { 'handlers': ['console' if DEBUG else 'syslog'], 'level': 'DEBUG' if DEBUG else 'INFO', }, + 'paramiko': { + 'handlers': ['console' if DEBUG else 'syslog'], + 'level': 'DEBUG' if DEBUG else 'INFO', + } } } diff --git a/okupy/settings/local.py.sample b/okupy/settings/local.py.sample index 245d48a..172b78f 100644 --- a/okupy/settings/local.py.sample +++ b/okupy/settings/local.py.sample @@ -79,3 +79,12 @@ AUTH_LDAP_USER_OBJECTCLASS = ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount', 'shadowAccount'] # additional objectClasses that are used by developers AUTH_LDAP_DEV_OBJECTCLASS = ['developerAccount'] + +# replace with your preferred port +SSH_BIND = ('0.0.0.0', 8022) +# paste your *server* private key here +SSH_SERVER_KEY = ''' +-----BEGIN RSA PRIVATE KEY----- +... +-----END RSA PRIVATE KEY----- +''' diff --git a/okupy/templates/login.html b/okupy/templates/login.html index f444028..aa9e169 100644 --- a/okupy/templates/login.html +++ b/okupy/templates/login.html @@ -37,6 +37,13 @@ </form> </p> {% endif %} + {% if ssh_auth_command %} + <p> + Login via SSH: + <code>{{ ssh_auth_command }}</code> + and <a href="{{ next }}">continue</a> + </p> + {% endif %} {% if not is_otp %} <a href="/recover">Forgot your password?</a> {% endif %} diff --git a/okupy/tests/settings.py b/okupy/tests/settings.py index 316f165..74dfa84 100644 --- a/okupy/tests/settings.py +++ b/okupy/tests/settings.py @@ -28,6 +28,7 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' AUTHENTICATION_BACKENDS = ( 'django_auth_ldap.backend.LDAPBackend', 'okupy.common.auth.SSLCertAuthBackend', + 'okupy.common.auth.SSHKeyAuthBackend', ) MIDDLEWARE_CLASSES = ( @@ -290,3 +291,21 @@ DATABASES['ldap'] = { DATABASE_ROUTERS = ['ldapdb.router.Router'] TEST_RUNNER = 'discover_runner.DiscoverRunner' + +# replace with your preferred port +SSH_BIND = ('0.0.0.0', 8022) +# paste your *server* private key here +SSH_SERVER_KEY = ''' +-----BEGIN RSA PRIVATE KEY----- +MIIBywIBAAJhANlKTt3/R+xVCgQbUNfqVWt28iHXJ+OCtqqVHaiLp9syBEGNiowK +KQZ3swwuaa5WSPFwrsmd1/+REm2HdnQITSF/HSAPtOpbv4xaJ45iX1S4JhOK88c+ +UuBrg+lOeclIHQIDAQABAmEAm9fr0NTzJNGpKWDeDr4HHdhluVezSD3L/XSNnQDt +Fw08eDeoEuCGpBjd1fLD4UIIJHBzc+8ybLZCTgE8JebokHGJxkk8tRt7NgVwnUws +arTKHqb4nOCu6s0re9IkSL0RAjEA9SV9PWk6J+smQQvVWIh6K+PnLrSV9C3LSisK +U8Zfrffly03j5sanjV5ZKx7bRJsXAjEA4ukYNYz3GPqQaYzDP/OObRZwQ2Iema2B +fCbBiokfh4w5Hio+UvbgDD+cBYCjcqbrAjAwjYBEjXbLOTOWZnWW11D7KGQ9R977 +QaalxeiBtyR0HEkS/xZIOsgso6cddzsOV3kCMQCpxK4JOsuRE77CSb+3dDkmYvhh +YeL1JbxQMArz5H4DgyUk7YQtvGmKoHjSIRmo6TsCMH+7CSP/hnzR1GXLwpeQ+dgp +IwszrV7pBZ5anBHnmPqwqBWF/I/0IIrtouUHzs9XHg== +-----END RSA PRIVATE KEY----- +''' diff --git a/okupy/tests/unit/test_auth.py b/okupy/tests/unit/test_auth.py index 27e6618..7c445f6 100644 --- a/okupy/tests/unit/test_auth.py +++ b/okupy/tests/unit/test_auth.py @@ -4,12 +4,17 @@ from mockldap import MockLdap from django.conf import settings from django.contrib.auth import authenticate +from django.test import TestCase from .. import vars -from ...common.test_helpers import OkupyTestCase, set_request +from ...common.test_helpers import ldap_users, set_request +import base64 -class AuthUnitTests(OkupyTestCase): +import paramiko + + +class AuthSSLUnitTests(TestCase): @classmethod def setUpClass(cls): cls.mockldap = MockLdap(vars.DIRECTORY) @@ -60,3 +65,45 @@ class AuthUnitTests(OkupyTestCase): u = authenticate(request=request) self.assertIs(u, None) + + +class AuthSSHUnitTests(TestCase): + @classmethod + def setUpClass(cls): + cls.mockldap = MockLdap(vars.DIRECTORY) + + def setUp(self): + self.mockldap.start() + self.ldapobject = self.mockldap[settings.AUTH_LDAP_SERVER_URI] + + def tearDown(self): + self.mockldap.stop() + + @staticmethod + def get_ssh_key(person, number=0): + keystr = person['sshPublicKey'][number] + return base64.b64decode(keystr.split()[1]) + + def test_valid_rsa_ssh_key_authenticates_alice(self): + dn, alice = ldap_users('alice') + key = paramiko.RSAKey(data=self.get_ssh_key(alice)) + u = authenticate(ssh_key=key) + self.assertEqual(u.username, alice['uid'][0]) + + def test_valid_dss_ssh_key_authenticates_bob(self): + dn, bob = ldap_users('bob') + key = paramiko.DSSKey(data=self.get_ssh_key(bob, 1)) + u = authenticate(ssh_key=key) + self.assertEqual(u.username, bob['uid'][0]) + + def test_valid_rsa_key_with_comment_authenticates_bob(self): + dn, bob = ldap_users('bob') + key = paramiko.RSAKey(data=self.get_ssh_key(bob)) + u = authenticate(ssh_key=key) + self.assertEqual(u.username, bob['uid'][0]) + + def test_unknown_ssh_key_returns_none(self): + key = paramiko.RSAKey( + data=base64.b64decode(vars.TEST_SSH_KEY_FOR_NO_USER)) + u = authenticate(ssh_key=key) + self.assertIs(u, None) diff --git a/okupy/tests/unit/test_ssh.py b/okupy/tests/unit/test_ssh.py new file mode 100644 index 0000000..915be6a --- /dev/null +++ b/okupy/tests/unit/test_ssh.py @@ -0,0 +1,189 @@ +# vim:fileencoding=utf8:et:ts=4:sts=4:sw=4:ft=python + +from django.conf import settings +from django.test import TestCase +from django.test.utils import override_settings + +import base64 +import socket + +import paramiko + +from ..vars import TEST_SSH_KEY_FOR_NO_USER +from ...common.ssh import ssh_handler, SSHServer +from ...common.exceptions import OkupyError + + +@override_settings(SSH_HANDLERS={}) +class SSHUnitTests(TestCase): + def setUp(self): + self._key = paramiko.RSAKey( + data=base64.b64decode(TEST_SSH_KEY_FOR_NO_USER)) + self._server = SSHServer() + + def test_ssh_handler_decorator_works(self): + @ssh_handler + def test(key): + pass + + self.assertEqual(settings.SSH_HANDLERS.get('test'), test) + + def test_noarg_handler_works(self): + @ssh_handler + def noarg(key): + return 'yay' + + self.assertEqual( + self._server.check_auth_publickey('noarg', self._key), + paramiko.AUTH_SUCCESSFUL) + + def test_failure_is_propagated_properly(self): + @ssh_handler + def failing(key): + return None + + self.assertEqual( + self._server.check_auth_publickey('failing', self._key), + paramiko.AUTH_FAILED) + + def test_argument_splitting_works(self): + @ssh_handler + def twoarg(a, b, key): + if a == '1' and b == '2': + return 'yay' + else: + return None + + self.assertEqual( + self._server.check_auth_publickey('twoarg+1+2', self._key), + paramiko.AUTH_SUCCESSFUL) + + def test_default_arguments_work(self): + @ssh_handler + def oneortwoarg(a, b='3', key=None): + if not key: + raise ValueError('key must not be None') + if a == '1' and b == '3': + return 'yay' + else: + return None + + self.assertEqual( + self._server.check_auth_publickey('oneortwoarg+1', self._key), + paramiko.AUTH_SUCCESSFUL) + + def test_wrong_command_returns_failure(self): + @ssh_handler + def somehandler(key): + return 'er?' + + self.assertEqual( + self._server.check_auth_publickey('otherhandler', self._key), + paramiko.AUTH_FAILED) + + def test_missing_arguments_return_failure(self): + @ssh_handler + def onearg(arg, key): + return 'er?' + + self.assertEqual( + self._server.check_auth_publickey('onearg', self._key), + paramiko.AUTH_FAILED) + + def test_too_many_arguments_return_failure(self): + @ssh_handler + def onearg(arg, key): + return 'er?' + + self.assertEqual( + self._server.check_auth_publickey('onearg+1+2', self._key), + paramiko.AUTH_FAILED) + + def test_typeerror_is_propagated_properly(self): + @ssh_handler + def onearg(key): + raise TypeError + + self.assertRaises(TypeError, + self._server.check_auth_publickey, 'onearg', self._key) + + def test_result_caching_works(self): + class Cache(object): + def __init__(self): + self.first = True + + def __call__(self, key): + if self.first: + self.first = False + return 'yay' + else: + return None + + cache = Cache() + @ssh_handler + def cached(key): + return cache(key) + + if (self._server.check_auth_publickey('cached', self._key) + != paramiko.AUTH_SUCCESSFUL): + raise OkupyError('Test prerequisite failed') + self.assertEqual( + self._server.check_auth_publickey('cached', self._key), + paramiko.AUTH_SUCCESSFUL) + + def test_message_is_printed_to_exec_request(self): + @ssh_handler + def noarg(key): + return 'test-message' + + if (self._server.check_auth_publickey('noarg', self._key) + != paramiko.AUTH_SUCCESSFUL): + raise OkupyError('Test prerequisite failed') + + s1, s2 = socket.socketpair() + self.assertTrue(self._server.check_channel_exec_request(s1, ':')) + self.assertEqual(s2.makefile().read().rstrip(), 'test-message') + + def test_message_is_printed_to_shell_request(self): + @ssh_handler + def noarg(key): + return 'test-message' + + if (self._server.check_auth_publickey('noarg', self._key) + != paramiko.AUTH_SUCCESSFUL): + raise OkupyError('Test prerequisite failed') + + s1, s2 = socket.socketpair() + self.assertTrue(self._server.check_channel_shell_request(s1)) + self.assertEqual(s2.makefile().read().rstrip(), 'test-message') + + def test_cache_is_invalidated_after_channel_request(self): + class Cache(object): + def __init__(self): + self.first = True + + def __call__(self, key): + if self.first: + self.first = False + return 'test-message' + else: + return None + + cache = Cache() + @ssh_handler + def cached(key): + return cache(key) + + if (self._server.check_auth_publickey('cached', self._key) + != paramiko.AUTH_SUCCESSFUL): + raise OkupyError('Test prerequisite failed') + + s1, s2 = socket.socketpair() + if not self._server.check_channel_shell_request(s1): + raise OkupyError('Test prerequisite failed') + if s2.makefile().read().rstrip() != 'test-message': + raise OkupyError('Test prerequisite failed') + + self.assertEqual( + self._server.check_auth_publickey('cached', self._key), + paramiko.AUTH_FAILED) diff --git a/okupy/tests/vars.py b/okupy/tests/vars.py index 44270a6..c2e9152 100644 --- a/okupy/tests/vars.py +++ b/okupy/tests/vars.py @@ -32,6 +32,7 @@ DIRECTORY = { "gentooRoles": ["kde, qt, cluster"], "gentooLocation": ["City1, Country1"], "gentooACL": ["user.group", "developer.group"], + "sshPublicKey": ["ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCbtxfr9vRO4xkDuUnsu02rL7BtBiABADkWdugnMxRAV6nKokitytgLGDhjY6iB8C87K8mCxz/ksMO+uct/lUEHMf1M2P1rPEStrJoXQuTXQbtVl7iF5cySbXhtd7Nu7DcXe1cIynVkbFosB2mznr8Db3633DnEslppUGvHdjHYoCAWsjv5juHESkBy62HhYgc1ZoGFj6ilrJhOdHs2ji2YBHJXPG2sB3uQleY5/KfAeSwESBH7D36VqRXf22Ya0nExnVh3h9jtzZmwIll35VHH/G9NmTmW/8lpl7BGV7fx10tByfvSLrQg2ZniiY3SfXdbraVm/FEuJ9+X81jpNQDd", "invalid-key-too-short", "ssh-rsa $$$INVALID%", "invalid-key-type AAAA=="], }, "uid=bob,ou=people,o=test": { "uid": ["bob"], @@ -45,7 +46,8 @@ DIRECTORY = { "mail": ["bob@test.com"], "gentoRoles": ["nothing"], "gentooLocation": ["City2, Country2"], - "gentooACL": ["user.group", "foundation.group"] + "gentooACL": ["user.group", "foundation.group"], + "sshPublicKey": ["ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUSOgwQ6uljefD9BiwhiGzRGn+sg7D3AKcqU8PWrB+p74n9GBIccc/iSuG458iid08FvUqHjY0RLwMQADND7NOGaEEW0NXbyblA6xZhZu6BgnFC4LZBHy5eok+sWIZddAgT8qAYXMW8GYzUZSPchtOFbMkyzaQlWYkjx1Z0usOdnl/QRPuabFTQjWtJ+lw8hrPydl1ZYP+FIUZy9NU/SxC2qgufmh3+nTzfnfQgupfQc6I9lXNR98vm/t5saVsuQReIIc4sR3mOmT5AnH6uCyjRBKnxq8ndcInfGagwpcx80o6+/V0QNIdr5NP1jRiXDbc/BT8NP/X4mWIpJNEIujj bob@example.com", "ssh-dss AAAAB3NzaC1kc3MAAACBAOpXehglYVU5efZoBGrRKHcsQvlS4jDAFGgsqNRQwM4F7anFIhaEYxs8REEhKNOUXEalFCUegtBxgKjvNRH+MBMJ5o6BAsDuTobwhFS7imcj5JO7QA6kfyNokNkULbqCOfmS9xmFozj2bk0zpKcvW54Zf91dHHT+NsmAXrcIw1onAAAAFQDLARFN4O0wquVKl/XGItngEeQGdwAAAIEAtTP8JkR9XZHkqb0s/uRA+2Wh9uOipc1+IgJn+UX15or2/zuudcG5loaVpDepuLuzhjrn/BZwj1GAncv/AFo4YraATU77HxNEXstHwkf5K8FaJ2f/6bVs7i/P9NS9rXys+HdOiPmAbvv9Hm69jw/Xbwnz752O7gvSNJPWjrC2460AAACBAItwlTJ2aUD7BSgjgaqGOrjUamnIMOi833RCc2XN9F9aY2z8DNr3O7KN5qzTUuLU4ltQbBO9Ct5CZmx785COTkJMXjoYVC7ObfKc8T0xB1FZzf7bIaqcC0dDmfrCzmcQdOTIJvKNlniRBG1XAQ7lf7YvX0We+C14oVU2FhyueoEe", "invalid-key-too-short", "ssh-rsa $$$INVALID%", "invalid-key-type AAAA=="], }, "uid=jack,ou=people,o=test": { "uid": ["jack"], @@ -147,3 +149,5 @@ gmp1MA0GCSqGSIb3DQEBBQUAA2EAH+Qaz/Dmd5QqU1pVgPUz2loWQhy+cX6bgubJ vj3k/SSqj6qjnxryY6QSKWOTRbKhwmRHrrsFRuR2rCZWYZUJ6ohCDYrwVKvs7i2R VNG3Q7+oqLajmyDfZmHkENQ0rCdc -----END CERTIFICATE-----''' + +TEST_SSH_KEY_FOR_NO_USER = 'AAAAB3NzaC1yc2EAAAADAQABAAAAYQCXMUpwxMi/01Th94+pP9r3bPGOEejSic7eH1VXHnqHPRFh9rOenSbhWLXwCUcM+0ZMoLmkJ3gMz3IKq2HTJfEwBcW/v/cm5b2lT6biO0u9Q5br4KosNhrvJBZ0f6trkCk=' diff --git a/okupy/wsgi.py b/okupy/wsgi.py index 306104d..e2c657e 100644 --- a/okupy/wsgi.py +++ b/okupy/wsgi.py @@ -41,9 +41,21 @@ except ImportError: # we're probably running from django's built-in server pass else: - from uwsgidecorators import timer + from uwsgidecorators import postfork, thread, timer from django.utils import autoreload + # autodiscover SSH handlers + import okupy.accounts.ssh + from okupy.common.ssh import ssh_main + + import Crypto.Random + + postfork(thread(ssh_main)) + + @postfork + def reset_rng(): + Crypto.Random.atfork() + @timer(5) def change_code_gracefull_reload(sig): if autoreload.code_changed(): diff --git a/requirements.txt b/requirements.txt index 11c4030..710459a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ django-otp>=0.1.7 git+https://github.com/tampakrap/django-ldapdb@okupy#egg=django-ldapdb mock>=1.0.1 hg+https://bitbucket.org/psagers/mockldap#egg=mockldap +paramiko>=1.10.1 passlib>=1.6.1 pycrypto>=2.6 pyopenssl>=0.13 @@ -38,6 +38,7 @@ setup( 'django-compressor>=1.3', 'django-ldapdb', 'django-otp>=0.1.7', + 'paramiko>=1.10.1', 'passlib>=1.6.1', 'pycrypto>=2.6', 'pyopenssl>=0.13', |