Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for client SSL certificate authentication #79

Merged
merged 1 commit into from Dec 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 37 additions & 8 deletions cachito/web/auth.py
Expand Up @@ -18,14 +18,45 @@ def user_loader(username):
return User.query.filter_by(username=username).first()


def _get_kerberos_principal(request):
"""
Get the Kerberos principal from the current request.

This relies on the "REMOTE_USER" environment variable being set. This is usually set by the
mod_auth_gssapi Apache authentication module.

:param flask.Request request: the Flask request
:return: the user's Kerberos principal or None
:rtype: str
"""
return request.environ.get('REMOTE_USER')


def _get_cert_dn(request):
"""
Get the client certificate's subject's distinguished name.

This relies on the "SSL_CLIENT_S_DN" environment variable being set. This is set by the mod_ssl
Apache module. If Apache is unable to verify the client certificate, no user will be returned.

:param flask.Request request: the Flask request
:return: the client certificate's subject's distinguished name or None
:rtype: str
"""
ssl_client_verify = request.environ.get('SSL_CLIENT_VERIFY')
lcarva marked this conversation as resolved.
Show resolved Hide resolved
if ssl_client_verify != 'SUCCESS':
current_app.logger.debug(
'The SSL_CLIENT_VERIFY environment variable was set to %s', ssl_client_verify
)
return

return request.environ.get('SSL_CLIENT_S_DN')


def load_user_from_request(request):
"""
Load the user that authenticated from the current request.

When authentication is turned on (default in production), this relies on the "REMOTE_USER"
environment variable being set. This is usually set by the mod_auth_gssapi Apache authentication
module.

This is used by the Flask-Login library. If the user does not exist in the database, an entry
will be created.

Expand All @@ -37,18 +68,16 @@ def load_user_from_request(request):
:return: the User object associated with the username or None
:rtype: cachito.web.models.User
"""
remote_user = request.environ.get('REMOTE_USER')
if not remote_user:
username = _get_kerberos_principal(request) or _get_cert_dn(request)
if not username:
if current_app.config.get('LOGIN_DISABLED', False) is True:
current_app.logger.info(
'The REMOTE_USER environment variable wasn\'t set on the request, but the '
'LOGIN_DISABLED configuration is set to True.'
)
return

username = remote_user
current_app.logger.info(f'The user "{username}" was authenticated successfully by httpd')

user = User.get_or_create(username)
if not user.id:
db.session.commit()
Expand Down
8 changes: 8 additions & 0 deletions tests/conftest.py
Expand Up @@ -42,6 +42,14 @@ def auth_env():
return {'REMOTE_USER': 'tbrady@DOMAIN.LOCAL'}


@pytest.fixture(scope='session')
def auth_ssl_env():
return {
'SSL_CLIENT_S_DN': 'CN=tbrady,OU=serviceusers,DC=domain,DC=local',
'SSL_CLIENT_VERIFY': 'SUCCESS',
}


@pytest.fixture()
def client(app):
"""Return Flask application client for the pytest session."""
Expand Down
15 changes: 15 additions & 0 deletions tests/test_api_v1.py
Expand Up @@ -83,6 +83,21 @@ def test_create_and_fetch_request(
assert fetched_request['state_reason'] == 'The request was initiated'


@mock.patch('cachito.web.api_v1.chain')
def test_create_request_ssl_auth(mock_chain, auth_ssl_env, client, db):
data = {
'repo': 'https://github.com/release-engineering/retrodep.git',
'ref': 'c50b93a32df1c9d700e3e80996845bc2e13be848',
}

rv = client.post('/api/v1/requests', json=data, environ_base=auth_ssl_env)
assert rv.status_code == 201
created_request = rv.json

cert_dn = 'CN=tbrady,OU=serviceusers,DC=domain,DC=local'
assert created_request['user'] == cert_dn


@mock.patch('cachito.web.api_v1.chain')
def test_create_and_fetch_request_with_flag(mock_chain, app, auth_env, client, db):
data = {
Expand Down