Search code examples
pythondjangoapachecentosmod-ssl

Can Apache / mod_ssl support two-tier operation?


I have a web server that maintains most of its content in a local database, but needs to query a back-end directory service to retrieve user information. The directory query works fine as a stand-alone operation, but when the web server makes the query, ssl returns an error.

The server is based on CentOS 7-2.1511 / Django 1.9.3 / PostgreSQL 9.2.15 / Apache 2.4.6-40 / mod_ssl 1:2.4.6-40 / OpenSSL 1:1.0.1 / Python 3.4.3. Apache uses mod_ssl to serve https: requests from the client (browser), and I'm assuming Python's ssl.py uses the same engine to make https: requests to the directory server. Python SSL says that it's implementation is based on OpenSSL. Yum apparently can't list mod_ssl's dependencies, but I assume it also uses the installed version of openssl.

The following code will take the user's distinguished name (extracted from SSL_CLIENT_CERT) and query the directory server for the user's attributes using a RESTful interface:

import requests, urllib

URL = 'https://example.com/rest/user_info/'

def get_user_info(dn)
    query = URL + urllib.parse.quote(dn)
    return requests.get(query, cert=('server.crt', 'server.key'),
                        verify='ca_bundle.crt').json()

When I am running on the server as user apache in the server's WSGI directory the routine correctly returns a dict containing user attributes:

$ python
>>> import auth
>>> dn='cn=Me,o=Company,c=US'
>>> attr = auth.get_user_info(dn)

But when Apache calls the same function with the same DN from it's WSGI script (views.py), it raises an OSError:

OSError(0, 'Error')
Line 810, /lib64/python3.4/ssl.py

803  def do_handshake(self, block=False):
804      """Perform a TLS/SSL handshake."""
805      self._check_connected()
806      timeout = self.gettimeout()
807      try:
808          if timeout == 0.0 and block:
809              self.settimeout(None)
810          self._sslobj.do_handshake()   

I will start researching locking as suggested for OpenSSL (since I can't think of anything else that would be causing these errors) but it's hard to believe that a webserver using SSL for backend queries isn't already a well-traveled path. Questions:

  1. Is multi-threading / locking / reentrancy barking up the right tree for the cause of these errors?
  2. Are there already worked examples for using SSL for webserver-backend connections?

Solution

  • I give up. The following works reliably, at the expense of both beauty and efficiency. It tries a requests query first, and if that blows up, it punts and calls wget in a subprocess. It returns a meta item $method that lets the page view know whether the inline request failed.

    def get_user_info(dn, sub=True, ttl=300):
        query = URL + urllib.parse.quote(dn)
        try:
            info = requests.get(query, cert=(SERVER_CERT, SERVER_KEY),
                                verify=CA_CERT).json()
            info['$method'] = 'requests'
            return info
        except OSError:
            if sub:
                args = ['wget', '-O', '-',
                    '--certificate=' + SERVER_CERT,
                    '--private-key=' + SERVER_KEY,
                    query]
                bytes = subprocess.check_output(args, timeout=5)
                info = json.loads(bytes.decode(encoding='utf-8'))
                info['$method'] = 'subprocess'
                return info
            else:
                raise
    

    It sure would be nice if OpenSSL used contexts instead of global variables.