From 28b112975358e17827a5e8ed869ca188fc82ee42 Mon Sep 17 00:00:00 2001 From: John Bresnahan Date: Thu, 28 Mar 2013 11:09:19 -1000 Subject: [PATCH] Verify SSL certificates at boot time When Glance is configured to run with SSL this patch will check that the certificate and key exist, are readable, and match each other at boot time. If any of the above conditions fail an friendly error message is logged directing the operator at a solution. Previously the keys were not checked until a client connected. fixes bug: 1160529 Change-Id: I16975608f9ae40ac5b84b3caa52ed5f8e22f296e --- glance/common/utils.py | 35 +++++++++++++++++++++++++++++++++ glance/common/wsgi.py | 2 ++ glance/tests/unit/test_utils.py | 30 ++++++++++++++++++++++++++++ tools/pip-requires | 1 + 4 files changed, 68 insertions(+) diff --git a/glance/common/utils.py b/glance/common/utils.py index a85bb3cbdf..150357e951 100644 --- a/glance/common/utils.py +++ b/glance/common/utils.py @@ -32,7 +32,9 @@ import os import platform import subprocess import sys +import uuid +from OpenSSL import crypto from oslo.config import cfg from webob import exc @@ -467,3 +469,36 @@ class LazyPluggable(object): def __getattr__(self, key): backend = self.__get_backend() return getattr(backend, key) + + +def validate_key_cert(key_file, cert_file): + try: + error_key_name = "private key" + error_filename = key_file + key_str = open(key_file, "r").read() + key = crypto.load_privatekey(crypto.FILETYPE_PEM, key_str) + + error_key_name = "certficate" + error_filename = cert_file + cert_str = open(cert_file, "r").read() + cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_str) + except IOError, ioe: + raise RuntimeError(_("There is a problem with your %s " + "%s. Please verify it. Error: %s" + % (error_key_name, error_filename, ioe))) + except crypto.Error, ce: + raise RuntimeError(_("There is a problem with your %s " + "%s. Please verify it. OpenSSL error: %s" + % (error_key_name, error_filename, ce))) + + try: + data = str(uuid.uuid4()) + digest = "sha1" + + out = crypto.sign(key, data, digest) + crypto.verify(cert, out, data, digest) + except crypto.Error, ce: + raise RuntimeError(_("There is a problem with your key pair. " + "Please verify that cert %s and key %s " + "belong together. OpenSSL error %s" + % (cert_file, key_file, ce))) diff --git a/glance/common/wsgi.py b/glance/common/wsgi.py index 01ea48cbbb..1bc4848203 100644 --- a/glance/common/wsgi.py +++ b/glance/common/wsgi.py @@ -115,6 +115,8 @@ def get_socket(default_port): "option value in your configuration file")) def wrap_ssl(sock): + utils.validate_key_cert(key_file, cert_file) + ssl_kwargs = { 'server_side': True, 'certfile': cert_file, diff --git a/glance/tests/unit/test_utils.py b/glance/tests/unit/test_utils.py index ac7dacf581..37d79cca95 100644 --- a/glance/tests/unit/test_utils.py +++ b/glance/tests/unit/test_utils.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import os import StringIO import tempfile @@ -103,3 +104,32 @@ class TestUtils(test_utils.BaseTestCase): byte = reader.read(1) self.assertRaises(exception.ImageSizeLimitExceeded, _consume_all_read) + + def test_validate_key_cert_key(self): + var_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), + '../', 'var')) + keyfile = os.path.join(var_dir, 'privatekey.key') + certfile = os.path.join(var_dir, 'certificate.crt') + utils.validate_key_cert(keyfile, certfile) + + def test_validate_key_cert_no_private_key(self): + with tempfile.NamedTemporaryFile('w+') as tmpf: + self.assertRaises(RuntimeError, + utils.validate_key_cert, + "/not/a/file", tmpf.name) + + def test_validate_key_cert_cert_cant_read(self): + with tempfile.NamedTemporaryFile('w+') as keyf: + with tempfile.NamedTemporaryFile('w+') as certf: + os.chmod(certf.name, 0) + self.assertRaises(RuntimeError, + utils.validate_key_cert, + keyf.name, certf.name) + + def test_validate_key_cert_key_cant_read(self): + with tempfile.NamedTemporaryFile('w+') as keyf: + with tempfile.NamedTemporaryFile('w+') as keyf: + os.chmod(keyf.name, 0) + self.assertRaises(RuntimeError, + utils.validate_key_cert, + keyf.name, keyf.name) diff --git a/tools/pip-requires b/tools/pip-requires index f5a08473f6..7fe4b353e3 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -36,3 +36,4 @@ Paste passlib jsonschema python-keystoneclient>=0.2.0 +pyOpenSSL