summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2013-01-23 17:30:12 +0000
committerGerrit Code Review <review@openstack.org>2013-01-23 17:30:12 +0000
commit26477c5ec7d8f4c3c0025db97e889e62619f4ba8 (patch)
tree80c96909cdd3c5f375aeb758122f5d1cdea4b479
parent86c2ad2498aaeb3bb5dfca7ac432dbaa5eaebe1c (diff)
parent7d31c7b15d4c929e384df0e114db615448601cc3 (diff)
Merge "Adds SSL support for API server."
-rwxr-xr-xbin/nova-api5
-rw-r--r--nova/service.py9
-rw-r--r--nova/tests/ssl_cert/ca.crt35
-rw-r--r--nova/tests/ssl_cert/certificate.crt30
-rw-r--r--nova/tests/ssl_cert/privatekey.key51
-rw-r--r--nova/tests/test_wsgi.py97
-rw-r--r--nova/wsgi.py75
7 files changed, 297 insertions, 5 deletions
diff --git a/bin/nova-api b/bin/nova-api
index 8457ea43d..16cf33cc5 100755
--- a/bin/nova-api
+++ b/bin/nova-api
@@ -44,13 +44,16 @@ from nova import utils
CONF = cfg.CONF
CONF.import_opt('enabled_apis', 'nova.service')
+CONF.import_opt('enabled_ssl_apis', 'nova.service')
if __name__ == '__main__':
config.parse_args(sys.argv)
logging.setup("nova")
utils.monkey_patch()
+
launcher = service.ProcessLauncher()
for api in CONF.enabled_apis:
- server = service.WSGIService(api)
+ should_use_ssl = api in CONF.enabled_ssl_apis
+ server = service.WSGIService(api, use_ssl=should_use_ssl)
launcher.launch_server(server, workers=server.workers or 1)
launcher.wait()
diff --git a/nova/service.py b/nova/service.py
index df8cf020f..c5e2aa636 100644
--- a/nova/service.py
+++ b/nova/service.py
@@ -61,6 +61,9 @@ service_opts = [
cfg.ListOpt('enabled_apis',
default=['ec2', 'osapi_compute', 'metadata'],
help='a list of APIs to enable by default'),
+ cfg.ListOpt('enabled_ssl_apis',
+ default=[],
+ help='a list of APIs with enabled SSL'),
cfg.StrOpt('ec2_listen',
default="0.0.0.0",
help='IP address for EC2 API to listen'),
@@ -565,7 +568,7 @@ class Service(object):
class WSGIService(object):
"""Provides ability to launch API from a 'paste' configuration."""
- def __init__(self, name, loader=None):
+ def __init__(self, name, loader=None, use_ssl=False):
"""Initialize, but do not start the WSGI server.
:param name: The name of the WSGI server given to the loader.
@@ -580,10 +583,12 @@ class WSGIService(object):
self.host = getattr(CONF, '%s_listen' % name, "0.0.0.0")
self.port = getattr(CONF, '%s_listen_port' % name, 0)
self.workers = getattr(CONF, '%s_workers' % name, None)
+ self.use_ssl = use_ssl
self.server = wsgi.Server(name,
self.app,
host=self.host,
- port=self.port)
+ port=self.port,
+ use_ssl=self.use_ssl)
# Pull back actual port used
self.port = self.server.port
self.backdoor_port = None
diff --git a/nova/tests/ssl_cert/ca.crt b/nova/tests/ssl_cert/ca.crt
new file mode 100644
index 000000000..9d66ca627
--- /dev/null
+++ b/nova/tests/ssl_cert/ca.crt
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+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
+WoRMgEwjGJWqzhJZUYpUAQ==
+-----END CERTIFICATE-----
diff --git a/nova/tests/ssl_cert/certificate.crt b/nova/tests/ssl_cert/certificate.crt
new file mode 100644
index 000000000..3c1aa6363
--- /dev/null
+++ b/nova/tests/ssl_cert/certificate.crt
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFLjCCAxYCAQEwDQYJKoZIhvcNAQEFBQAwYTELMAkGA1UEBhMCQVUxEzARBgNV
+BAgTClNvbWUtU3RhdGUxFTATBgNVBAoTDE9wZW5zdGFjayBDQTESMBAGA1UECxMJ
+R2xhbmNlIENBMRIwEAYDVQQDEwlHbGFuY2UgQ0EwHhcNMTIwMjA5MTcxMDUzWhcN
+MjIwMjA2MTcxMDUzWjBZMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0
+ZTESMBAGA1UEChMJT3BlbnN0YWNrMQ8wDQYDVQQLEwZHbGFuY2UxEDAOBgNVBAMT
+BzAuMC4wLjAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXpUkQN6pu
+avo+gz3o1K4krVdPl1m7NjNJDyD/+ZH0EGNcEN7iag1qPE7JsjqGPNZsQK1dMoXb
+Sz+OSi9qvNeJnBcfwUx5qTAtwyAb9AxGkwuMafIU+lWbsclo+dPGsja01ywbXTCZ
+bF32iqnpOMYhfxWUdoQYiBkhxxhW9eMPKLS/KkP8/bx+Vaa2XJiAebqkd9nrksAA
+BeGc9mlafYBEmiChPdJEPw+1ePA4QVq9aPepDsqAKtGN8JLpmoC3BdxQQTbbwL3Q
+8fTXK4tCNUaVk4AbDy/McFq6y0ocQoBPJjihOY35mWG/OLtcI99yPOpWGnps/5aG
+/64DDJ2D67Fnaj6gKHV+6TXFO8KZxlnxtgtiZDJBZkneTBt9ArSOv+l6NBsumRz0
+iEJ4o4H1S2TSMnprAvX7WnGtc6Xi9gXahYcDHEelwwYzqAiTBv6hxSp4MZ2dNXa+
+KzOitC7ZbV2qsg0au0wjfE/oSQ3NvsvUr8nOmfutJTvHRAwbC1v4G/tuAsO7O0w2
+0u2B3u+pG06m5+rnEqp+rB9hmukRYTfgEFRRsVIvpFl/cwvPXKRcX03UIMx+lLr9
+Ft+ep7YooBhY3wY2kwCxD4lRYNmbwsCIVywZt40f/4ad98TkufR9NhsfycxGeqbr
+mTMFlZ8TTlmP82iohekKCOvoyEuTIWL2+wIDAQABMA0GCSqGSIb3DQEBBQUAA4IC
+AQBMUBgV0R+Qltf4Du7u/8IFmGAoKR/mktB7R1gRRAqsvecUt7kIwBexGdavGg1y
+0pU0+lgUZjJ20N1SlPD8gkNHfXE1fL6fmMjWz4dtYJjzRVhpufHPeBW4tl8DgHPN
+rBGAYQ+drDSXaEjiPQifuzKx8WS+DGA3ki4co5mPjVnVH1xvLIdFsk89z3b3YD1k
+yCJ/a9K36x6Z/c67JK7s6MWtrdRF9+MVnRKJ2PK4xznd1kBz16V+RA466wBDdARY
+vFbtkafbEqOb96QTonIZB7+fAldKDPZYnwPqasreLmaGOaM8sxtlPYAJ5bjDONbc
+AaXG8BMRQyO4FyH237otDKlxPyHOFV66BaffF5S8OlwIMiZoIvq+IcTZOdtDUSW2
+KHNLfe5QEDZdKjWCBrfqAfvNuG13m03WqfmcMHl3o/KiPJlx8l9Z4QEzZ9xcyQGL
+cncgeHM9wJtzi2cD/rTDNFsx/gxvoyutRmno7I3NRbKmpsXF4StZioU3USRspB07
+hYXOVnG3pS+PjVby7ThT3gvFHSocguOsxClx1epdUJAmJUbmM7NmOp5WVBVtMtC2
+Su4NG/xJciXitKzw+btb7C7RjO6OEqv/1X/oBDzKBWQAwxUC+lqmnM7W6oqWJFEM
+YfTLnrjs7Hj6ThMGcEnfvc46dWK3dz0RjsQzUxugPuEkLA==
+-----END CERTIFICATE-----
diff --git a/nova/tests/ssl_cert/privatekey.key b/nova/tests/ssl_cert/privatekey.key
new file mode 100644
index 000000000..b63df3d29
--- /dev/null
+++ b/nova/tests/ssl_cert/privatekey.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/nova/tests/test_wsgi.py b/nova/tests/test_wsgi.py
index b4b25ed97..b04bc3e03 100644
--- a/nova/tests/test_wsgi.py
+++ b/nova/tests/test_wsgi.py
@@ -21,9 +21,17 @@
import os.path
import tempfile
+import eventlet
+
import nova.exception
from nova import test
import nova.wsgi
+import urllib2
+import webob
+
+SSL_CERT_DIR = os.path.normpath(os.path.join(
+ os.path.dirname(os.path.abspath(__file__)),
+ 'ssl_cert'))
class TestLoaderNothingExists(test.TestCase):
@@ -99,3 +107,92 @@ class TestWSGIServer(test.TestCase):
self.assertNotEqual(0, server.port)
server.stop()
server.wait()
+
+
+class TestWSGIServerWithSSL(test.TestCase):
+ """WSGI server with SSL tests."""
+
+ def setUp(self):
+ super(TestWSGIServerWithSSL, self).setUp()
+ self.flags(enabled_ssl_apis=['fake_ssl'],
+ ssl_cert_file=os.path.join(SSL_CERT_DIR, 'certificate.crt'),
+ ssl_key_file=os.path.join(SSL_CERT_DIR, 'privatekey.key'))
+
+ def test_ssl_server(self):
+
+ def test_app(env, start_response):
+ start_response('200 OK', {})
+ return ['PONG']
+
+ fake_ssl_server = nova.wsgi.Server("fake_ssl", test_app,
+ host="127.0.0.1", port=0,
+ use_ssl=True)
+ fake_ssl_server.start()
+ self.assertNotEqual(0, fake_ssl_server.port)
+
+ cli = eventlet.connect(("localhost", fake_ssl_server.port))
+ cli = eventlet.wrap_ssl(cli,
+ ca_certs=os.path.join(SSL_CERT_DIR, 'ca.crt'))
+
+ cli.write('POST / HTTP/1.1\r\nHost: localhost\r\n'
+ 'Connection: close\r\nContent-length:4\r\n\r\nPING')
+ response = cli.read(8192)
+ self.assertEquals(response[-4:], "PONG")
+
+ fake_ssl_server.stop()
+ fake_ssl_server.wait()
+
+ def test_two_servers(self):
+
+ def test_app(env, start_response):
+ start_response('200 OK', {})
+ return ['PONG']
+
+ fake_ssl_server = nova.wsgi.Server("fake_ssl", test_app,
+ host="127.0.0.1", port=0, use_ssl=True)
+ fake_ssl_server.start()
+ self.assertNotEqual(0, fake_ssl_server.port)
+
+ fake_server = nova.wsgi.Server("fake", test_app,
+ host="127.0.0.1", port=0)
+ fake_server.start()
+ self.assertNotEquals(0, fake_server.port)
+
+ cli = eventlet.connect(("localhost", fake_ssl_server.port))
+ cli = eventlet.wrap_ssl(cli,
+ ca_certs=os.path.join(SSL_CERT_DIR, 'ca.crt'))
+
+ cli.write('POST / HTTP/1.1\r\nHost: localhost\r\n'
+ 'Connection: close\r\nContent-length:4\r\n\r\nPING')
+ response = cli.read(8192)
+ self.assertEquals(response[-4:], "PONG")
+
+ cli = eventlet.connect(("localhost", fake_server.port))
+
+ cli.sendall('POST / HTTP/1.1\r\nHost: localhost\r\n'
+ 'Connection: close\r\nContent-length:4\r\n\r\nPING')
+ response = cli.recv(8192)
+ self.assertEquals(response[-4:], "PONG")
+
+ fake_ssl_server.stop()
+ fake_ssl_server.wait()
+
+ def test_app_using_ipv6_and_ssl(self):
+ greetings = 'Hello, World!!!'
+
+ @webob.dec.wsgify
+ def hello_world(req):
+ return greetings
+
+ server = nova.wsgi.Server("fake_ssl",
+ hello_world,
+ host="::1",
+ port=0,
+ use_ssl=True)
+ server.start()
+
+ response = urllib2.urlopen('https://[::1]:%d/' % server.port)
+ self.assertEquals(greetings, response.read())
+
+ server.stop()
+ server.wait()
diff --git a/nova/wsgi.py b/nova/wsgi.py
index 16851dba8..0a7570b6c 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -28,6 +28,7 @@ import eventlet.wsgi
import greenlet
from paste import deploy
import routes.middleware
+import ssl
import webob.dec
import webob.exc
@@ -45,7 +46,21 @@ wsgi_opts = [
help='A python format string that is used as the template to '
'generate log lines. The following values can be formatted '
'into it: client_ip, date_time, request_line, status_code, '
- 'body_length, wall_seconds.')
+ 'body_length, wall_seconds.'),
+ cfg.StrOpt('ssl_ca_file',
+ default=None,
+ help="CA certificate file to use to verify "
+ "connecting clients"),
+ cfg.StrOpt('ssl_cert_file',
+ default=None,
+ help="SSL certificate of API server"),
+ cfg.StrOpt('ssl_key_file',
+ default=None,
+ help="SSL private key of API server"),
+ cfg.IntOpt('tcp_keepidle',
+ default=600,
+ help="Sets the value of TCP_KEEPIDLE in seconds for each "
+ "server socket. Not supported on OS X.")
]
CONF = cfg.CONF
CONF.register_opts(wsgi_opts)
@@ -59,7 +74,8 @@ class Server(object):
default_pool_size = 1000
def __init__(self, name, app, host='0.0.0.0', port=0, pool_size=None,
- protocol=eventlet.wsgi.HttpProtocol, backlog=128):
+ protocol=eventlet.wsgi.HttpProtocol, backlog=128,
+ use_ssl=False):
"""Initialize, but do not start, a WSGI server.
:param name: Pretty name for logging.
@@ -78,6 +94,7 @@ class Server(object):
self._pool = eventlet.GreenPool(pool_size or self.default_pool_size)
self._logger = logging.getLogger("nova.%s.wsgi.server" % self.name)
self._wsgi_logger = logging.WritableLogger(self._logger)
+ self._use_ssl = use_ssl
if backlog < 1:
raise exception.InvalidInput(
@@ -106,6 +123,60 @@ class Server(object):
:returns: None
"""
+ if self._use_ssl:
+ try:
+ ca_file = CONF.ssl_ca_file
+ cert_file = CONF.ssl_cert_file
+ key_file = CONF.ssl_key_file
+
+ if cert_file and not os.path.exists(cert_file):
+ raise RuntimeError(
+ _("Unable to find cert_file : %s") % cert_file)
+
+ if ca_file and not os.path.exists(ca_file):
+ raise RuntimeError(
+ _("Unable to find ca_file : %s") % ca_file)
+
+ if key_file and not os.path.exists(key_file):
+ raise RuntimeError(
+ _("Unable to find key_file : %s") % key_file)
+
+ if self._use_ssl and (not cert_file or not key_file):
+ raise RuntimeError(
+ _("When running server in SSL mode, you must "
+ "specify both a cert_file and key_file "
+ "option value in your configuration file"))
+ ssl_kwargs = {
+ 'server_side': True,
+ 'certfile': cert_file,
+ 'keyfile': key_file,
+ 'cert_reqs': ssl.CERT_NONE,
+ }
+
+ if CONF.ssl_ca_file:
+ ssl_kwargs['ca_certs'] = ca_file
+ ssl_kwargs['cert_reqs'] = ssl.CERT_REQUIRED
+
+ self._socket = eventlet.wrap_ssl(self._socket,
+ **ssl_kwargs)
+
+ self._socket.setsockopt(socket.SOL_SOCKET,
+ socket.SO_REUSEADDR, 1)
+ # sockets can hang around forever without keepalive
+ self._socket.setsockopt(socket.SOL_SOCKET,
+ socket.SO_KEEPALIVE, 1)
+
+ # This option isn't available in the OS X version of eventlet
+ if hasattr(socket, 'TCP_KEEPIDLE'):
+ self._socket.setsockopt(socket.IPPROTO_TCP,
+ socket.TCP_KEEPIDLE,
+ CONF.tcp_keepidle)
+
+ except Exception:
+ LOG.error(_("Failed to start %(name)s on %(host)s"
+ ":%(port)s with SSL support") % self.__dict__)
+ raise
+
self._server = eventlet.spawn(eventlet.wsgi.server,
self._socket,
self.app,