From d7c8e936f373695580721f418e3eea7a31c00ea1 Mon Sep 17 00:00:00 2001 From: Sylvain Bauza Date: Mon, 29 Sep 2014 13:33:50 +0200 Subject: [PATCH] Fix unsafe SSL connection on TrustedFilter TrustedFilter was using httplib which doesn't check for CAs. Here the change is using Requests and verifies local CAs by default (or another one if provided) This effort is related to CVE 2013-2255. SecurityImpact ReleaseNote This patch adds an option attestation_insecure_ssl in TrustedFilter which can be used to verify CAs. The default value is set to True, disabling SSL certificate verification. While this is the insecure option, it was selected for backward compatibility reasons. Closes-Bug: #1373993 (cherry picked from commit 30871e8702737edbbfbcbbb5f21858873b37685c) Conflicts: nova/tests/scheduler/test_host_filters.py Change-Id: I0b8e6319a4cc39876b1e396ef705f0fc5def1e44 --- nova/scheduler/filters/trusted_filter.py | 80 ++++++++--------------- nova/tests/scheduler/test_host_filters.py | 5 +- 2 files changed, 28 insertions(+), 57 deletions(-) diff --git a/nova/scheduler/filters/trusted_filter.py b/nova/scheduler/filters/trusted_filter.py index edc1d25928ef..61057d03ef57 100644 --- a/nova/scheduler/filters/trusted_filter.py +++ b/nova/scheduler/filters/trusted_filter.py @@ -42,11 +42,8 @@ the Open Attestation project at: https://github.com/OpenAttestation/OpenAttestation """ -import httplib -import socket -import ssl - from oslo.config import cfg +import requests from nova import context from nova import db @@ -74,6 +71,9 @@ trusted_opts = [ cfg.IntOpt('attestation_auth_timeout', default=60, help='Attestation status cache valid period length'), + cfg.BoolOpt('attestation_insecure_ssl', + default=True, + help='Disable SSL cert verification for Attestation service') ] CONF = cfg.CONF @@ -82,37 +82,6 @@ CONF.register_group(trust_group) CONF.register_opts(trusted_opts, group=trust_group) -class HTTPSClientAuthConnection(httplib.HTTPSConnection): - """Class to make a HTTPS connection, with support for full client-based - SSL Authentication - """ - - def __init__(self, host, port, key_file, cert_file, ca_file, timeout=None): - httplib.HTTPSConnection.__init__(self, host, - key_file=key_file, - cert_file=cert_file) - self.host = host - self.port = port - self.key_file = key_file - self.cert_file = cert_file - self.ca_file = ca_file - self.timeout = timeout - - def connect(self): - """Connect to a host on a given (SSL) port. - If ca_file is pointing somewhere, use it to check Server Certificate. - - Redefined/copied and extended from httplib.py:1105 (Python 2.6.x). - This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to - ssl.wrap_socket(), which forces SSL to check server certificate - against our client certificate. - """ - sock = socket.create_connection((self.host, self.port), self.timeout) - self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, - ca_certs=self.ca_file, - cert_reqs=ssl.CERT_REQUIRED) - - class AttestationService(object): # Provide access wrapper to attestation server to get integrity report. @@ -125,29 +94,36 @@ class AttestationService(object): self.cert_file = None self.ca_file = CONF.trusted_computing.attestation_server_ca_file self.request_count = 100 + # If the CA file is not provided, let's check the cert if verification + # asked + self.verify = (not CONF.trusted_computing.attestation_insecure_ssl + and self.ca_file or True) + self.cert = (self.cert_file, self.key_file) def _do_request(self, method, action_url, body, headers): # Connects to the server and issues a request. # :returns: result data # :raises: IOError if the request fails - action_url = "%s/%s" % (self.api_url, action_url) + action_url = "https://%s:%d%s/%s" % (self.host, self.port, + self.api_url, action_url) try: - c = HTTPSClientAuthConnection(self.host, self.port, - key_file=self.key_file, - cert_file=self.cert_file, - ca_file=self.ca_file) - c.request(method, action_url, body, headers) - res = c.getresponse() - status_code = res.status - if status_code in (httplib.OK, - httplib.CREATED, - httplib.ACCEPTED, - httplib.NO_CONTENT): - return httplib.OK, res + res = requests.request(method, action_url, data=body, + headers=headers, cert=self.cert, + verify=self.verify) + status_code = res.status_code + # pylint: disable=E1101 + if status_code in (requests.codes.OK, + requests.codes.CREATED, + requests.codes.ACCEPTED, + requests.codes.NO_CONTENT): + try: + return requests.codes.OK, jsonutils.loads(res.text) + except ValueError: + return requests.codes.OK, res.text return status_code, None - except (socket.error, IOError): + except requests.exceptions.RequestException: return IOError, None def _request(self, cmd, subcmd, hosts): @@ -161,11 +137,7 @@ class AttestationService(object): if self.auth_blob: headers['x-auth-blob'] = self.auth_blob status, res = self._do_request(cmd, subcmd, cooked, headers) - if status == httplib.OK: - data = res.read() - return status, jsonutils.loads(data) - else: - return status, None + return status, res def do_attestation(self, hosts): """Attests compute nodes through OAT service. diff --git a/nova/tests/scheduler/test_host_filters.py b/nova/tests/scheduler/test_host_filters.py index 47a0a224b8e4..5e36ab75aaf7 100644 --- a/nova/tests/scheduler/test_host_filters.py +++ b/nova/tests/scheduler/test_host_filters.py @@ -15,9 +15,8 @@ Tests For Scheduler Host Filters. """ -import httplib - from oslo.config import cfg +import requests import stubout from nova import context @@ -242,7 +241,7 @@ class HostFiltersTestCase(test.NoDBTestCase): def fake_oat_request(self, *args, **kwargs): """Stubs out the response from OAT service.""" self.oat_attested = True - return httplib.OK, self.oat_data + return requests.codes.OK, self.oat_data def setUp(self): super(HostFiltersTestCase, self).setUp()