Gitlab will migrate to a new storage backend starting 0300 UTC on 2020-04-04. We do not anticipate a maintenance window for this migration. Performance may be impacted over the weekend. Thanks for your patience.

Commit 4ee31f4d authored by Tanner Prestegard's avatar Tanner Prestegard Committed by GraceDB

Add API backend for full cert verify and auth

New API backend which gets a full X509 certificate, verifies it,
and extracts the subject.  To be used in the cloud deployment
with Traefik.
parent c29061a2
......@@ -262,6 +262,7 @@ SHIB_ATTRIBUTE_MAP = {
# Headers to use for X509 authentication
# List of authentication backends to use when attempting to authenticate
# a user. Will be used in this order. Authentication for the API is
......@@ -49,6 +49,13 @@ DATABASES = {
# Main server "hostname" - a little hacky but OK
# Use full client certificate to authenticate
# Update allowed hosts from environment variables -----------------------------
hosts_from_env = os.environ.get('DJANGO_ALLOWED_HOSTS', None)
if hosts_from_env is not None:
import base64
import logging
import OpenSSL.crypto
import OpenSSL.SSL
import re
import urlparse
from django.contrib.auth import get_user_model, authenticate
from django.conf import settings
......@@ -54,6 +58,10 @@ class GraceDbBasicAuthentication(authentication.BasicAuthentication):
class GraceDbX509Authentication(authentication.BaseAuthentication):
Authentication based on X509 certificate subject.
Certificate should be verified by Apache already.
api_only = True
www_authenticate_realm = 'api'
subject_dn_header = getattr(settings, 'X509_SUBJECT_DN_HEADER',
......@@ -122,6 +130,90 @@ class GraceDbX509Authentication(authentication.BaseAuthentication):
return (user, None)
class GraceDbX509FullCertAuthentication(GraceDbX509Authentication):
Authentication based on a full X509 certificate. We verify the
certificate here.
api_only = True
www_authenticate_realm = 'api'
cert_header = getattr(settings, 'X509_CERT_HEADER',
def authenticate(self, request):
# Make sure this request is directed to the API
if self.api_only and not is_api_request(request.path):
return None
# Try to get certificate from request headers
cert_data = self.get_certificate_data_from_request(request)
# If no certificate is found, abort
if not cert_data:
return None
# Verify certificate
certificate = self.verify_certificate_chain(cert_data)
except exceptions.AuthenticationFailed as e:
except Exception as e:
raise exceptions.AuthenticationFailed(_('Certificate could not be '
return self.authenticate_credentials(certificate)
def get_certificate_data_from_request(cls, request):
"""Get certificate data from request"""
cert_quoted = request.META.get(cls.cert_header, None)
if cert_quoted is None:
return None
# Process the certificate a bit
cert_b64 = urlparse.unquote(cert_quoted)
cert_der = base64.b64decode(cert_b64)
return cert_der
def verify_certificate_chain(self, cert_data,
# Load certificate data
certificate = OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_ASN1, cert_data)
# Set up context and get certificate store
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
ctx.load_verify_locations(None, capath=trusted_certs)
store = ctx.get_cert_store()
# Verify certificate
store_ctx = OpenSSL.crypto.X509StoreContext(store, certificate)
# Check if expired
if certificate.has_expired():
raise exceptions.AuthenticationFailed(_('Certificate has expired'))
return certificate
def authenticate_credentials(self, certificate):
# Convert certificate to subject
subject = self.get_certificate_subject_string(certificate)
return super(GraceDbX509FullCertAuthentication, self) \
def get_certificate_subject_string(certificate):
subject = certificate.get_subject()
subject_string = '/' + "/".join(["=".join(c) for c in
return subject_string
class GraceDbAuthenticatedAuthentication(authentication.BaseAuthentication):
If user is already authenticated by the main Django middleware,
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment