Skip to content
Browse files

allow zygote to serve over https

  • Loading branch information...
1 parent d7db851 commit 84176a8904d5de8664bbf186772dc789f7404f67 @blampe blampe committed Mar 28, 2012
Showing with 172 additions and 21 deletions.
  1. +15 −0 example/certs/ca.cert
  2. +15 −0 example/certs/ca.key
  3. +16 −0 example/certs/server.cert
  4. +15 −0 example/certs/server.key
  5. +37 −10 tests/test.py
  6. +6 −2 zygote/handlers.py
  7. +2 −0 zygote/main.py
  8. +56 −5 zygote/master.py
  9. +10 −4 zygote/worker.py
View
15 example/certs/ca.cert
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICQTCCAaoCCQDOnsDkrUiGaDANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJV
+UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzARBgNVBAoT
+ClllbHAsIEluYy4xHDAaBgkqhkiG9w0BCQEWDWZha2VAeWVscC5jb20wHhcNMTIw
+MzI4MjE1MzI0WhcNMTMwMzI4MjE1MzI0WjBlMQswCQYDVQQGEwJVUzELMAkGA1UE
+CBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzARBgNVBAoTClllbHAsIElu
+Yy4xHDAaBgkqhkiG9w0BCQEWDWZha2VAeWVscC5jb20wgZ8wDQYJKoZIhvcNAQEB
+BQADgY0AMIGJAoGBAMghIEzv4fr6sRjQY+kHq82zpjEfy0b2i/fDZM1ne43mxOzO
+LJ7NlWMwdRf9A3b9ZAA/dC1t85JezSkPx98y0zz1iSVhLJoFoAR4Pa5SHUSNuZvd
+hvAvES/swPUlMoawm41Qyg9PTTcvz8MWjnx7o749KE7wrCb9FAtF9IZJddCHAgMB
+AAEwDQYJKoZIhvcNAQEFBQADgYEAx91P6rcMxYrYMAn9EkjrAYG4fFrodp8jWX2M
+ujp3vvEKenj3nQAyq0tIcOIYi46Irb6aBT9X6jie0lshEvdnEkEaNbP96DfqXXBQ
+0PBHNRLDryXKXkKG4nNmm0YA5oz4MWMJ4/4Y4Wuul0iwRjq9ygCoQ+cg+EwPX56s
+7iYoNqY=
+-----END CERTIFICATE-----
View
15 example/certs/ca.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDIISBM7+H6+rEY0GPpB6vNs6YxH8tG9ov3w2TNZ3uN5sTsziye
+zZVjMHUX/QN2/WQAP3QtbfOSXs0pD8ffMtM89YklYSyaBaAEeD2uUh1Ejbmb3Ybw
+LxEv7MD1JTKGsJuNUMoPT003L8/DFo58e6O+PShO8Kwm/RQLRfSGSXXQhwIDAQAB
+AoGBALhJcNIIL4MK6JueAfKrIrSIEqi3y1tsWxVrM17GRQs1ju81J/eP/llZXOob
+Dd67lSN5SwUuc5W8gJWoN3g7+DFOT7NitV2lL/JEoO9p2PR9LTAO9ECY8XRygUCE
+DvcUmVEuKQmltj/gzSoGfwQ32FOVNTr56JwxZ7+AaKC/35nJAkEA+dCXt9S1Yyok
+96Ws3qWyvo2hcLTq3ZroHnWTQpDLPWjNuZYJEd7hmFRPxU+JcU0j9cIISj1eTPIY
+8d6vG9+bhQJBAM0VnHofXfdKD1DMYrdzsOKy9RnMjgP9hI46mu4mpnVOCKlCrVIE
+j99Lc/sF4hCEPY0Jb1ji+uvvvAntHvi/O5sCQDXiQFdukhFprb+dBMShiQSBGClv
+XJmgKCEpyzG4eZ4tVPKK4jnwkUiCJxKwLT07Hl6ME62vvv9p2OeS2MJyYOECQE4N
+zptQOuQ2ZLPcximKN6VgdRaXmul74Kp9NaA0R6BXzcYV4X9YyyUyQ3cjmxGsMvzt
+vVo4MUYA3TFt9R65MccCQQCDPzvzl4jLecGpKQrSqfY6bkEbrM0s+d3FG/bP+KzC
+kw4emei4iYJ6RC7CMB18uEScDD0Y+mjB6rJPE4GXzjcG
+-----END RSA PRIVATE KEY-----
View
16 example/certs/server.cert
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICbjCCAdcCCQD/wqBrOU2+kTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJV
+UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzARBgNVBAoT
+ClllbHAsIEluYy4xHDAaBgkqhkiG9w0BCQEWDWZha2VAeWVscC5jb20wHhcNMTIw
+MzI4MjE1ODI2WhcNMTMwMzI4MjE1ODI2WjCBkTELMAkGA1UEBhMCVVMxCzAJBgNV
+BAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYDVQQKEwpZZWxwLCBJ
+bmMuMRUwEwYDVQQLEwxSZXZlbnVlIFRlYW0xEjAQBgNVBAMTCWxvY2FsaG9zdDEd
+MBsGCSqGSIb3DQEJARYOYnJ5Y2VAeWVscC5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
+gY0AMIGJAoGBALA1ppWfixI8BiVRwvSChthxsQqVzWgwG5dFQi0xEu00yk6g3MMX
+kY3FOHpqAF77oTuhxmKhWQ858KzHags1s3Xyb2IUdKqI5QfsX0QL7rkCx+tHDiQe
+tmn1GeQxx97NgKCN1oMtiQ2ejYo/3U7CUayOFE/kRbaSzyr08pj0hmdXAgMBAAEw
+DQYJKoZIhvcNAQEFBQADgYEASP7MMJVqbPNKMEAV8X2lNBUB0672oS3p7pFiBf24
+Z2B2o3ZD4DhHbMMByClLA38EYs74eZjkihxd0e2Vf+lK8YSyZN99/AYiy/h1Uf7d
+qchqbMZXiE0D7j4J7G0+jr/10nEJdj/DEXNuYSxzB6Us1sq6G3drM9hK6Tt5Iufz
+hcQ=
+-----END CERTIFICATE-----
View
15 example/certs/server.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCwNaaVn4sSPAYlUcL0gobYcbEKlc1oMBuXRUItMRLtNMpOoNzD
+F5GNxTh6agBe+6E7ocZioVkPOfCsx2oLNbN18m9iFHSqiOUH7F9EC+65AsfrRw4k
+HrZp9RnkMcfezYCgjdaDLYkNno2KP91OwlGsjhRP5EW2ks8q9PKY9IZnVwIDAQAB
+AoGAQEYBkngUgT01vK0bIJbv2sl1m3fS6dsKZV4U6mkJD07/MDkK7XqOVBkJJdW+
+dubwA9FZjKZv1tb6i/tdGeOnppXFaAZ18cQCdDgr6Oj0GWWVJnZ46EI1zX4QFvdr
+tP0pkDDukgG8Q0Sl4C57U44WtaRem1gVm/nhrLvl5uBrBuECQQDkKPxdHLnscW8O
+PDwg7lQtWWPL/177OAP2uQultRXVarDoBqskSxxTpXvCdRxOb2+HIEXS9ixdrG+6
+zsR6Z+rRAkEAxbXkOUQgLmcmmGtkIpeXX4em9WpYmeG3ptNWCoeH4X6PUH7R4AfX
+IZ8bu6955ihB0DH5DeJhvlVIP6B8XsTppwJAN9EzFBBwB8EkeyYPS7siismgmYqL
+EQh+J8DTcaGgisqEJu9itQlPD8OfSE5gM2wdq8AgdODWr7/8wYXOGWgM0QJAMXUA
+tDqQeksfYn1qvSSCn0kFwNprc4L9N+Qh39xrZ0MLgq1Wvt33ONfeTiLlMWfcnsIB
+dTGuFbirrA7vTZ2gfQJBAJhCllG0fQs2g4ppjYqPQPUzHdTBfSm17gsb5OT8slXp
+Tnnc6hNaJvnR2dh2gOxgQXLjNwGC8ShWW1MvjRbhtjU=
+-----END RSA PRIVATE KEY-----
View
47 tests/test.py
@@ -9,7 +9,7 @@
import os
import tornado.simple_httpclient
-from tornado.httpclient import HTTPRequest, HTTPClient
+from tornado.httpclient import HTTPRequest, HTTPClient, HTTPError
from testify import *
@@ -25,10 +25,14 @@ class ZygoteTest(TestCase):
basedir = './example'
control_port = None
port = None
+ protocol = 'http'
num_workers = 4
+ extra_proc_args = []
- def get_url(self, path):
- req = HTTPRequest('http://localhost:%d%s' % (self.port, path))
+ def get_url(self, path, protocol=None):
+ protocol = protocol or self.protocol
+ ca_cert_path = os.path.join(self.basedir, 'certs', 'ca.cert')
+ req = HTTPRequest('%s://localhost:%d%s' % (protocol, self.port, path), validate_cert=True, ca_certs=ca_cert_path)
try:
response = self.http_client.fetch(req)
except socket.error, e:
@@ -75,13 +79,20 @@ def create_process(self):
kw['stdout'] = sys.stdout
kw['stderr'] = sys.stderr
- self.proc = subprocess.Popen(['python', 'zygote/main.py',
- '-d',
- '-b', self.basedir,
- '-p', str(self.port),
- '--control-port', str(self.control_port),
- '--num-workers', str(self.num_workers),
- '-m', 'example'], **kw)
+
+ proc_args = ['python', 'zygote/main.py',
+ '-d',
+ '-b', self.basedir,
+ '-p', str(self.port),
+ '--control-port', str(self.control_port),
+ '--num-workers', str(self.num_workers),
+ '-m', 'example',
+ ]
+
+ if self.extra_proc_args:
+ proc_args += self.extra_proc_args
+
+ self.proc = subprocess.Popen(proc_args, **kw)
@setup
def sanity_check_process(self):
@@ -213,5 +224,21 @@ def test_hup_intermediate(self):
final_zygote = self.get_zygote(process_tree)
assert_not_equal(initial_zygote, final_zygote)
+
+class SecureZygoteTests(ZygoteTest):
+ protocol = 'https'
+ extra_proc_args = ['--cert', 'certs/server.cert', '--key', 'certs/server.key']
+
+ def test_https_get(self):
+ for x in xrange(self.num_workers + 1):
+ resp = self.get_url('/')
+ self.check_response(resp)
+ assert resp.body.startswith('uptime: ')
+
+ def test_http_get(self):
+ for x in xrange(self.num_workers + 1):
+ assert_raises(HTTPError, self.get_url, '/', protocol='http')
+
+
if __name__ == '__main__':
main()
View
8 zygote/handlers.py
@@ -64,7 +64,7 @@ def get(self):
self.set_header('Content-Type', 'application/json')
self.write(json.dumps(env, cls=JSONEncoder, indent=2))
-def get_httpserver(io_loop, port, zygote_master, zygote_base=None):
+def get_httpserver(io_loop, port, zygote_master, zygote_base=None, ssl_options=None):
if zygote_base is not None:
static_path = os.path.realpath(os.path.join(zygote_base, 'static'))
template_path = os.path.realpath(os.path.join(zygote_base, 'templates'))
@@ -90,6 +90,10 @@ def get_httpserver(io_loop, port, zygote_master, zygote_base=None):
static_path=static_path,
template_path=template_path)
app.settings['worker_sockname'] = zygote_master.sock.getsockname()
- http_server = tornado.httpserver.HTTPServer(app, io_loop=io_loop, no_keep_alive=True)
+ http_server = tornado.httpserver.HTTPServer(app,
+ io_loop=io_loop,
+ no_keep_alive=True,
+ ssl_options=ssl_options,
+ )
http_server.listen(port)
return open_fds, http_server
View
2 zygote/main.py
@@ -33,6 +33,8 @@ def main():
parser.add_option('--num-workers', type='int', default=8, help='How many workers to run')
parser.add_option('--max-requests', type='int', default=None, help='The maximum number of requests a child can run')
parser.add_option('--zygote-base', default=None, help='The base path to the zygote')
+ parser.add_option('--cert', default=None, help='Certificate to use for HTTPS traffic.')
+ parser.add_option('--key', default=None, help='Private key for HTTPS traffic.')
opts, args = parser.parse_args()
if not opts.basepath:
View
61 zygote/master.py
@@ -25,6 +25,12 @@ def emit(self, record):
log = logging.getLogger('zygote.master')
+try:
+ import ssl # Python 2.6+
+except ImportError:
+ ssl = None
+
+
class ZygoteMaster(object):
instantiated = False
@@ -37,17 +43,29 @@ class ZygoteMaster(object):
# how many seconds to wait before sending SIGKILL to children
WAIT_FOR_KILL_TIME = 10.0
- def __init__(self, sock, basepath, module, num_workers, control_port, application_args=[], max_requests=None, zygote_base=None):
+ def __init__(self,
+ sock,
+ basepath,
+ module,
+ num_workers,
+ control_port,
+ application_args=None,
+ max_requests=None,
+ zygote_base=None,
+ ssl_options=None,
+ ):
+
if self.__class__.instantiated:
log.error('cannot instantiate zygote master more than once')
sys.exit(1)
self.__class__.instantiated = True
self.stopped = False
self.started_transition = None
- self.application_args = application_args
+ self.application_args = application_args or []
self.io_loop = ZygoteIOLoop(log_name='zygote.master.ioloop')
self.sock = sock
+ self.ssl_options = ssl_options
self.basepath = basepath
self.module = module
self.num_workers = num_workers
@@ -69,7 +87,13 @@ def __init__(self, sock, basepath, module, num_workers, control_port, applicatio
for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGQUIT):
signal.signal(sig, self.stop)
- self.open_fds, self.status_http_server = zygote.handlers.get_httpserver(self.io_loop, control_port, self, zygote_base=zygote_base)
+ self.open_fds, self.status_http_server = zygote.handlers.get_httpserver(
+ self.io_loop,
+ control_port,
+ self,
+ zygote_base=zygote_base,
+ ssl_options=self.ssl_options,
+ )
def reap_child(self, signum, frame):
"""Signal handler for SIGCHLD. Reaps children and updates
@@ -263,7 +287,13 @@ def create_zygote(self):
# Make the zygote a process group leader
os.setpgid(os.getpid(), os.getpid())
# create the zygote
- z = ZygoteWorker(self.sock, realbase, self.module, self.application_args)
+ z = ZygoteWorker(
+ sock=self.sock,
+ basepath=realbase,
+ module=self.module,
+ args=self.application_args,
+ ssl_options=self.ssl_options,
+ )
z.loop()
def start(self):
@@ -316,6 +346,27 @@ def main(opts, extra_args):
sock.setblocking(0)
sock.bind((opts.interface, opts.port))
sock.listen(128)
- master = ZygoteMaster(sock, opts.basepath, opts.module, opts.num_workers, opts.control_port, extra_args, opts.max_requests, opts.zygote_base)
+
+ ssl_options=None
+ if opts.cert:
+ ssl_options = dict(certfile=opts.cert, keyfile=opts.key)
+ log.info('using SSL with %s', ssl_options)
+
+ sock = ssl.wrap_socket(sock,
+ server_side=True,
+ do_handshake_on_connect=False,
+ **ssl_options
+ )
+
+ master = ZygoteMaster(sock,
+ basepath=opts.basepath,
+ module=opts.module,
+ num_workers=opts.num_workers,
+ control_port=opts.control_port,
+ application_args=extra_args,
+ max_requests=opts.max_requests,
+ zygote_base=opts.zygote_base,
+ ssl_options=ssl_options,
+ )
atexit.register(master.stop)
master.start()
View
14 zygote/worker.py
@@ -65,8 +65,9 @@ class ZygoteWorker(object):
# how many seconds to wait before sending SIGKILL to children
WAIT_FOR_KILL_TIME = 10.0
- def __init__(self, sock, basepath, module, args):
+ def __init__(self, sock, basepath, module, args, ssl_options=None):
self.args = args
+ self.ssl_options = ssl_options
self.ppid = os.getppid()
# Set up the control socket nice and early
@@ -230,17 +231,22 @@ def on_close(disconnected=False):
# io_loop is passed into get_application for program to add handler
# or schedule task on the main io_loop. Program that uses this
# io_loop instance should NOT use io_loop.start() because start()
- # is invoked by the corresponding zygote worker.
+ # is invoked by the corresponding zygote worker.
kwargs = {'io_loop': io_loop}
log.debug("Invoking get_application")
app = self.get_application(*self.args, **kwargs)
except Exception:
log.error("Unable to get application")
raise
- #http_server = tornado.httpserver.HTTPServer(app, io_loop=io_loop, no_keep_alive=True)
# TODO: make keep-alive servers work
log.debug("Creating HTTPServer")
- http_server = HTTPServer(app, io_loop=io_loop, no_keep_alive=True, close_callback=on_close, headers_callback=on_headers)
+ http_server = HTTPServer(app,
+ io_loop=io_loop,
+ no_keep_alive=True,
+ close_callback=on_close,
+ headers_callback=on_headers,
+ ssl_options=self.ssl_options
+ )
if tornado.version_info >= (2,1,0):
http_server.add_socket(self.sock)
else:

0 comments on commit 84176a8

Please sign in to comment.
Something went wrong with that request. Please try again.