From 7d8d1bab56ec24bc7fa9ca06cd2e29e35b779e28 Mon Sep 17 00:00:00 2001 From: Tanner Prestegard <tanner.prestegard@ligo.org> Date: Thu, 14 Feb 2019 14:19:08 -0600 Subject: [PATCH] Adding cert info based authentication backend for API Traefik can pass the subject and issuer in a 'cert-infos' header, so I am adding an authentication backend to use that. --- config/settings/base.py | 1 + gracedb/api/backends.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/config/settings/base.py b/config/settings/base.py index ed24bd04e..affca13cf 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -272,6 +272,7 @@ SHIB_ATTRIBUTE_MAP = { X509_SUBJECT_DN_HEADER = 'HTTP_SSL_CLIENT_S_DN' X509_ISSUER_DN_HEADER = 'HTTP_SSL_CLIENT_I_DN' X509_CERT_HEADER = 'X_FORWARDED_TLS_CLIENT_CERT' +X509_INFOS_HEADER = 'X_FORWARDED_TLS_CLIENT_CERT_INFOS' # List of authentication backends to use when attempting to authenticate # a user. Will be used in this order. Authentication for the API is diff --git a/gracedb/api/backends.py b/gracedb/api/backends.py index e8c067d78..ba273c6ff 100644 --- a/gracedb/api/backends.py +++ b/gracedb/api/backends.py @@ -161,6 +161,47 @@ class GraceDbX509Authentication(authentication.BaseAuthentication): return (user, None) +class GraceDbX509CertInfosAuthentication(GraceDbX509Authentication): + """ + Authentication based on X509 "infos" header. + Certificate should be verified by Traefik already. + """ + api_only = True + infos_header = getattr(settings, 'X509_INFOS_HEADER', + 'X_FORWARDED_TLS_CLIENT_CERT_INFOS') + infos_pattern = re.compile(r'Subject="(.*?)".*Issuer="(.*?)"') + + @classmethod + def get_cert_dn_from_request(cls, request): + """Get SSL headers and return subject for user""" + + # Get infos from request headers + infos = request.META.get(cls.infos_header, None) + + # Unquote (handle pluses -> spaces) + infos_unquoted = unquote_plus(infos) + + # Extract subject and issuer + subject, issuer = cls.infos_pattern.search(infos_unquoted).groups() + + # Convert formats + subject = cls.convert_format(subject) + issuer = cls.convert_format(issuer) + + # Handled proxied certificates + subject = cls.extract_subject_from_proxied_cert(subject, issuer) + + return subject + + @staticmethod + def convert_format(s): + # Convert subject or issuer strings from comma to slash format + s = s.replace(',', '/') + if not s.startswith('/'): + s = '/' + s + return s + + class GraceDbX509FullCertAuthentication(GraceDbX509Authentication): """ Authentication based on a full X509 certificate. We verify the -- GitLab