summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDon Dugger <donald.d.dugger@intel.com>2012-05-08 18:30:57 -0600
committerDon Dugger <donald.d.dugger@intel.com>2012-05-28 23:01:42 -0600
commit14c01e09b68b367d708c6ddd6f3d4e440687727c (patch)
treec1fdbb6b1b41e75a974089ae4544a4ae819dac6b
parentd7e613dabc2dbc28d9405a5b450dc2b4dfa9d47b (diff)
downloadnova-14c01e09b68b367d708c6ddd6f3d4e440687727c.tar.gz
nova-14c01e09b68b367d708c6ddd6f3d4e440687727c.tar.xz
nova-14c01e09b68b367d708c6ddd6f3d4e440687727c.zip
Add scheduler filter for trustedness of a host
Implements blueprint trusted-computing-pools Add a scheduling filter that filters based upon the trustedness of a node. A request is sent to the attestation service to disover the trustedness of the target node and, only if it matches the `trust_host' key/value pair in the `extra_specs' for the instance type, then the instance can be started on that node. More details can be found in the docspec for the filter in: nova/scheduler/filters/trusted_filter.py To setup an attestation server go to the Open Attestation Project at: https://github.com/OpenAttestation/OpenAttestation Also add 5 tests for the new filter that verifies: 1) Schedule works with no trust in the extra specs 2) Schedule works with trusted instance and trusted host 3) Schedule works with untrusted instance and untrusted host 4) Schedule fails with trusted instance and untrusted host 5) Scheduel fails with untrusted instance and trusted host Signed-off-by: Don Dugger <donald.d.dugger@intel.com> Signed-off-by: Fred Yang <fred.yang@intel.com> Change-Id: Iafa6aed8061f6cd4630367553aee14bd4b0263e2
-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}}