Commit 46298d66 authored by Tanner Prestegard's avatar Tanner Prestegard Committed by GraceDB

Fix up proxied X509 cert authentication

Clean up the logic and add some examples for X509 cert auth with
impersonation proxies.  Fully implement the corresponding unit tests.
parent dae9e063
...@@ -71,7 +71,6 @@ class GraceDbX509Authentication(authentication.BaseAuthentication): ...@@ -71,7 +71,6 @@ class GraceDbX509Authentication(authentication.BaseAuthentication):
proxy_pattern = re.compile(r'^(.*?)(/CN=\d+)*$') proxy_pattern = re.compile(r'^(.*?)(/CN=\d+)*$')
def authenticate(self, request): def authenticate(self, request):
# Make sure this request is directed to the API # Make sure this request is directed to the API
if self.api_only and not is_api_request(request.path): if self.api_only and not is_api_request(request.path):
return None return None
...@@ -97,14 +96,46 @@ class GraceDbX509Authentication(authentication.BaseAuthentication): ...@@ -97,14 +96,46 @@ class GraceDbX509Authentication(authentication.BaseAuthentication):
certdn = request.META.get(cls.subject_dn_header, None) certdn = request.META.get(cls.subject_dn_header, None)
issuer = request.META.get(cls.issuer_dn_header, '') issuer = request.META.get(cls.issuer_dn_header, '')
# Proxies can be signed by proxies; each level adds '/CN=[0-9]+' to the # Handled proxied certificates
# signers' subject, so we remove those to get the original identity's certdn = cls.extract_subject_from_proxied_cert(certdn, issuer)
# certificate DN
if certdn and issuer and certdn.startswith(issuer):
certdn = cls.proxy_pattern.match(issuer).group(1)
return certdn return certdn
@classmethod
def extract_subject_from_proxied_cert(cls, subject, issuer):
"""
Handles the case of "impersonation proxies", where /CN=[0-9]+ is
appended to the end of the certificate subject. This occurs when you
generate a certificate and it "follows" you to another machine - you
effectively self-sign a copy of the certificate to use on the other
machine.
Example:
Albert generates a certificate with ligo-proxy-init on his laptop.
Subject and issuer when he pings the GraceDB server from his laptop:
/DC=org/DC=cilogon/C=US/O=LIGO/CN=Albert Einstein albert.einstein@ligo.org
/DC=org/DC=cilogon/C=US/O=CILogon/CN=CILogon Basic CA 1
Subject and issuer when he gsisshs to an LDG cluster and then pings the
GraceDB server from there:
/DC=org/DC=cilogon/C=US/O=LIGO/CN=Albert Einstein albert.einstein@ligo.org/CN=1492637212
/DC=org/DC=cilogon/C=US/O=LIGO/CN=Albert Einstein albert.einstein@ligo.org
If he then gsisshs to *another* machine from there and repeats this,
he would get:
/DC=org/DC=cilogon/C=US/O=LIGO/CN=Albert Einstein albert.einstein@ligo.org/CN=1492637212/CN=28732493
/DC=org/DC=cilogon/C=US/O=LIGO/CN=Albert Einstein albert.einstein@ligo.org/CN=1492637212
"""
if subject and issuer and subject.startswith(issuer):
# If we get here, we have an impersonation proxy, so we extract
# the proxy /CN=12345... part from the subject. Could also
# do it from the issuer (see above examples)
subject = cls.proxy_pattern.match(subject).group(1)
return subject
def authenticate_credentials(self, user_cert_dn): def authenticate_credentials(self, user_cert_dn):
certs = X509Cert.objects.filter(subject=user_cert_dn) certs = X509Cert.objects.filter(subject=user_cert_dn)
if not certs.exists(): if not certs.exists():
......
...@@ -192,17 +192,33 @@ class TestGraceDbX509Authentication(GraceDbApiTestBase): ...@@ -192,17 +192,33 @@ class TestGraceDbX509Authentication(GraceDbApiTestBase):
"""User can authenticate to API with proxied X509 certificate""" """User can authenticate to API with proxied X509 certificate"""
# Set up request # Set up request
request = self.factory.get(api_reverse('api:root')) request = self.factory.get(api_reverse('api:root'))
#request.META[GraceDbX509Authentication.subject_dn_header] = \ request.META[GraceDbX509Authentication.subject_dn_header] = \
# '/CN=123' + self.x509_subject self.x509_subject + '/CN=123456789'
#request.META[GraceDbX509Authentication.issuer_dn_header] = \ request.META[GraceDbX509Authentication.issuer_dn_header] = \
# '/CN=123' self.x509_subject
# Authentication attempt # Authentication attempt
#user, other = self.backend_instance.authenticate(request) user, other = self.backend_instance.authenticate(request)
# Check authenticated user # Check authenticated user
#self.assertEqual(user, self.internal_user) self.assertEqual(user, self.internal_user)
def test_authenticate_cert_with_double_proxy(self):
"""User can authenticate to API with double-proxied X509 certificate"""
proxied_x509_subject = self.x509_subject + '/CN=123456789'
# Set up request
request = self.factory.get(api_reverse('api:root'))
request.META[GraceDbX509Authentication.subject_dn_header] = \
proxied_x509_subject + '/CN=987654321'
request.META[GraceDbX509Authentication.issuer_dn_header] = \
proxied_x509_subject
# Authentication attempt
user, other = self.backend_instance.authenticate(request)
# Check authenticated user
self.assertEqual(user, self.internal_user)
class TestGraceDbAuthenticatedAuthentication(GraceDbApiTestBase): class TestGraceDbAuthenticatedAuthentication(GraceDbApiTestBase):
"""Test shibboleth auth backend for API""" """Test shibboleth auth backend for API"""
......
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