https_forwarder.py revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
15d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved.
25d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# found in the LICENSE file.
45d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
55d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)"""An https server that forwards requests to another server. This allows a
65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)server that supports http only to be accessed over https.
75d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)"""
85d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
95d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import BaseHTTPServer
105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import os
115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import SocketServer
125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import sys
135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import urllib2
145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import urlparse
155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import testserver_base
165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import tlslite.api
175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class RedirectSuppressor(urllib2.HTTPErrorProcessor):
205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Prevents urllib2 from following http redirects.
215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  If this class is placed in an urllib2.OpenerDirector's handler chain before
235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  the default urllib2.HTTPRedirectHandler, it will terminate the processing of
245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  responses containing redirect codes (301, 302, 303, 307) before they reach the
255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  default redirect handler.
265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def http_response(self, req, response):
295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return response
305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def https_response(self, req, response):
325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return response
335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class RequestForwarder(BaseHTTPServer.BaseHTTPRequestHandler):
365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Handles requests received by forwarding them to the another server."""
375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def do_GET(self):
395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Forwards GET requests."""
405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._forward(None)
415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def do_POST(self):
435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Forwards POST requests."""
445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._forward(self.rfile.read(int(self.headers['Content-Length'])))
455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def _forward(self, body):
475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Forwards a GET or POST request to another server.
485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Args:
505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      body: The request body. This should be |None| for GET requests.
515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    request_url = urlparse.urlparse(self.path)
535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    url = urlparse.urlunparse((self.server.forward_scheme,
545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               self.server.forward_netloc,
555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               self.server.forward_path + request_url[2],
565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               request_url[3],
575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               request_url[4],
585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               request_url[5]))
595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    headers = dict((key, value) for key, value in dict(self.headers).iteritems()
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                   if key.lower() != 'host')
625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    opener = urllib2.build_opener(RedirectSuppressor)
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    forward = opener.open(urllib2.Request(url, body, headers))
645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.send_response(forward.getcode())
665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    for key, value in dict(forward.info()).iteritems():
675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      self.send_header(key, value)
685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.end_headers()
695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.wfile.write(forward.read())
705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class MultiThreadedHTTPSServer(SocketServer.ThreadingMixIn,
735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               tlslite.api.TLSSocketServerMixIn,
745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               testserver_base.ClientRestrictingServerMixIn,
755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               testserver_base.BrokenPipeHandlerMixIn,
765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               testserver_base.StoppableHTTPServer):
775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """A multi-threaded version of testserver.HTTPSServer."""
785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def __init__(self, server_address, request_hander_class, pem_cert_and_key):
805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Initializes the server.
815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Args:
835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      server_address: Server host and port.
845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      request_hander_class: The class that will handle requests to the server.
855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      pem_cert_and_key: Path to file containing the https cert and private key.
865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Force using only python implementation - otherwise behavior is different
895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # depending on whether m2crypto Python module is present (error is thrown
905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # when it is). m2crypto uses a C (based on OpenSSL) implementation under
915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # the hood.
925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                               private=True,
945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                               implementations=['python'])
955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    testserver_base.StoppableHTTPServer.__init__(self,
975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                                 server_address,
985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                                 request_hander_class)
995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def handshake(self, tlsConnection):
1015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Performs the SSL handshake for an https connection.
1025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Args:
1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      tlsConnection: The https connection.
1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Returns:
1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      Whether the SSL handshake succeeded.
1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
1085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    try:
1095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      self.tlsConnection = tlsConnection
1105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      tlsConnection.handshakeServer(certChain=self.cert_chain,
1115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                    privateKey=self.private_key)
1125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      tlsConnection.ignoreAbruptClose = True
1135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      return True
1145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    except:
1155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      return False
1165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class ServerRunner(testserver_base.TestServerRunner):
1195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Runner that starts an https server which forwards requests to another
1205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  server.
1215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def create_server(self, server_data):
1245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Performs the SSL handshake for an https connection.
1255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Args:
1275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      server_data: Dictionary that holds information about the server.
1285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Returns:
1295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      The started server.
1305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    port = self.options.port
1325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    host = self.options.host
1335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if not os.path.isfile(self.options.cert_and_key_file):
1355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      raise testserver_base.OptionError(
1365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          'Specified server cert file not found: ' +
1375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          self.options.cert_and_key_file)
1385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    pem_cert_and_key = open(self.options.cert_and_key_file).read()
1395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    server = MultiThreadedHTTPSServer((host, port),
1415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                      RequestForwarder,
1425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                      pem_cert_and_key)
1435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    print 'HTTPS server started on %s:%d...' % (host, server.server_port)
1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    forward_target = urlparse.urlparse(self.options.forward_target)
1465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    server.forward_scheme = forward_target[0]
1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    server.forward_netloc = forward_target[1]
1485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    server.forward_path = forward_target[2].rstrip('/')
1495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    server.forward_host = forward_target.hostname
1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if forward_target.port:
1515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      server.forward_host += ':' + str(forward_target.port)
1525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    server_data['port'] = server.server_port
1535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return server
1545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def add_options(self):
1565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Specifies the command-line options understood by the server."""
1575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    testserver_base.TestServerRunner.add_options(self)
1585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.option_parser.add_option('--https', action='store_true',
1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                  help='Ignored (provided for compatibility '
1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                  'only).')
1615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.option_parser.add_option('--cert-and-key-file', help='The path to the '
1625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                  'file containing the certificate and private '
1635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                  'key for the server in PEM format.')
1645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.option_parser.add_option('--forward-target', help='The URL prefix to '
1655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                  'which requests will be forwarded.')
1665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)if __name__ == '__main__':
1695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  sys.exit(ServerRunner().main())
170