summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Authors1
-rw-r--r--nova/scheduler/filters/trusted_filter.py207
-rw-r--r--nova/tests/scheduler/test_host_filters.py81
3 files changed, 289 insertions, 0 deletions
diff --git a/Authors b/Authors
index 88285d971..6acda36c7 100644
--- a/Authors
+++ b/Authors
@@ -55,6 +55,7 @@ Devdeep Singh <devdeep.singh@citrix.com>
Devendra Modium <dmodium@isi.edu>
Devin Carlen <devin.carlen@gmail.com>
Dina Belova <dbelova@mirantis.com>
+Don Dugger <donald.d.dugger@intel.com>
Donal Lafferty <donal.lafferty@citrix.com>
Dong-In David Kang <dkang@isi.edu>
Doug Hellmann <doug.hellmann@dreamhost.com>
diff --git a/nova/scheduler/filters/trusted_filter.py b/nova/scheduler/filters/trusted_filter.py
new file mode 100644
index 000000000..e081daf37
--- /dev/null
+++ b/nova/scheduler/filters/trusted_filter.py
@@ -0,0 +1,207 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2012 Intel, Inc.
+# Copyright (c) 2011-2012 Openstack, LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Filter to add support for Trusted Computing Pools.
+
+Filter that only schedules tasks on a host if the integrity (trust)
+of that host matches the trust requested in the `extra_specs' for the
+flavor. The `extra_specs' will contain a key/value pair where the
+key is `trust'. The value of this pair (`trusted'/`untrusted') must
+match the integrity of that host (obtained from the Attestation
+service) before the task can be scheduled on that host.
+
+Note that the parameters to control access to the Attestation Service
+are in the `nova.conf' file in a separate `trust' section. For example,
+the config file will look something like:
+
+ [DEFAULT]
+ verbose=True
+ ...
+ [trust]
+ server=attester.mynetwork.com
+
+Details on the specific parameters can be found in the file `trust_attest.py'.
+
+Details on setting up and using an Attestation Service can be found at
+the Open Attestation project at:
+
+ https://github.com/OpenAttestation/OpenAttestation
+"""
+
+import httplib
+import json
+import socket
+import ssl
+
+from nova import flags
+from nova import log as logging
+from nova.openstack.common import cfg
+from nova.scheduler import filters
+from nova import utils
+
+
+LOG = logging.getLogger(__name__)
+
+trusted_opts = [
+ cfg.StrOpt('server',
+ default=None,
+ help='attestation server http'),
+ cfg.StrOpt('server_ca_file',
+ default=None,
+ help='attestation server Cert file for Identity verification'),
+ cfg.StrOpt('port',
+ default='8443',
+ help='attestation server port'),
+ cfg.StrOpt('api_url',
+ default='/OpenAttestationWebServices/V1.0',
+ help='attestation web API URL'),
+ cfg.StrOpt('auth_blob',
+ default=None,
+ help='attestation authorization blob - must change'),
+]
+
+FLAGS = flags.FLAGS
+trust_group = cfg.OptGroup(name='trusted_computing', title='Trust parameters')
+FLAGS.register_group(trust_group)
+FLAGS.register_opts(trusted_opts, group='trusted_computing')
+
+
+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(httplib.HTTPSConnection):
+ # Provide access wrapper to attestation server to get integrity report.
+
+ def __init__(self):
+ self.api_url = FLAGS.trusted_computing.api_url
+ self.host = FLAGS.trusted_computing.server
+ self.port = FLAGS.trusted_computing.port
+ self.auth_blob = FLAGS.trusted_computing.auth_blob
+ self.key_file = None
+ self.cert_file = None
+ self.ca_file = FLAGS.trusted_computing.server_ca_file
+ self.request_count = 100
+
+ 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)
+ 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
+ return status_code, None
+
+ except (socket.error, IOError) as e:
+ return IOError, None
+
+ def _request(self, cmd, subcmd, host):
+ body = {}
+ body['count'] = 1
+ body['hosts'] = host
+ cooked = json.dumps(body)
+ headers = {}
+ headers['content-type'] = 'application/json'
+ headers['Accept'] = 'application/json'
+ 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, json.loads(data)
+ else:
+ return status, None
+
+ def _check_trust(self, data, host):
+ for item in data:
+ for state in item['hosts']:
+ if state['host_name'] == host:
+ return state['trust_lvl']
+ return ""
+
+ def do_attestation(self, host):
+ state = []
+ status, data = self._request("POST", "PollHosts", host)
+ if status != httplib.OK:
+ return {}
+ state.append(data)
+ return self._check_trust(state, host)
+
+
+class TrustedFilter(filters.BaseHostFilter):
+ """Trusted filter to support Trusted Compute Pools."""
+
+ def __init__(self):
+ self.attestation_service = AttestationService()
+
+ def _is_trusted(self, host, trust):
+ level = self.attestation_service.do_attestation(host)
+ LOG.debug(_("TCP: trust state of "
+ "%(host)s:%(level)s(%(trust)s)") % locals())
+ return trust == level
+
+ def host_passes(self, host_state, filter_properties):
+ instance = filter_properties.get('instance_type', {})
+ extra = instance.get('extra_specs', {})
+ trust = extra.get('trusted_host')
+ host = host_state.host
+ if trust:
+ return self._is_trusted(host, trust)
+ return True
diff --git a/nova/tests/scheduler/test_host_filters.py b/nova/tests/scheduler/test_host_filters.py
index 22a162aa2..b7a5402c8 100644
--- a/nova/tests/scheduler/test_host_filters.py
+++ b/nova/tests/scheduler/test_host_filters.py
@@ -15,17 +15,45 @@
Tests For Scheduler Host Filters.
"""
+import httplib
import json
+import stubout
from nova import context
from nova import exception
from nova import flags
from nova.scheduler import filters
+from nova.scheduler.filters.trusted_filter import AttestationService
from nova import test
from nova.tests.scheduler import fakes
from nova import utils
+DATA = ''
+
+
+def stub_out_https_backend(stubs):
+ """
+ Stubs out the httplib.HTTPRequest.getresponse to return
+ faked-out data instead of grabbing actual contents of a resource
+
+ The stubbed getresponse() returns an iterator over
+ the data "I am a teapot, short and stout\n"
+
+ :param stubs: Set of stubout stubs
+ """
+
+ class FakeHTTPResponse(object):
+
+ def read(self):
+ return DATA
+
+ def fake_do_request(self, *args, **kwargs):
+ return httplib.OK, FakeHTTPResponse()
+
+ stubs.Set(AttestationService, '_do_request', fake_do_request)
+
+
class TestFilter(filters.BaseHostFilter):
pass
@@ -40,6 +68,8 @@ class HostFiltersTestCase(test.TestCase):
def setUp(self):
super(HostFiltersTestCase, self).setUp()
+ self.stubs = stubout.StubOutForTesting()
+ stub_out_https_backend(self.stubs)
self.context = context.RequestContext('fake', 'fake')
self.json_query = json.dumps(
['and', ['>=', '$free_ram_mb', 1024],
@@ -586,6 +616,57 @@ class HostFiltersTestCase(test.TestCase):
filter_properties = {'scheduler_hints': {'query': json.dumps(raw)}}
self.assertTrue(filt_cls.host_passes(host, filter_properties))
+ def test_trusted_filter_default_passes(self):
+ self._stub_service_is_up(True)
+ filt_cls = self.class_map['TrustedFilter']()
+ filter_properties = {'instance_type': {'memory_mb': 1024}}
+ host = fakes.FakeHostState('host1', 'compute', {})
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+
+ def test_trusted_filter_trusted_and_trusted_passes(self):
+ global DATA
+ DATA = '{"hosts":[{"host_name":"host1","trust_lvl":"trusted"}]}'
+ self._stub_service_is_up(True)
+ filt_cls = self.class_map['TrustedFilter']()
+ extra_specs = {'trusted_host': 'trusted'}
+ filter_properties = {'instance_type': {'memory_mb': 1024,
+ 'extra_specs': extra_specs}}
+ host = fakes.FakeHostState('host1', 'compute', {})
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+
+ def test_trusted_filter_trusted_and_untrusted_fails(self):
+ global DATA
+ DATA = '{"hosts":[{"host_name":"host1","trust_lvl":"untrusted"}]}'
+ self._stub_service_is_up(True)
+ filt_cls = self.class_map['TrustedFilter']()
+ extra_specs = {'trusted_host': 'trusted'}
+ filter_properties = {'instance_type': {'memory_mb': 1024,
+ 'extra_specs': extra_specs}}
+ host = fakes.FakeHostState('host1', 'compute', {})
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
+ def test_trusted_filter_untrusted_and_trusted_fails(self):
+ global DATA
+ DATA = '{"hosts":[{"host_name":"host1","trust_lvl":"trusted"}]}'
+ self._stub_service_is_up(True)
+ filt_cls = self.class_map['TrustedFilter']()
+ extra_specs = {'trusted_host': 'untrusted'}
+ filter_properties = {'instance_type': {'memory_mb': 1024,
+ 'extra_specs': extra_specs}}
+ host = fakes.FakeHostState('host1', 'compute', {})
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
+ def test_trusted_filter_untrusted_and_untrusted_passes(self):
+ global DATA
+ DATA = '{"hosts":[{"host_name":"host1","trust_lvl":"untrusted"}]}'
+ self._stub_service_is_up(True)
+ filt_cls = self.class_map['TrustedFilter']()
+ extra_specs = {'trusted_host': 'untrusted'}
+ filter_properties = {'instance_type': {'memory_mb': 1024,
+ 'extra_specs': extra_specs}}
+ host = fakes.FakeHostState('host1', 'compute', {})
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+
def test_core_filter_passes(self):
filt_cls = self.class_map['CoreFilter']()
filter_properties = {'instance_type': {'vcpus': 1}}