Skip to content

Commit

Permalink
SAML configuration for Varfish
Browse files Browse the repository at this point in the history
Co-authored-by: Fabian Brand <brand@imbie.meb.uni-bonn.de>
  • Loading branch information
Fabian Brand authored and holtgrewe committed Jun 17, 2021
1 parent bde924d commit 871be7b
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 17 deletions.
8 changes: 8 additions & 0 deletions HISTORY.rst
Expand Up @@ -6,9 +6,17 @@ History / Changelog
HEAD (unreleased)
-----------------

End-User Summary
================

- Added SAML Login possibility from sodar-core to varfish

Full Change List
================

- Added sso urls to config/urls.py
- Added SAML configuration to config/settings/base.py
- Added necessary tools to the Dockerfile
- Fix for missing PROJECTROLES_DISABLE_CATEGORIES variable in settings.

-------
Expand Down
80 changes: 79 additions & 1 deletion config/settings/base.py
Expand Up @@ -82,6 +82,7 @@
"encrypted_model_fields",
"rest_framework_httpsignature",
"webpack_loader",
"django_saml2_auth",
]

# Apps specific for this project go here.
Expand Down Expand Up @@ -689,7 +690,6 @@ def set_logging(debug):
)
)


# URL to redirect after the login.
# Default: "/"
DJANGO_SU_LOGIN_REDIRECT_URL = "/"
Expand All @@ -708,6 +708,84 @@ def set_logging(debug):
DJANGO_SU_CUSTOM_LOGIN_ACTION = None


# SAML configuration
# ------------------------------------------------------------------------------


ENABLE_SAML = env.bool("ENABLE_SAML", False)
SAML2_AUTH = {
# Required setting
#
# Pysaml2 Saml client settings, cf.
# https://pysaml2.readthedocs.io/en/latest/howto/config.html
"SAML_CLIENT_SETTINGS": {
# The optional entity ID string to be passed in the 'Issuer'
# element of authn request, if required by the IDP.
"entityid": env.str("SAML_CLIENT_ENTITY_ID", "SODARcore"),
"entitybaseurl": env.str("SAML_CLIENT_ENTITY_URL", "https://localhost:8000"),
"metadata": {
"local": [
env.str(
"SAML_CLIENT_METADATA_FILE", "metadata.xml"
), # The auto(dynamic) metadata configuration URL of SAML2
],
},
"service": {
"sp": {
"idp": env.str("SAML_CLIENT_IDP", "https://sso.hpc.bihealth.org/auth/realms/cubi",),
# Keycloak expects client signature
"authn_requests_signed": "true",
# Enforce POST binding which is required by keycloak
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
},
},
"key_file": env.str("SAML_CLIENT_KEY_FILE", "key.pem"),
"cert_file": env.str("SAML_CLIENT_CERT_FILE", "cert.pem"),
"xmlsec_binary": env.str("SAML_CLIENT_XMLSEC1", "/usr/bin/xmlsec1"),
"encryption_keypairs": [
{
"key_file": env.str("SAML_CLIENT_KEY_FILE", "key.pem"),
"cert_file": env.str("SAML_CLIENT_CERT_FILE", "cert.pem"),
}
],
},
# Custom target redirect URL after the user get logged in. Default to
# /admin if not set. This setting will be overwritten if you have parameter
# ?next= specificed in the login URL.
"DEFAULT_NEXT_URL": "/",
# Optional settings
"NEW_USER_PROFILE": {
"USER_GROUPS": env.list("SAML_NEW_USER_GROUPS", default=[]),
"ACTIVE_STATUS": env.bool("SAML_NEW_USER_ACTIVE_STATUS", True),
"STAFF_STATUS": env.bool("SAML_NEW_USER_STAFF_STATUS", True),
"SUPERUSER_STATUS": env.bool("SAML_NEW_USER_SUPERUSER_STATUS", False),
},
"ATTRIBUTES_MAP": env.dict(
"SAML_ATTRIBUTES_MAP",
default={
"email": "urn:oid:1.2.840.113549.1.9.1",
"username": "username",
"first_name": "urn:oid:2.5.4.42",
"last_name": "urn:oid:2.5.4.4",
},
),
# Optional SAML Trigger
# Very unlikely to be needed in configuration, since it requires
# changes to the codebase
# 'TRIGGER': {
# 'FIND_USER': 'path.to.your.find.user.hook.method',
# 'NEW_USER': 'path.to.your.new.user.hook.method',
# 'CREATE_USER': 'path.to.your.create.user.hook.method',
# 'BEFORE_LOGIN': 'path.to.your.login.hook.method',
# },
}

# 'ASSERTION_URL': 'https://your.url.here', # Custom URL to validate incoming SAML requests against
assertion_url = env.str("SAML_ASSERTION_URL", None)
if assertion_url is not None:
SAML2_AUTH = {**SAML2_AUTH, **{"ASSERTION_URL": assertion_url}}


# STORAGE CONFIGURATION
# ------------------------------------------------------------------------------

Expand Down
23 changes: 21 additions & 2 deletions config/urls.py
Expand Up @@ -7,6 +7,7 @@
from django.views.generic import TemplateView
from django.views import defaults as default_views

import django_saml2_auth.views
from httpproxy.views import HttpProxy
from projectroles.views import HomeView as ProjectRolesHomeView
from variants.views import KioskHomeView
Expand All @@ -17,15 +18,33 @@ def handler500(request, *args, **argv):
return render(request, "500.html", {"sentry_event_id": last_event_id()}, status=500)


urlpatterns = [
# These are the SAML2 related URLs. You can change "^saml2_auth/" regex to
# any path you want, like "^sso_auth/", "^sso_login/", etc. (required)
url(r"^saml2_auth/", include("django_saml2_auth.urls")),
# The following line will replace the default user login with SAML2 (optional)
# If you want to specific the after-login-redirect-URL, use parameter "?next=/the/path/you/want"
# with this view.
url(r"^sso/login/$", django_saml2_auth.views.signin),
# The following line will replace the admin login with SAML2 (optional)
# If you want to specific the after-login-redirect-URL, use parameter "?next=/the/path/you/want"
# with this view.
url(r"^sso/admin/login/$", django_saml2_auth.views.signin),
# The following line will replace the default user logout with the signout page (optional)
url(r"^sso/logout/$", django_saml2_auth.views.signout),
# The following line will replace the default admin user logout with the signout page (optional)
url(r"^sso/admin/logout/$", django_saml2_auth.views.signout),
]

# The functionality differs greatly depending on whether kiosk mode is enabled or not. However, the URL patterns
# do not need to.
if settings.KIOSK_MODE:
urlpatterns = [
urlpatterns += [
url(r"^$", KioskHomeView.as_view(), name="kiosk-upload"),
url(r"^real-home/$", ProjectRolesHomeView.as_view(), name="home"),
]
else:
urlpatterns = [url(r"^$", ProjectRolesHomeView.as_view(), name="home")]
urlpatterns += [url(r"^$", ProjectRolesHomeView.as_view(), name="home")]
HomeView = ProjectRolesHomeView

urlpatterns += [
Expand Down
3 changes: 2 additions & 1 deletion docker/Dockerfile
Expand Up @@ -25,7 +25,8 @@ RUN apt-get update && \
libsasl2-dev \
make \
postgresql-client \
wget
wget \
xmlsec1

# Install Python dependencies.
RUN cd /usr/src/app && \
Expand Down
60 changes: 60 additions & 0 deletions docs_manual/admin_config.rst
Expand Up @@ -63,6 +63,66 @@ If you have the first LDAP configured then you can also enable the second one an

The remaining variable names are derived from the ones of the primary server but using the prefix ``AUTH_LDAP2`` instead of ``AUTH_LDAP``.

------------------
SAML Configuration
------------------

Besides LDAP configuration, it is also possible to authenticate with existing SAML 2.0 ID Providers (e.g. Keycloak). Since varfish is built
on top of sodar core, you can also refer to the `sodar-core documentation <https://sodar-core.readthedocs.io/en/latest/app_projectroles_settings.html#saml-sso-configuration-optional>`__ for further help in configuring the ID Providers.

To enable SAML authentication with your ID Provider, a few steps are necessary. First, add a SAML Client for your ID Provider of choice. The sodar-core documentation features examples for Keycloak. Make sure you have assertion signing turned on and allow redirects to your varfish site.
The SAML processing URL should be set to the externally visible address of your varfish deployment, e.g. ``https://varfish.example.com/saml2_auth/acs/``.

Next, you need to obtain your metadata.xml aswell as the signing certificate and key file from the ID Provider. Make sure you convert these keys to standard OpenSSL
format, before starting your varfish instance (you can find more details `here <https://sodar-core.readthedocs.io/en/latest/app_projectroles_settings.html#saml-sso-configuration-optional>`__).
If you deploy varfish without docker, you can pass the file paths of your metadata.xml and key pair directly. Otherwise, make sure that you have included them
into a single folder and added the corresponding folder to your ``docker-compose.yml`` (or add it as a ``docker-compose-overrrided.yml``), like in the following snippet.

.. code-block:: yml
varfish-web:
...
volumes:
- "/path/to/my/secrets:/secrets:ro"
Then, define atleast the following variables in your docker-compose ``.env`` file (or the environment variables when running the server natively).

``ENABLE_SAML``
[Default 0] Enable [1] or Disable [0] SAML authentication
``SAML_CLIENT_ENTITY_ID``
The SAML client ID set in the ID Provider config (e.g. "varfish")
``SAML_CLIENT_ENTITY_URL``
The externally visible URL of your varfish deployment
``SAML_CLIENT_METADATA_FILE``
The path to the metadata.xml file retrieved from your ID Provider. If you deploy using docker, this must be a path inside the container.
``SAML_CLLIENT_IDP``
The url to your IDP. In case of keycloak it can look something like https://keycloak.example.com/auth/realms/<my_varfish_realm>
``SAML_CLIENT_KEY_FILE``
Path to the SAML signing key for the client.
``SAML_CLIENT_CERT_FILE``
Path to the SAML certificate for the client.
``SAML_CLIENT_XMLSEC1``
[Default /usr/bin/xmlsec1] Path to the xmlsec executable.

By default, the SAML attributes map is configured to work with Keycloak as SAML Auth provider. If you are using a different ID Provider,
or different settings you also need to adjust the ``SAML_ATTRIBUTES_MAP`` option.

``SAML_ATTRIBUTES_MAP``
A dictionary identifying the SAML claims needed to retrieve user information. You need to set atleast ``email``, ``username``, ``first_name`` and ``last_name``. Example: ``SAML_ATTRIBUTES_MAP="email=email,username=uid,first_name=firstName,last_name=name"``

To set initial user permissions on first login, you can use the following options:

``SAML_NEW_USER_GROUPS``
Comma separated list of groups for a new user to join.
``SAML_NEW_USER_ACTIVE_STATUS``
[Default True] Whether a new user is considered active.
``SAML_NEW_USER_STAFF_STATUS``
[Default True] New users get the staff status.
``SAML_NEW_USER_SUPERUSER_STATUS``
[Default False] New users are marked superusers (I advise leaving this one alone).

If you encounter any troubles with this rather involved procedure, feel free to take a look at the discussion forums on `github<https://github.com/bihealth/varfish-server/discussions>`__ and open a thread.

-----------------
Sending of Emails
-----------------
Expand Down
38 changes: 25 additions & 13 deletions varfish/templates/users/login.html
Expand Up @@ -9,16 +9,17 @@
{% block title %}Login{% endblock title %}

{% block content %}

<div class="container-fluid">

{# Django messages / site app messages #}
{% include 'projectroles/_messages.html' %}

{# Display error if login was unsuccessful. #}
{% if request.POST %}
<div class="alert alert-danger sodar-alert-top">
Login failed. Please make sure the user name, domain and password are correct.
<a href="#" class="pull-right sodar-alert-close-link"><i class="fa fa-close text-muted"></i></a>
<div class="alert alert-danger alert-dismissable fade show sodar-alert-top">
Login failed. Please make sure the user name, domain and password are correct.
<a href="#" data-dismiss="alert" class="pull-right sodar-alert-close-link"><i class="fa fa-close text-muted"></i></a>
</div>
{% endif %}

Expand All @@ -33,16 +34,27 @@ <h2 class="sodar-pr-content-title">Log In</h2>

<form class="form-signin" method="post" id="sodar-form-login">
{% csrf_token %}
<input type="text" name="username" id="sodar-login-username" class="form-control" placeholder="username@DOMAIN" required autofocus>
<input type="password" name="password" id="sodar-login-password" class="form-control my-sm-2 mb-sm-2" placeholder="Password" required>
<button class="btn btn-md btn-primary btn-block" type="submit" id="sodar-login-submit"><i class="fa fa-sign-in"></i> Log In</button>
{% get_django_setting 'ENABLE_LDAP' as enable_ldap %}
<input type="text" name="username" id="sodar-login-username" class="form-control"
placeholder="{% if enable_ldap %}username@DOMAIN{% else %}username{% endif %}"
required autofocus>
<input type="password" name="password" id="sodar-login-password"
class="form-control my-sm-2 mb-sm-2" placeholder="password"
required>
<button class="btn btn-md btn-primary btn-block" type="submit" id="sodar-login-submit">
<i class="fa fa-sign-in"></i> Log In
</button>
</form>

<p class="text-muted text-right mt-3">
<small>
VarFish v{% site_version %} - For research use only.
</small>
</p>
{% get_django_setting 'ENABLE_SAML' as enable_saml %}
{% if enable_saml %}
<hr class="my-3" />
<p>To log in with your SSO provider, please click below.</p>
<a href="/sso/login"
class="btn btn-md btn-info btn-block">
<i class="fa fa-sign-in"></i> Single Sign-On
</a>
{% endif %}
</div>
</div>
{% endblock content %}

{% endblock content %}

0 comments on commit 871be7b

Please sign in to comment.