From 6c26218d3d881c4c2609046a298245556dfa8d6c Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Feb 15 2019 13:55:15 +0000 Subject: Add support for "kerberos_or_ssl" auth the same way as we do in ODCS. This is basically copy-paste of ODCS code which has been introduced in https://pagure.io/odcs/pull-request/132. The code is taken from ODCS master. We need SSL client auth in order to allow monitoring using New Relic which does not support keytabs. --- diff --git a/freshmaker/auth.py b/freshmaker/auth.py index 997af60..df018fb 100644 --- a/freshmaker/auth.py +++ b/freshmaker/auth.py @@ -88,6 +88,41 @@ def load_krb_user_from_request(request): return user +@commit_on_success +def load_ssl_user_from_request(request): + """ + Loads SSL user from current request. + + SSL_CLIENT_VERIFY and SSL_CLIENT_S_DN needs to be set in + request.environ. This is set by frontend httpd mod_ssl module. + """ + ssl_client_verify = request.environ.get('SSL_CLIENT_VERIFY') + if ssl_client_verify != 'SUCCESS': + raise Unauthorized('Cannot verify client: %s' % ssl_client_verify) + + username = request.environ.get('SSL_CLIENT_S_DN') + if not username: + raise Unauthorized('Unable to get user information (DN) from client certificate') + + user = User.find_user_by_name(username) + if not user: + user = User.create_user(username=username) + + g.groups = [] + g.user = user + return user + + +def load_krb_or_ssl_user_from_request(request): + """ + Loads User using Kerberos or SSL auth. + """ + if request.environ.get('REMOTE_USER'): + return load_krb_user_from_request(request) + else: + return load_ssl_user_from_request(request) + + def query_ldap_groups(uid): client = ldap.initialize(conf.auth_ldap_server) groups = client.search_s(conf.auth_ldap_group_base, @@ -195,6 +230,15 @@ def init_auth(login_manager, backend): elif backend == 'openidc': global load_openidc_user load_openidc_user = login_manager.request_loader(load_openidc_user) + elif backend == 'kerberos_or_ssl': + _validate_kerberos_config() + global load_krb_or_ssl_user_from_request + load_krb_or_ssl_user_from_request = login_manager.request_loader( + load_krb_or_ssl_user_from_request) + elif backend == 'ssl': + global load_ssl_user_from_request + load_ssl_user_from_request = login_manager.request_loader( + load_ssl_user_from_request) else: raise ValueError('Unknown backend name {0}.'.format(backend)) diff --git a/tests/test_auth.py b/tests/test_auth.py index 30ad69d..8a183e4 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -33,11 +33,113 @@ from freshmaker.auth import init_auth from freshmaker.auth import load_krb_user_from_request from freshmaker.auth import load_openidc_user from freshmaker.auth import query_ldap_groups +from freshmaker.auth import load_krb_or_ssl_user_from_request +from freshmaker.auth import load_ssl_user_from_request from freshmaker import app, db from freshmaker.models import User from tests.helpers import ModelsTestCase, FreshmakerTestCase +class TestLoadSSLUserFromRequest(ModelsTestCase): + + def setUp(self): + super(TestLoadSSLUserFromRequest, self).setUp() + + self.user = User(username='CN=tester1,L=prod,DC=example,DC=com') + db.session.add(self.user) + db.session.commit() + + def test_create_new_user(self): + environ_base = { + 'SSL_CLIENT_VERIFY': 'SUCCESS', + 'SSL_CLIENT_S_DN': 'CN=client,L=prod,DC=example,DC=com', + } + + with app.test_request_context(environ_base=environ_base): + load_ssl_user_from_request(flask.request) + + expected_user = db.session.query(User).filter( + User.username == 'CN=client,L=prod,DC=example,DC=com')[0] + + self.assertEqual(expected_user.id, flask.g.user.id) + self.assertEqual(expected_user.username, flask.g.user.username) + + # Ensure user's groups are set to empty list + self.assertEqual(0, len(flask.g.groups)) + + def test_return_existing_user(self): + environ_base = { + 'SSL_CLIENT_VERIFY': 'SUCCESS', + 'SSL_CLIENT_S_DN': self.user.username, + } + + with app.test_request_context(environ_base=environ_base): + load_ssl_user_from_request(flask.request) + + self.assertEqual(self.user.id, flask.g.user.id) + self.assertEqual(self.user.username, flask.g.user.username) + + # Ensure user's groups are set to empty list + self.assertEqual(0, len(flask.g.groups)) + + def test_401_if_ssl_client_verify_not_success(self): + environ_base = { + 'SSL_CLIENT_VERIFY': 'GENEROUS', + 'SSL_CLIENT_S_DN': self.user.username, + } + + with app.test_request_context(environ_base=environ_base): + with self.assertRaises(Unauthorized) as ctx: + load_ssl_user_from_request(flask.request) + self.assertIn('Cannot verify client: GENEROUS', + ctx.exception.description) + + def test_401_if_cn_not_set(self): + environ_base = { + 'SSL_CLIENT_VERIFY': 'SUCCESS', + } + + with app.test_request_context(environ_base=environ_base): + with self.assertRaises(Unauthorized) as ctx: + load_ssl_user_from_request(flask.request) + self.assertIn('Unable to get user information (DN) from client certificate', + ctx.exception.description) + + +class TestLoadKrbOrSSLUserFromRequest(ModelsTestCase): + + @patch("freshmaker.auth.load_ssl_user_from_request") + @patch("freshmaker.auth.load_krb_user_from_request") + def test_load_krb_or_ssl_user_from_request_remote_user( + self, load_krb_user, load_ssl_user): + load_krb_user.return_value = "krb_user" + load_ssl_user.return_value = "ssl_user" + + environ_base = { + 'REMOTE_USER': 'newuser@EXAMPLE.COM' + } + + with app.test_request_context(environ_base=environ_base): + user = load_krb_or_ssl_user_from_request(flask.request) + self.assertEqual(user, "krb_user") + + @patch("freshmaker.auth.load_ssl_user_from_request") + @patch("freshmaker.auth.load_krb_user_from_request") + def test_load_krb_or_ssl_user_from_request_ssl_client( + self, load_krb_user, load_ssl_user): + load_krb_user.return_value = "krb_user" + load_ssl_user.return_value = "ssl_user" + + environ_base = { + 'SSL_CLIENT_VERIFY': 'SUCCESS', + 'SSL_CLIENT_S_DN': 'ssl_user', + } + + with app.test_request_context(environ_base=environ_base): + user = load_krb_or_ssl_user_from_request(flask.request) + self.assertEqual(user, "ssl_user") + + class TestLoadKrbUserFromRequest(ModelsTestCase): @patch('freshmaker.auth.query_ldap_groups') @@ -227,6 +329,16 @@ class TestInitAuth(FreshmakerTestCase): self.login_manager.request_loader.assert_called_once_with( load_openidc_user) + def test_select_ssl_auth_backend(self): + init_auth(self.login_manager, 'ssl') + self.login_manager.request_loader.assert_called_once_with( + load_ssl_user_from_request) + + def test_select_kerberos_or_ssl_auth_backend(self): + init_auth(self.login_manager, 'kerberos_or_ssl') + self.login_manager.request_loader.assert_called_once_with( + load_krb_or_ssl_user_from_request) + def test_not_use_auth_backend(self): init_auth(self.login_manager, 'noauth') self.login_manager.request_loader.assert_not_called()