Merge "Remove deprecated TrustedFilter"
This commit is contained in:
commit
690f8e4e24
@ -753,12 +753,6 @@ With the API, use the ``os:scheduler_hints`` key:
|
||||
}
|
||||
}
|
||||
|
||||
TrustedFilter
|
||||
-------------
|
||||
|
||||
Filters hosts based on their trust. Only passes hosts that meet the trust
|
||||
requirements specified in the instance properties.
|
||||
|
||||
.. _TypeAffinityFilter:
|
||||
|
||||
TypeAffinityFilter
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 123 KiB |
Binary file not shown.
Before Width: | Height: | Size: 66 KiB |
@ -6,137 +6,6 @@ OpenStack Compute can be integrated with various third-party technologies to
|
||||
increase security. For more information, see the `OpenStack Security Guide
|
||||
<https://docs.openstack.org/security-guide/>`_.
|
||||
|
||||
Trusted compute pools
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note:: The ``TrustedFilter`` was deprecated in the 16.0.0 Pike release and
|
||||
will be removed in the 17.0.0 Queens release.
|
||||
|
||||
Administrators can designate a group of compute hosts as trusted using trusted
|
||||
compute pools. The trusted hosts use hardware-based security features, such as
|
||||
the Intel Trusted Execution Technology (TXT), to provide an additional level of
|
||||
security. Combined with an external stand-alone, web-based remote attestation
|
||||
server, cloud providers can ensure that the compute node runs only software
|
||||
with verified measurements and can ensure a secure cloud stack.
|
||||
|
||||
Trusted compute pools provide the ability for cloud subscribers to request
|
||||
services run only on verified compute nodes.
|
||||
|
||||
The remote attestation server performs node verification like this:
|
||||
|
||||
1. Compute nodes boot with Intel TXT technology enabled.
|
||||
|
||||
2. The compute node BIOS, hypervisor, and operating system are measured.
|
||||
|
||||
3. When the attestation server challenges the compute node, the measured data
|
||||
is sent to the attestation server.
|
||||
|
||||
4. The attestation server verifies the measurements against a known good
|
||||
database to determine node trustworthiness.
|
||||
|
||||
A description of how to set up an attestation service is beyond the scope of
|
||||
this document. For an open source project that you can use to implement an
|
||||
attestation service, see the `Open Attestation
|
||||
<https://github.com/OpenAttestation/OpenAttestation>`__ project.
|
||||
|
||||
.. figure:: figures/OpenStackTrustedComputePool1.png
|
||||
|
||||
**Configuring Compute to use trusted compute pools**
|
||||
|
||||
#. Enable scheduling support for trusted compute pools by adding these lines to
|
||||
the ``DEFAULT`` section of the ``/etc/nova/nova.conf`` file:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[DEFAULT]
|
||||
compute_scheduler_driver=nova.scheduler.filter_scheduler.FilterScheduler
|
||||
scheduler_available_filters=nova.scheduler.filters.all_filters
|
||||
scheduler_default_filters=AvailabilityZoneFilter,RamFilter,ComputeFilter,TrustedFilter
|
||||
|
||||
#. Specify the connection information for your attestation service by adding
|
||||
these lines to the ``trusted_computing`` section of the
|
||||
``/etc/nova/nova.conf`` file:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[trusted_computing]
|
||||
attestation_server = 10.1.71.206
|
||||
attestation_port = 8443
|
||||
# If using OAT v2.0 after, use this port:
|
||||
# attestation_port = 8181
|
||||
attestation_server_ca_file = /etc/nova/ssl.10.1.71.206.crt
|
||||
# If using OAT v1.5, use this api_url:
|
||||
attestation_api_url = /AttestationService/resources
|
||||
# If using OAT pre-v1.5, use this api_url:
|
||||
# attestation_api_url = /OpenAttestationWebServices/V1.0
|
||||
attestation_auth_blob = i-am-openstack
|
||||
|
||||
In this example:
|
||||
|
||||
``server``
|
||||
Host name or IP address of the host that runs the attestation service
|
||||
|
||||
``port``
|
||||
HTTPS port for the attestation service
|
||||
|
||||
``server_ca_file``
|
||||
Certificate file used to verify the attestation server's identity
|
||||
|
||||
``api_url``
|
||||
The attestation service's URL path
|
||||
|
||||
``auth_blob``
|
||||
An authentication blob, required by the attestation service.
|
||||
|
||||
#. Save the file, and restart the ``nova-compute`` and ``nova-scheduler``
|
||||
service to pick up the changes.
|
||||
|
||||
To customize the trusted compute pools, use these configuration option
|
||||
settings:
|
||||
|
||||
.. list-table:: **Description of trusted computing configuration options**
|
||||
:header-rows: 2
|
||||
|
||||
* - Configuration option = Default value
|
||||
- Description
|
||||
* - [trusted_computing]
|
||||
-
|
||||
* - attestation_api_url = /OpenAttestationWebServices/V1.0
|
||||
- (StrOpt) Attestation web API URL
|
||||
* - attestation_auth_blob = None
|
||||
- (StrOpt) Attestation authorization blob - must change
|
||||
* - attestation_auth_timeout = 60
|
||||
- (IntOpt) Attestation status cache valid period length
|
||||
* - attestation_insecure_ssl = False
|
||||
- (BoolOpt) Disable SSL cert verification for Attestation service
|
||||
* - attestation_port = 8443
|
||||
- (StrOpt) Attestation server port
|
||||
* - attestation_server = None
|
||||
- (StrOpt) Attestation server HTTP
|
||||
* - attestation_server_ca_file = None
|
||||
- (StrOpt) Attestation server Cert file for Identity verification
|
||||
|
||||
**Specifying trusted flavors**
|
||||
|
||||
#. Flavors can be designated as trusted using the :command:`openstack flavor
|
||||
set` command. In this example, the ``m1.tiny`` flavor is being set as
|
||||
trusted:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack flavor set --property trusted_host=trusted m1.tiny
|
||||
|
||||
#. You can request that your instance is run on a trusted host by specifying a
|
||||
trusted flavor when booting the instance:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack server create --flavor m1.tiny \
|
||||
--key-name myKeypairName --image myImageID newInstanceName
|
||||
|
||||
|
||||
.. figure:: figures/OpenStackTrustedComputePool2.png
|
||||
|
||||
Encrypt Compute metadata traffic
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -19,7 +19,6 @@ System administration
|
||||
live-migration-usage.rst
|
||||
remote-console-access.rst
|
||||
service-groups.rst
|
||||
security.rst
|
||||
node-down.rst
|
||||
adv-config.rst
|
||||
|
||||
|
@ -154,14 +154,6 @@ There are many standard filter classes which may be used
|
||||
a set of instances.
|
||||
* |RetryFilter| - filters hosts that have been attempted for scheduling.
|
||||
Only passes hosts that have not been previously attempted.
|
||||
* |TrustedFilter| (EXPERIMENTAL) - filters hosts based on their trust. Only
|
||||
passes hosts that meet the trust requirements specified in the instance
|
||||
properties.
|
||||
|
||||
.. warning:: TrustedFilter is deprecated for removal in the 17.0.0 Queens
|
||||
release. There is no replacement planned for this filter. It has been
|
||||
marked experimental since its inception. It is incomplete and not tested.
|
||||
|
||||
* |TypeAffinityFilter| - Only passes hosts that are not already running an
|
||||
instance of the requested type.
|
||||
|
||||
@ -304,14 +296,6 @@ exception even if the problem is related to 1:N compute nodes. If you see that
|
||||
case in the scheduler logs, then your problem is most likely related to a
|
||||
compute problem and you should check the compute logs.
|
||||
|
||||
The |TrustedFilter| filters hosts based on their trust. Only passes hosts
|
||||
that match the trust requested in the ``extra_specs`` for the flavor. The key
|
||||
for this filter must be scope format as ``trust:trusted_host``, where ``trust``
|
||||
is the scope of the key and ``trusted_host`` is the actual key value.
|
||||
The value of this pair (``trusted``/``untrusted``) must match the
|
||||
integrity of a host (obtained from the Attestation service) before it is
|
||||
passed by the |TrustedFilter|.
|
||||
|
||||
The |NUMATopologyFilter| considers the NUMA topology that was specified for the instance
|
||||
through the use of flavor extra_specs in combination with the image properties, as
|
||||
described in detail in the related nova-spec document:
|
||||
@ -501,7 +485,6 @@ in :mod:`nova.tests.scheduler`.
|
||||
.. |DifferentHostFilter| replace:: :class:`DifferentHostFilter <nova.scheduler.filters.affinity_filter.DifferentHostFilter>`
|
||||
.. |SameHostFilter| replace:: :class:`SameHostFilter <nova.scheduler.filters.affinity_filter.SameHostFilter>`
|
||||
.. |RetryFilter| replace:: :class:`RetryFilter <nova.scheduler.filters.retry_filter.RetryFilter>`
|
||||
.. |TrustedFilter| replace:: :class:`TrustedFilter <nova.scheduler.filters.trusted_filter.TrustedFilter>`
|
||||
.. |TypeAffinityFilter| replace:: :class:`TypeAffinityFilter <nova.scheduler.filters.type_filter.TypeAffinityFilter>`
|
||||
.. |AggregateTypeAffinityFilter| replace:: :class:`AggregateTypeAffinityFilter <nova.scheduler.filters.type_filter.AggregateTypeAffinityFilter>`
|
||||
.. |ServerGroupAntiAffinityFilter| replace:: :class:`ServerGroupAntiAffinityFilter <nova.scheduler.filters.affinity_filter.ServerGroupAntiAffinityFilter>`
|
||||
|
@ -610,207 +610,6 @@ Related options:
|
||||
* aggregate_image_properties_isolation_namespace
|
||||
""")]
|
||||
|
||||
trust_group = cfg.OptGroup(name="trusted_computing",
|
||||
title="Trust parameters",
|
||||
help="""
|
||||
Configuration options for enabling Trusted Platform Module.
|
||||
""")
|
||||
|
||||
trusted_opts = [
|
||||
cfg.HostAddressOpt("attestation_server",
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason="Incomplete filter",
|
||||
deprecated_since="Pike",
|
||||
help="""
|
||||
The host to use as the attestation server.
|
||||
|
||||
Cloud computing pools can involve thousands of compute nodes located at
|
||||
different geographical locations, making it difficult for cloud providers to
|
||||
identify a node's trustworthiness. When using the Trusted filter, users can
|
||||
request that their VMs only be placed on nodes that have been verified by the
|
||||
attestation server specified in this option.
|
||||
|
||||
This option is only used by the FilterScheduler and its subclasses; if you use
|
||||
a different scheduler, this option has no effect. Also note that this setting
|
||||
only affects scheduling if the 'TrustedFilter' filter is enabled.
|
||||
|
||||
Possible values:
|
||||
|
||||
* A string representing the host name or IP address of the attestation server,
|
||||
or an empty string.
|
||||
|
||||
Related options:
|
||||
|
||||
* attestation_server_ca_file
|
||||
* attestation_port
|
||||
* attestation_api_url
|
||||
* attestation_auth_blob
|
||||
* attestation_auth_timeout
|
||||
* attestation_insecure_ssl
|
||||
"""),
|
||||
cfg.StrOpt("attestation_server_ca_file",
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason="Incomplete filter",
|
||||
deprecated_since="Pike",
|
||||
help="""
|
||||
The absolute path to the certificate to use for authentication when connecting
|
||||
to the attestation server. See the `attestation_server` help text for more
|
||||
information about host verification.
|
||||
|
||||
This option is only used by the FilterScheduler and its subclasses; if you use
|
||||
a different scheduler, this option has no effect. Also note that this setting
|
||||
only affects scheduling if the 'TrustedFilter' filter is enabled.
|
||||
|
||||
Possible values:
|
||||
|
||||
* A string representing the path to the authentication certificate for the
|
||||
attestation server, or an empty string.
|
||||
|
||||
Related options:
|
||||
|
||||
* attestation_server
|
||||
* attestation_port
|
||||
* attestation_api_url
|
||||
* attestation_auth_blob
|
||||
* attestation_auth_timeout
|
||||
* attestation_insecure_ssl
|
||||
"""),
|
||||
cfg.PortOpt("attestation_port",
|
||||
default=8443,
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason="Incomplete filter",
|
||||
deprecated_since="Pike",
|
||||
help="""
|
||||
The port to use when connecting to the attestation server. See the
|
||||
`attestation_server` help text for more information about host verification.
|
||||
|
||||
This option is only used by the FilterScheduler and its subclasses; if you use
|
||||
a different scheduler, this option has no effect. Also note that this setting
|
||||
only affects scheduling if the 'TrustedFilter' filter is enabled.
|
||||
|
||||
Related options:
|
||||
|
||||
* attestation_server
|
||||
* attestation_server_ca_file
|
||||
* attestation_api_url
|
||||
* attestation_auth_blob
|
||||
* attestation_auth_timeout
|
||||
* attestation_insecure_ssl
|
||||
"""),
|
||||
cfg.StrOpt("attestation_api_url",
|
||||
default="/OpenAttestationWebServices/V1.0",
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason="Incomplete filter",
|
||||
deprecated_since="Pike",
|
||||
help="""
|
||||
The URL on the attestation server to use. See the `attestation_server` help
|
||||
text for more information about host verification.
|
||||
|
||||
This value must be just that path portion of the full URL, as it will be joined
|
||||
to the host specified in the attestation_server option.
|
||||
|
||||
This option is only used by the FilterScheduler and its subclasses; if you use
|
||||
a different scheduler, this option has no effect. Also note that this setting
|
||||
only affects scheduling if the 'TrustedFilter' filter is enabled.
|
||||
|
||||
Possible values:
|
||||
|
||||
* A valid URL string of the attestation server, or an empty string.
|
||||
|
||||
Related options:
|
||||
|
||||
* attestation_server
|
||||
* attestation_server_ca_file
|
||||
* attestation_port
|
||||
* attestation_auth_blob
|
||||
* attestation_auth_timeout
|
||||
* attestation_insecure_ssl
|
||||
"""),
|
||||
cfg.StrOpt("attestation_auth_blob",
|
||||
secret=True,
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason="Incomplete filter",
|
||||
deprecated_since="Pike",
|
||||
help="""
|
||||
Attestation servers require a specific blob that is used to authenticate. The
|
||||
content and format of the blob are determined by the particular attestation
|
||||
server being used. There is no default value; you must supply the value as
|
||||
specified by your attestation service. See the `attestation_server` help text
|
||||
for more information about host verification.
|
||||
|
||||
This option is only used by the FilterScheduler and its subclasses; if you use
|
||||
a different scheduler, this option has no effect. Also note that this setting
|
||||
only affects scheduling if the 'TrustedFilter' filter is enabled.
|
||||
|
||||
Possible values:
|
||||
|
||||
* A string containing the specific blob required by the attestation server, or
|
||||
an empty string.
|
||||
|
||||
Related options:
|
||||
|
||||
* attestation_server
|
||||
* attestation_server_ca_file
|
||||
* attestation_port
|
||||
* attestation_api_url
|
||||
* attestation_auth_timeout
|
||||
* attestation_insecure_ssl
|
||||
"""),
|
||||
cfg.IntOpt("attestation_auth_timeout",
|
||||
default=60,
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason="Incomplete filter",
|
||||
deprecated_since="Pike",
|
||||
min=0,
|
||||
help="""
|
||||
This value controls how long a successful attestation is cached. Once this
|
||||
period has elapsed, a new attestation request will be made. See the
|
||||
`attestation_server` help text for more information about host verification.
|
||||
|
||||
This option is only used by the FilterScheduler and its subclasses; if you use
|
||||
a different scheduler, this option has no effect. Also note that this setting
|
||||
only affects scheduling if the 'TrustedFilter' filter is enabled.
|
||||
|
||||
Possible values:
|
||||
|
||||
* A integer value, corresponding to the timeout interval for attestations in
|
||||
seconds. Any integer is valid, although setting this to zero or negative
|
||||
values can greatly impact performance when using an attestation service.
|
||||
|
||||
Related options:
|
||||
|
||||
* attestation_server
|
||||
* attestation_server_ca_file
|
||||
* attestation_port
|
||||
* attestation_api_url
|
||||
* attestation_auth_blob
|
||||
* attestation_insecure_ssl
|
||||
"""),
|
||||
cfg.BoolOpt("attestation_insecure_ssl",
|
||||
default=False,
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason="Incomplete filter",
|
||||
deprecated_since="Pike",
|
||||
help="""
|
||||
When set to True, the SSL certificate verification is skipped for the
|
||||
attestation service. See the `attestation_server` help text for more
|
||||
information about host verification.
|
||||
|
||||
This option is only used by the FilterScheduler and its subclasses; if you use
|
||||
a different scheduler, this option has no effect. Also note that this setting
|
||||
only affects scheduling if the 'TrustedFilter' filter is enabled.
|
||||
|
||||
Related options:
|
||||
|
||||
* attestation_server
|
||||
* attestation_server_ca_file
|
||||
* attestation_port
|
||||
* attestation_api_url
|
||||
* attestation_auth_blob
|
||||
* attestation_auth_timeout
|
||||
"""),
|
||||
]
|
||||
|
||||
metrics_group = cfg.OptGroup(name="metrics",
|
||||
title="Metrics parameters",
|
||||
help="""
|
||||
@ -938,9 +737,6 @@ def register_opts(conf):
|
||||
conf.register_group(filter_scheduler_group)
|
||||
conf.register_opts(filter_scheduler_opts, group=filter_scheduler_group)
|
||||
|
||||
conf.register_group(trust_group)
|
||||
conf.register_opts(trusted_opts, group=trust_group)
|
||||
|
||||
conf.register_group(metrics_group)
|
||||
conf.register_opts(metrics_weight_opts, group=metrics_group)
|
||||
|
||||
@ -948,5 +744,4 @@ def register_opts(conf):
|
||||
def list_opts():
|
||||
return {scheduler_group: scheduler_opts,
|
||||
filter_scheduler_group: filter_scheduler_opts,
|
||||
trust_group: trusted_opts,
|
||||
metrics_group: metrics_weight_opts}
|
||||
|
@ -1,253 +0,0 @@
|
||||
# Copyright (c) 2012 Intel, Inc.
|
||||
# Copyright (c) 2011-2012 OpenStack Foundation
|
||||
# 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 (EXPERIMENTAL).
|
||||
|
||||
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]
|
||||
debug=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
|
||||
"""
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_log import versionutils
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import timeutils
|
||||
import requests
|
||||
|
||||
import nova.conf
|
||||
from nova import context
|
||||
from nova.i18n import _LW
|
||||
from nova import objects
|
||||
from nova.scheduler import filters
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = nova.conf.CONF
|
||||
|
||||
|
||||
class AttestationService(object):
|
||||
# Provide access wrapper to attestation server to get integrity report.
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = CONF.trusted_computing.attestation_api_url
|
||||
self.host = CONF.trusted_computing.attestation_server
|
||||
self.port = CONF.trusted_computing.attestation_port
|
||||
self.auth_blob = CONF.trusted_computing.attestation_auth_blob
|
||||
self.key_file = None
|
||||
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 = "https://%s:%d%s/%s" % (self.host, self.port,
|
||||
self.api_url, action_url)
|
||||
try:
|
||||
res = requests.request(method, action_url, data=body,
|
||||
headers=headers, cert=self.cert,
|
||||
verify=self.verify)
|
||||
status_code = res.status_code
|
||||
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 (TypeError, ValueError):
|
||||
return requests.codes.OK, res.text
|
||||
return status_code, None
|
||||
|
||||
except requests.exceptions.RequestException:
|
||||
return IOError, None
|
||||
|
||||
def _request(self, cmd, subcmd, hosts):
|
||||
body = {}
|
||||
body['count'] = len(hosts)
|
||||
body['hosts'] = hosts
|
||||
cooked = jsonutils.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)
|
||||
return status, res
|
||||
|
||||
def do_attestation(self, hosts):
|
||||
"""Attests compute nodes through OAT service.
|
||||
|
||||
:param hosts: hosts list to be attested
|
||||
:returns: dictionary for trust level and validate time
|
||||
"""
|
||||
result = None
|
||||
|
||||
status, data = self._request("POST", "PollHosts", hosts)
|
||||
if data is not None:
|
||||
result = data.get('hosts')
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class ComputeAttestationCache(object):
|
||||
"""Cache for compute node attestation
|
||||
|
||||
Cache compute node's trust level for sometime,
|
||||
if the cache is out of date, poll OAT service to flush the
|
||||
cache.
|
||||
|
||||
OAT service may have cache also. OAT service's cache valid time
|
||||
should be set shorter than trusted filter's cache valid time.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.attestservice = AttestationService()
|
||||
self.compute_nodes = {}
|
||||
admin = context.get_admin_context()
|
||||
|
||||
# Fetch compute node list to initialize the compute_nodes,
|
||||
# so that we don't need poll OAT service one by one for each
|
||||
# host in the first round that scheduler invokes us.
|
||||
computes = objects.ComputeNodeList.get_all(admin)
|
||||
for compute in computes:
|
||||
host = compute.hypervisor_hostname
|
||||
self._init_cache_entry(host)
|
||||
|
||||
def _cache_valid(self, host):
|
||||
cachevalid = False
|
||||
if host in self.compute_nodes:
|
||||
node_stats = self.compute_nodes.get(host)
|
||||
if not timeutils.is_older_than(
|
||||
node_stats['vtime'],
|
||||
CONF.trusted_computing.attestation_auth_timeout):
|
||||
cachevalid = True
|
||||
return cachevalid
|
||||
|
||||
def _init_cache_entry(self, host):
|
||||
self.compute_nodes[host] = {
|
||||
'trust_lvl': 'unknown',
|
||||
'vtime': timeutils.normalize_time(
|
||||
timeutils.parse_isotime("1970-01-01T00:00:00Z"))}
|
||||
|
||||
def _invalidate_caches(self):
|
||||
for host in self.compute_nodes:
|
||||
self._init_cache_entry(host)
|
||||
|
||||
def _update_cache_entry(self, state):
|
||||
entry = {}
|
||||
|
||||
host = state['host_name']
|
||||
entry['trust_lvl'] = state['trust_lvl']
|
||||
|
||||
try:
|
||||
# Normalize as naive object to interoperate with utcnow().
|
||||
entry['vtime'] = timeutils.normalize_time(
|
||||
timeutils.parse_isotime(state['vtime']))
|
||||
except ValueError:
|
||||
try:
|
||||
# Mt. Wilson does not necessarily return an ISO8601 formatted
|
||||
# `vtime`, so we should try to parse it as a string formatted
|
||||
# datetime.
|
||||
vtime = timeutils.parse_strtime(state['vtime'], fmt="%c")
|
||||
entry['vtime'] = timeutils.normalize_time(vtime)
|
||||
except ValueError:
|
||||
# Mark the system as un-trusted if get invalid vtime.
|
||||
entry['trust_lvl'] = 'unknown'
|
||||
entry['vtime'] = timeutils.utcnow()
|
||||
|
||||
self.compute_nodes[host] = entry
|
||||
|
||||
def _update_cache(self):
|
||||
self._invalidate_caches()
|
||||
states = self.attestservice.do_attestation(
|
||||
list(self.compute_nodes.keys()))
|
||||
if states is None:
|
||||
return
|
||||
for state in states:
|
||||
self._update_cache_entry(state)
|
||||
|
||||
def get_host_attestation(self, host):
|
||||
"""Check host's trust level."""
|
||||
if host not in self.compute_nodes:
|
||||
self._init_cache_entry(host)
|
||||
if not self._cache_valid(host):
|
||||
self._update_cache()
|
||||
level = self.compute_nodes.get(host).get('trust_lvl')
|
||||
return level
|
||||
|
||||
|
||||
class ComputeAttestation(object):
|
||||
def __init__(self):
|
||||
self.caches = ComputeAttestationCache()
|
||||
|
||||
def is_trusted(self, host, trust):
|
||||
level = self.caches.get_host_attestation(host)
|
||||
return trust == level
|
||||
|
||||
|
||||
class TrustedFilter(filters.BaseHostFilter):
|
||||
"""Trusted filter to support Trusted Compute Pools."""
|
||||
|
||||
RUN_ON_REBUILD = False
|
||||
|
||||
def __init__(self):
|
||||
self.compute_attestation = ComputeAttestation()
|
||||
msg = _LW('The TrustedFilter is deprecated as it has been marked '
|
||||
'experimental for some time with no tests. It will be '
|
||||
'removed in the 17.0.0 Queens release.')
|
||||
versionutils.report_deprecated_feature(LOG, msg)
|
||||
|
||||
# The hosts the instances are running on doesn't change within a request
|
||||
run_filter_once_per_request = True
|
||||
|
||||
def host_passes(self, host_state, spec_obj):
|
||||
instance_type = spec_obj.flavor
|
||||
extra = (instance_type.extra_specs
|
||||
if 'extra_specs' in instance_type else {})
|
||||
trust = extra.get('trust:trusted_host')
|
||||
host = host_state.nodename
|
||||
if trust:
|
||||
return self.compute_attestation.is_trusted(host, trust)
|
||||
return True
|
@ -61,8 +61,6 @@ class TestAggregateInstanceExtraSpecsFilter(test.NoDBTestCase):
|
||||
'opt1': '1',
|
||||
# Scoped extra spec that applies to this filter
|
||||
'aggregate_instance_extra_specs:opt2': '2',
|
||||
# Scoped extra spec that does not apply to this filter
|
||||
'trust:trusted_host': 'true',
|
||||
}
|
||||
self._do_test_aggregate_filter_extra_specs(especs, passes=True)
|
||||
|
||||
@ -73,8 +71,6 @@ class TestAggregateInstanceExtraSpecsFilter(test.NoDBTestCase):
|
||||
'opt1': '1',
|
||||
# Scoped extra spec that applies to this filter
|
||||
'aggregate_instance_extra_specs:opt1': '3',
|
||||
# Scoped extra spec that does not apply to this filter
|
||||
'trust:trusted_host': 'true',
|
||||
}
|
||||
self._do_test_aggregate_filter_extra_specs(especs, passes=True)
|
||||
|
||||
@ -91,7 +87,6 @@ class TestAggregateInstanceExtraSpecsFilter(test.NoDBTestCase):
|
||||
agg_mock.return_value = {'opt1': set(['1']), 'opt2': set(['2'])}
|
||||
especs = {
|
||||
'opt1': '1',
|
||||
'opt2': '222',
|
||||
'trust:trusted_host': 'true'
|
||||
'opt2': '222'
|
||||
}
|
||||
self._do_test_aggregate_filter_extra_specs(especs, passes=False)
|
||||
|
@ -107,20 +107,19 @@ class TestComputeCapabilitiesFilter(test.NoDBTestCase):
|
||||
def test_compute_filter_passes_extra_specs_simple(self):
|
||||
self._do_test_compute_filter_extra_specs(
|
||||
ecaps={'stats': {'opt1': 1, 'opt2': 2}},
|
||||
especs={'opt1': '1', 'opt2': '2', 'trust:trusted_host': 'true'},
|
||||
especs={'opt1': '1', 'opt2': '2'},
|
||||
passes=True)
|
||||
|
||||
def test_compute_filter_fails_extra_specs_simple(self):
|
||||
self._do_test_compute_filter_extra_specs(
|
||||
ecaps={'stats': {'opt1': 1, 'opt2': 2}},
|
||||
especs={'opt1': '1', 'opt2': '222', 'trust:trusted_host': 'true'},
|
||||
especs={'opt1': '1', 'opt2': '222'},
|
||||
passes=False)
|
||||
|
||||
def test_compute_filter_pass_extra_specs_simple_with_scope(self):
|
||||
self._do_test_compute_filter_extra_specs(
|
||||
ecaps={'stats': {'opt1': 1, 'opt2': 2}},
|
||||
especs={'capabilities:opt1': '1',
|
||||
'trust:trusted_host': 'true'},
|
||||
especs={'capabilities:opt1': '1'},
|
||||
passes=True)
|
||||
|
||||
def test_compute_filter_pass_extra_specs_same_as_scope(self):
|
||||
@ -140,15 +139,13 @@ class TestComputeCapabilitiesFilter(test.NoDBTestCase):
|
||||
def test_compute_filter_extra_specs_simple_with_wrong_scope(self):
|
||||
self._do_test_compute_filter_extra_specs(
|
||||
ecaps={'opt1': 1, 'opt2': 2},
|
||||
especs={'wrong_scope:opt1': '1',
|
||||
'trust:trusted_host': 'true'},
|
||||
especs={'wrong_scope:opt1': '1'},
|
||||
passes=True)
|
||||
|
||||
def test_compute_filter_extra_specs_pass_multi_level_with_scope(self):
|
||||
self._do_test_compute_filter_extra_specs(
|
||||
ecaps={'stats': {'opt1': {'a': 1, 'b': {'aa': 2}}, 'opt2': 2}},
|
||||
especs={'opt1:a': '1', 'capabilities:opt1:b:aa': '2',
|
||||
'trust:trusted_host': 'true'},
|
||||
especs={'opt1:a': '1', 'capabilities:opt1:b:aa': '2'},
|
||||
passes=True)
|
||||
|
||||
def test_compute_filter_pass_ram_with_backward_compatibility(self):
|
||||
|
@ -1,277 +0,0 @@
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import fixture as utils_fixture
|
||||
from oslo_utils import timeutils
|
||||
import requests
|
||||
|
||||
from nova import objects
|
||||
from nova.scheduler.filters import trusted_filter
|
||||
from nova import test
|
||||
from nova.tests.unit.scheduler import fakes
|
||||
from nova import utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class AttestationServiceTestCase(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(AttestationServiceTestCase, self).setUp()
|
||||
self.api_url = '/OpenAttestationWebServices/V1.0'
|
||||
self.host = 'localhost'
|
||||
self.port = '8443'
|
||||
self.statuses = (requests.codes.OK, requests.codes.CREATED,
|
||||
requests.codes.ACCEPTED, requests.codes.NO_CONTENT)
|
||||
|
||||
@mock.patch.object(requests, 'request')
|
||||
def test_do_request_possible_statuses(self, request_mock):
|
||||
"""This test case checks if '_do_request()' method returns
|
||||
appropriate status_code (200) and result (text converted to json),
|
||||
while status_code returned by request is in one of fourth eligible
|
||||
statuses
|
||||
"""
|
||||
|
||||
for status_code in self.statuses:
|
||||
request_mock.return_value.status_code = status_code
|
||||
request_mock.return_value.text = '{"test": "test"}'
|
||||
|
||||
attestation_service = trusted_filter.AttestationService()
|
||||
status, result = attestation_service._do_request(
|
||||
'POST', 'PollHosts', {}, {})
|
||||
|
||||
self.assertEqual(requests.codes.OK, status)
|
||||
self.assertEqual(jsonutils.loads(request_mock.return_value.text),
|
||||
result)
|
||||
|
||||
@mock.patch.object(requests, 'request')
|
||||
def test_do_request_other_status(self, request_mock):
|
||||
"""This test case checks if '_do_request()' method returns
|
||||
appropriate status (this returned by request method) and result
|
||||
(None), while status_code returned by request is not in one of fourth
|
||||
eligible statuses
|
||||
"""
|
||||
|
||||
request_mock.return_value.status_code = requests.codes.NOT_FOUND
|
||||
request_mock.return_value.text = '{"test": "test"}'
|
||||
|
||||
attestation_service = trusted_filter.AttestationService()
|
||||
status, result = attestation_service._do_request(
|
||||
'POST', 'PollHosts', {}, {})
|
||||
|
||||
self.assertEqual(requests.codes.NOT_FOUND, status)
|
||||
self.assertIsNone(result)
|
||||
|
||||
@mock.patch.object(requests, 'request')
|
||||
def test_do_request_unconvertible_text(self, request_mock):
|
||||
for status_code in self.statuses:
|
||||
# this unconvertible_texts leads to TypeError and ValueError
|
||||
# in jsonutils.loads(res.text) in _do_request() method
|
||||
for unconvertible_text in ({"test": "test"}, '{}{}'):
|
||||
request_mock.return_value.status_code = status_code
|
||||
request_mock.return_value.text = unconvertible_text
|
||||
|
||||
attestation_service = trusted_filter.AttestationService()
|
||||
status, result = attestation_service._do_request(
|
||||
'POST', 'PollHosts', {}, {})
|
||||
|
||||
self.assertEqual(requests.codes.OK, status)
|
||||
self.assertEqual(unconvertible_text, result)
|
||||
|
||||
|
||||
@mock.patch.object(trusted_filter.AttestationService, '_request')
|
||||
class TestTrustedFilter(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestTrustedFilter, self).setUp()
|
||||
# TrustedFilter's constructor creates the attestation cache, which
|
||||
# calls to get a list of all the compute nodes.
|
||||
fake_compute_nodes = [
|
||||
objects.ComputeNode(hypervisor_hostname='node1'),
|
||||
]
|
||||
with mock.patch('nova.objects.ComputeNodeList.get_all') as mocked:
|
||||
mocked.return_value = fake_compute_nodes
|
||||
self.filt_cls = trusted_filter.TrustedFilter()
|
||||
|
||||
def test_trusted_filter_default_passes(self, req_mock):
|
||||
spec_obj = objects.RequestSpec(
|
||||
context=mock.sentinel.ctx,
|
||||
flavor=objects.Flavor(memory_mb=1024))
|
||||
host = fakes.FakeHostState('host1', 'node1', {})
|
||||
self.assertTrue(self.filt_cls.host_passes(host, spec_obj))
|
||||
self.assertFalse(req_mock.called)
|
||||
|
||||
def test_trusted_filter_trusted_and_trusted_passes(self, req_mock):
|
||||
oat_data = {"hosts": [{"host_name": "node1",
|
||||
"trust_lvl": "trusted",
|
||||
"vtime": utils.isotime()}]}
|
||||
req_mock.return_value = requests.codes.OK, oat_data
|
||||
|
||||
extra_specs = {'trust:trusted_host': 'trusted'}
|
||||
spec_obj = objects.RequestSpec(
|
||||
context=mock.sentinel.ctx,
|
||||
flavor=objects.Flavor(memory_mb=1024,
|
||||
extra_specs=extra_specs))
|
||||
host = fakes.FakeHostState('host1', 'node1', {})
|
||||
self.assertTrue(self.filt_cls.host_passes(host, spec_obj))
|
||||
req_mock.assert_called_once_with("POST", "PollHosts", ["node1"])
|
||||
|
||||
def test_trusted_filter_trusted_and_untrusted_fails(self, req_mock):
|
||||
oat_data = {"hosts": [{"host_name": "node1",
|
||||
"trust_lvl": "untrusted",
|
||||
"vtime": utils.isotime()}]}
|
||||
req_mock.return_value = requests.codes.OK, oat_data
|
||||
extra_specs = {'trust:trusted_host': 'trusted'}
|
||||
spec_obj = objects.RequestSpec(
|
||||
context=mock.sentinel.ctx,
|
||||
flavor=objects.Flavor(memory_mb=1024,
|
||||
extra_specs=extra_specs))
|
||||
host = fakes.FakeHostState('host1', 'node1', {})
|
||||
self.assertFalse(self.filt_cls.host_passes(host, spec_obj))
|
||||
|
||||
def test_trusted_filter_untrusted_and_trusted_fails(self, req_mock):
|
||||
oat_data = {"hosts": [{"host_name": "node",
|
||||
"trust_lvl": "trusted",
|
||||
"vtime": utils.isotime()}]}
|
||||
req_mock.return_value = requests.codes.OK, oat_data
|
||||
extra_specs = {'trust:trusted_host': 'untrusted'}
|
||||
spec_obj = objects.RequestSpec(
|
||||
context=mock.sentinel.ctx,
|
||||
flavor=objects.Flavor(memory_mb=1024,
|
||||
extra_specs=extra_specs))
|
||||
host = fakes.FakeHostState('host1', 'node1', {})
|
||||
self.assertFalse(self.filt_cls.host_passes(host, spec_obj))
|
||||
|
||||
def test_trusted_filter_untrusted_and_untrusted_passes(self, req_mock):
|
||||
oat_data = {"hosts": [{"host_name": "node1",
|
||||
"trust_lvl": "untrusted",
|
||||
"vtime": utils.isotime()}]}
|
||||
req_mock.return_value = requests.codes.OK, oat_data
|
||||
extra_specs = {'trust:trusted_host': 'untrusted'}
|
||||
spec_obj = objects.RequestSpec(
|
||||
context=mock.sentinel.ctx,
|
||||
flavor=objects.Flavor(memory_mb=1024,
|
||||
extra_specs=extra_specs))
|
||||
host = fakes.FakeHostState('host1', 'node1', {})
|
||||
self.assertTrue(self.filt_cls.host_passes(host, spec_obj))
|
||||
|
||||
def test_trusted_filter_update_cache(self, req_mock):
|
||||
oat_data = {"hosts": [{"host_name": "node1",
|
||||
"trust_lvl": "untrusted",
|
||||
"vtime": utils.isotime()}]}
|
||||
|
||||
req_mock.return_value = requests.codes.OK, oat_data
|
||||
extra_specs = {'trust:trusted_host': 'untrusted'}
|
||||
spec_obj = objects.RequestSpec(
|
||||
context=mock.sentinel.ctx,
|
||||
flavor=objects.Flavor(memory_mb=1024,
|
||||
extra_specs=extra_specs))
|
||||
host = fakes.FakeHostState('host1', 'node1', {})
|
||||
|
||||
self.filt_cls.host_passes(host, spec_obj) # Fill the caches
|
||||
|
||||
req_mock.reset_mock()
|
||||
self.filt_cls.host_passes(host, spec_obj)
|
||||
self.assertFalse(req_mock.called)
|
||||
|
||||
req_mock.reset_mock()
|
||||
|
||||
time_fixture = self.useFixture(utils_fixture.TimeFixture())
|
||||
time_fixture.advance_time_seconds(
|
||||
CONF.trusted_computing.attestation_auth_timeout + 80)
|
||||
self.filt_cls.host_passes(host, spec_obj)
|
||||
self.assertTrue(req_mock.called)
|
||||
|
||||
def test_trusted_filter_update_cache_timezone(self, req_mock):
|
||||
oat_data = {"hosts": [{"host_name": "node1",
|
||||
"trust_lvl": "untrusted",
|
||||
"vtime": "2012-09-09T05:10:40-04:00"}]}
|
||||
req_mock.return_value = requests.codes.OK, oat_data
|
||||
extra_specs = {'trust:trusted_host': 'untrusted'}
|
||||
spec_obj = objects.RequestSpec(
|
||||
context=mock.sentinel.ctx,
|
||||
flavor=objects.Flavor(memory_mb=1024,
|
||||
extra_specs=extra_specs))
|
||||
host = fakes.FakeHostState('host1', 'node1', {})
|
||||
|
||||
time_fixture = self.useFixture(utils_fixture.TimeFixture(
|
||||
timeutils.normalize_time(
|
||||
timeutils.parse_isotime("2012-09-09T09:10:40Z"))))
|
||||
|
||||
self.filt_cls.host_passes(host, spec_obj) # Fill the caches
|
||||
|
||||
req_mock.reset_mock()
|
||||
self.filt_cls.host_passes(host, spec_obj)
|
||||
self.assertFalse(req_mock.called)
|
||||
|
||||
req_mock.reset_mock()
|
||||
time_fixture.advance_time_seconds(
|
||||
CONF.trusted_computing.attestation_auth_timeout - 10)
|
||||
self.filt_cls.host_passes(host, spec_obj)
|
||||
self.assertFalse(req_mock.called)
|
||||
|
||||
def test_trusted_filter_combine_hosts(self, req_mock):
|
||||
fake_compute_nodes = [
|
||||
objects.ComputeNode(hypervisor_hostname='node1'),
|
||||
objects.ComputeNode(hypervisor_hostname='node2')
|
||||
]
|
||||
with mock.patch('nova.objects.ComputeNodeList.get_all') as mocked:
|
||||
mocked.return_value = fake_compute_nodes
|
||||
self.filt_cls = trusted_filter.TrustedFilter()
|
||||
oat_data = {"hosts": [{"host_name": "node1",
|
||||
"trust_lvl": "untrusted",
|
||||
"vtime": "2012-09-09T05:10:40-04:00"}]}
|
||||
req_mock.return_value = requests.codes.OK, oat_data
|
||||
extra_specs = {'trust:trusted_host': 'trusted'}
|
||||
spec_obj = objects.RequestSpec(
|
||||
context=mock.sentinel.ctx,
|
||||
flavor=objects.Flavor(memory_mb=1024,
|
||||
extra_specs=extra_specs))
|
||||
host = fakes.FakeHostState('host1', 'node1', {})
|
||||
|
||||
self.filt_cls.host_passes(host, spec_obj) # Fill the caches
|
||||
self.assertTrue(req_mock.called)
|
||||
self.assertEqual(1, req_mock.call_count)
|
||||
call_args = list(req_mock.call_args[0])
|
||||
|
||||
expected_call_args = ['POST', 'PollHosts', ['node2', 'node1']]
|
||||
self.assertJsonEqual(call_args, expected_call_args)
|
||||
|
||||
def test_trusted_filter_trusted_and_locale_formated_vtime_passes(self,
|
||||
req_mock):
|
||||
oat_data = {"hosts": [{"host_name": "host1",
|
||||
"trust_lvl": "trusted",
|
||||
"vtime": timeutils.utcnow().strftime(
|
||||
"%c")},
|
||||
{"host_name": "host2",
|
||||
"trust_lvl": "trusted",
|
||||
"vtime": timeutils.utcnow().strftime(
|
||||
"%D")},
|
||||
# This is just a broken date to ensure that
|
||||
# we're not just arbitrarily accepting any
|
||||
# date format.
|
||||
]}
|
||||
req_mock.return_value = requests.codes.OK, oat_data
|
||||
extra_specs = {'trust:trusted_host': 'trusted'}
|
||||
spec_obj = objects.RequestSpec(
|
||||
context=mock.sentinel.ctx,
|
||||
flavor=objects.Flavor(memory_mb=1024,
|
||||
extra_specs=extra_specs))
|
||||
host = fakes.FakeHostState('host1', 'host1', {})
|
||||
bad_host = fakes.FakeHostState('host2', 'host2', {})
|
||||
|
||||
self.assertTrue(self.filt_cls.host_passes(host, spec_obj))
|
||||
self.assertFalse(self.filt_cls.host_passes(bad_host,
|
||||
spec_obj))
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
The *TrustedFilter* along with its related ``[trusted_computing]``
|
||||
configuration options were deprecated in the 16.0.0 Pike release and have
|
||||
been removed in the 17.0.0 Queens release. The *TrustedFilter* was always
|
||||
experimental, had no continuous integration testing to prove it still
|
||||
worked, and no reported users.
|
Loading…
Reference in New Issue
Block a user