Fix CVE-2016-7404

This commit addresses multiple potential vulnerabilities in
Magnum. It makes the following changes:

* Permissions for /etc/sysconfig/heat-params inside Magnum
  created instances are tightened to 0600 (used to be 0755).
* Certificate retrieval is modified to work without the need
  for a Keystone trust.
* The cluster's Keystone trust id is only passed into
  instances for clusters where that is actually needed. This
  prevents the trustee user from consuming the trust in cases
  where it is not needed.
* The configuration setting trust/cluster_user_trust (False by
  default) is introduced. It needs to be explicitely enabled
  by the cloud operator to allow clusters that need the
  trust_id to be passed into instances to work. Without this
  setting, attempts to create such clusters will fail.

Please note, that none of these changes apply to existing
clusters. They will have to be deleted and rebuilt to benefit
from these changes.

(cherry picked from commit e93d82e8b3)

Change-Id: I643d408cde0d6e30812cf6429fb7118184793400
This commit is contained in:
Johannes Grassler 2016-09-16 10:01:07 +02:00
parent 6c9ef67682
commit 0c7625ff4b
27 changed files with 171 additions and 75 deletions

View File

@ -204,6 +204,7 @@ function create_magnum_conf {
--os-identity-api-version 3 role add \
--user $trustee_domain_admin_id --domain $trustee_domain_id \
admin
iniset $MAGNUM_CONF trust cluster_user_trust True
iniset $MAGNUM_CONF trust trustee_domain_name magnum
iniset $MAGNUM_CONF trust trustee_domain_admin_name trustee_domain_admin
iniset $MAGNUM_CONF trust trustee_domain_admin_password $MAGNUM_TRUSTEE_DOMAIN_ADMIN_PASSWORD

View File

@ -4,35 +4,37 @@
"default": "rule:admin_or_owner",
"admin_api": "rule:context_is_admin",
"admin_or_user": "is_admin:True or user_id:%(user_id)s",
"cluster_user": "user_id:%(trustee_user_id)s",
"deny_cluster_user": "not domain_id:%(trustee_domain_id)s",
"bay:create": "rule:default",
"bay:delete": "rule:default",
"bay:detail": "rule:default",
"bay:get": "rule:default",
"bay:get_all": "rule:default",
"bay:update": "rule:default",
"bay:create": "rule:deny_cluster_user",
"bay:delete": "rule:deny_cluster_user",
"bay:detail": "rule:deny_cluster_user",
"bay:get": "rule:deny_cluster_user",
"bay:get_all": "rule:deny_cluster_user",
"bay:update": "rule:deny_cluster_user",
"baymodel:create": "rule:default",
"baymodel:delete": "rule:default",
"baymodel:detail": "rule:default",
"baymodel:get": "rule:default",
"baymodel:get_all": "rule:default",
"baymodel:update": "rule:default",
"baymodel:create": "rule:deny_cluster_user",
"baymodel:delete": "rule:deny_cluster_user",
"baymodel:detail": "rule:deny_cluster_user",
"baymodel:get": "rule:deny_cluster_user",
"baymodel:get_all": "rule:deny_cluster_user",
"baymodel:update": "rule:deny_cluster_user",
"baymodel:publish": "rule:admin_or_owner",
"cluster:create": "rule:default",
"cluster:delete": "rule:default",
"cluster:detail": "rule:default",
"cluster:get": "rule:default",
"cluster:get_all": "rule:default",
"cluster:update": "rule:default",
"cluster:create": "rule:deny_cluster_user",
"cluster:delete": "rule:deny_cluster_user",
"cluster:detail": "rule:deny_cluster_user",
"cluster:get": "rule:deny_cluster_user",
"cluster:get_all": "rule:deny_cluster_user",
"cluster:update": "rule:deny_cluster_user",
"clustertemplate:create": "rule:default",
"clustertemplate:delete": "rule:default",
"clustertemplate:detail": "rule:default",
"clustertemplate:get": "rule:default",
"clustertemplate:get_all": "rule:default",
"clustertemplate:update": "rule:default",
"clustertemplate:create": "rule:deny_cluster_user",
"clustertemplate:delete": "rule:deny_cluster_user",
"clustertemplate:detail": "rule:deny_cluster_user",
"clustertemplate:get": "rule:deny_cluster_user",
"clustertemplate:get_all": "rule:deny_cluster_user",
"clustertemplate:update": "rule:deny_cluster_user",
"clustertemplate:publish": "rule:admin_or_owner",
"quotas:get": "rule:default",
@ -41,9 +43,9 @@
"quotas:update": "rule:admin_api",
"quotas:delete": "rule:admin_api",
"certificate:create": "rule:admin_or_user",
"certificate:get": "rule:admin_or_user",
"certificate:rotate_ca": "rule:admin_or_owner",
"certificate:create": "rule:admin_or_user or rule:cluster_user",
"certificate:get": "rule:admin_or_user or rule:cluster_user",
"magnum-service:get_all": "rule:admin_api",
"stats:get_all": "rule:admin_or_owner"

View File

@ -204,6 +204,7 @@ class KeystoneClientV3(object):
project=trustor_project_id,
trustee_user=trustee_user,
impersonation=True,
delegation_depth=0,
role_names=roles)
except Exception:
LOG.exception(_LE('Failed to create trust'))

View File

@ -20,6 +20,8 @@ from oslo_config import cfg
from oslo_policy import policy
import pecan
from magnum.common import clients
from magnum.common import context
from magnum.common import exception
@ -92,10 +94,20 @@ def enforce(context, rule=None, target=None,
if target is None:
target = {'project_id': context.project_id,
'user_id': context.user_id}
add_policy_attributes(target)
return enforcer.enforce(rule, target, credentials,
do_raise=do_raise, exc=exc, *args, **kwargs)
def add_policy_attributes(target):
"""Adds extra information for policy enforcement to raw target object"""
admin_context = context.make_admin_context()
admin_osc = clients.OpenStackClients(admin_context)
trustee_domain_id = admin_osc.keystone().trustee_domain_id
target['trustee_domain_id'] = trustee_domain_id
return target
def enforce_wsgi(api_name, act=None):
"""This is a decorator to simplify wsgi action policy rule check.

View File

@ -22,15 +22,20 @@ LOG = logging.getLogger(__name__)
def create_trustee_and_trust(osc, cluster):
try:
password = utils.generate_password(length=18)
trustee = osc.keystone().create_trustee(
cluster.uuid,
"%s_%s" % (cluster.uuid, cluster.project_id),
password,
)
cluster.trustee_username = trustee.name
cluster.trustee_user_id = trustee.id
cluster.trustee_password = password
trust = osc.keystone().create_trust(trustee.id)
trust = osc.keystone().create_trust(
cluster.trustee_user_id)
cluster.trust_id = trust.id
except Exception:
LOG.exception(
_LE('Failed to create trustee and trust for Cluster: %s'),
@ -41,9 +46,11 @@ def create_trustee_and_trust(osc, cluster):
def delete_trustee_and_trust(osc, context, cluster):
try:
kst = osc.keystone()
# The cluster which is upgraded from Liberty doesn't have trust_id
if cluster.trust_id:
osc.keystone().delete_trust(context, cluster)
kst.delete_trust(context, cluster)
except Exception:
# Exceptions are already logged by keystone().delete_trust
pass

View File

@ -18,6 +18,17 @@ trust_group = cfg.OptGroup(name='trust',
title='Trustee options for the magnum services')
trust_opts = [
cfg.BoolOpt('cluster_user_trust',
default=False,
help=_('This setting controls whether to assign a trust to'
' the cluster user or not. You will need to set it to'
' True for clusters with volume_driver=cinder or'
' registry_enabled=true in the underlying cluster'
' template to work. This is a potential security risk'
' since the trust gives instances OpenStack API access'
" to the cluster's project. Note that this setting"
' does not affect per-cluster trusts assigned to the'
'Magnum service user.')),
cfg.StrOpt('trustee_domain_id',
help=_('Id of the domain to create trustee for clusters')),
cfg.StrOpt('trustee_domain_name',

View File

@ -26,6 +26,8 @@ from sqlalchemy.orm.exc import MultipleResultsFound
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.sql import func
from magnum.common import clients
from magnum.common import context as request_context
from magnum.common import exception
import magnum.conf
from magnum.db import api
@ -122,8 +124,21 @@ class Connection(api.Connection):
if context.is_admin and context.all_tenants:
return query
if context.project_id:
admin_context = request_context.make_admin_context(all_tenants=True)
osc = clients.OpenStackClients(admin_context)
kst = osc.keystone()
# User in a regular project (not in the trustee domain)
if context.project_id and context.domain_id != kst.trustee_domain_id:
query = query.filter_by(project_id=context.project_id)
# Match project ID component in trustee user's user name against
# cluster's project_id to associate per-cluster trustee users who have
# no project information with the project their clusters/cluster models
# reside in. This is equivalent to the project filtering above.
elif context.domain_id == kst.trustee_domain_id:
user_name = kst.client.users.get(context.user_id).name
user_project = user_name.split('_', 2)[1]
query = query.filter_by(project_id=user_project)
else:
query = query.filter_by(user_id=context.user_id)

View File

@ -49,11 +49,6 @@ auth_json=$(cat << EOF
"password": "$TRUSTEE_PASSWORD"
}
}
},
"scope": {
"OS-TRUST:trust": {
"id": "$TRUST_ID"
}
}
}
}

View File

@ -71,11 +71,6 @@ auth_json=$(cat << EOF
"password": "$TRUSTEE_PASSWORD"
}
}
},
"scope": {
"OS-TRUST:trust": {
"id": "$TRUST_ID"
}
}
}
}

View File

@ -3,7 +3,7 @@ merge_how: dict(recurse_array)+list(append)
write_files:
- path: /etc/sysconfig/heat-params
owner: "root:root"
permissions: "0644"
permissions: "0600"
content: |
KUBE_API_PUBLIC_ADDRESS="$KUBE_API_PUBLIC_ADDRESS"
KUBE_API_PRIVATE_ADDRESS="$KUBE_API_PRIVATE_ADDRESS"

View File

@ -3,7 +3,7 @@ merge_how: dict(recurse_array)+list(append)
write_files:
- path: /etc/sysconfig/heat-params
owner: "root:root"
permissions: "0644"
permissions: "0600"
content: |
KUBE_ALLOW_PRIV="$KUBE_ALLOW_PRIV"
KUBE_MASTER_IP="$KUBE_MASTER_IP"

View File

@ -150,11 +150,6 @@ def get_user_token(config):
"password": "%(trustee_password)s"
}
}
},
"scope": {
"OS-TRUST:trust": {
"id": "%(trust_id)s"
}
}
}
}
@ -162,7 +157,6 @@ def get_user_token(config):
params = {
'trustee_user_id': config['TRUSTEE_USER_ID'],
'trustee_password': config['TRUSTEE_PASSWORD'],
'trust_id': config['TRUST_ID']
}
creds = creds_str % params
headers = {'Content-Type': 'application/json'}

View File

@ -3,7 +3,7 @@ merge_how: dict(recurse_array)+list(append)
write_files:
- path: /etc/sysconfig/heat-params
owner: "root:root"
permissions: "0644"
permissions: "0600"
content: |
WAIT_HANDLE_ENDPOINT="$WAIT_HANDLE_ENDPOINT"
WAIT_HANDLE_TOKEN="$WAIT_HANDLE_TOKEN"

View File

@ -3,7 +3,7 @@ merge_how: dict(recurse_array)+list(append)
write_files:
- path: /etc/sysconfig/heat-params
owner: "root:root"
permissions: "0644"
permissions: "0600"
content: |
WAIT_HANDLE_ENDPOINT="$WAIT_HANDLE_ENDPOINT"
WAIT_HANDLE_TOKEN="$WAIT_HANDLE_TOKEN"

View File

@ -21,6 +21,7 @@ import six
from magnum.common import clients
from magnum.common import exception
import magnum.conf
from magnum.i18n import _LE
from magnum.i18n import _LW
from requests import exceptions as req_exceptions
@ -245,7 +246,20 @@ class BaseTemplateDefinition(TemplateDefinition):
extra_params['trustee_user_id'] = cluster.trustee_user_id
extra_params['trustee_username'] = cluster.trustee_username
extra_params['trustee_password'] = cluster.trustee_password
extra_params['trust_id'] = cluster.trust_id
# Only pass trust ID into the template when it is needed.
if (cluster_template.volume_driver == 'rexray' or
cluster_template.registry_enabled):
if CONF.trust.cluster_user_trust:
extra_params['trust_id'] = cluster.trust_id
else:
missing_setting = ('trust/cluster_user_trust = True')
msg = _LE('This cluster can only be created with %s in '
'magnum.conf')
raise exception.ConfigInvalid(msg % missing_setting)
else:
extra_params['trust_id'] = ""
extra_params['auth_url'] = context.auth_url
return super(BaseTemplateDefinition,

View File

@ -63,11 +63,6 @@ write_files:
"password": "$TRUSTEE_PASSWORD"
}
}
},
"scope": {
"OS-TRUST:trust": {
"id": "$TRUST_ID"
}
}
}
}

View File

@ -86,11 +86,6 @@ write_files:
"password": "$TRUSTEE_PASSWORD"
}
}
},
"scope": {
"OS-TRUST:trust": {
"id": "$TRUST_ID"
}
}
}
}

View File

@ -3,7 +3,7 @@ merge_how: dict(recurse_array)+list(append)
write_files:
- path: /etc/sysconfig/heat-params
owner: "root:root"
permissions: "0644"
permissions: "0600"
content: |
KUBE_API_PUBLIC_ADDRESS="$KUBE_API_PUBLIC_ADDRESS"
KUBE_API_PRIVATE_ADDRESS="$KUBE_API_PRIVATE_ADDRESS"

View File

@ -3,7 +3,7 @@ merge_how: dict(recurse_array)+list(append)
write_files:
- path: /etc/sysconfig/heat-params
owner: "root:root"
permissions: "0644"
permissions: "0600"
content: |
KUBE_ALLOW_PRIV="$KUBE_ALLOW_PRIV"
KUBE_MASTER_IP="$KUBE_MASTER_IP"

View File

@ -3,7 +3,7 @@ merge_how: dict(recurse_array)+list(append)
write_files:
- path: /etc/sysconfig/heat-params
owner: "root:root"
permissions: "0644"
permissions: "0600"
content: |
MESOS_MASTERS_IPS="$MESOS_MASTERS_IPS"
EXECUTOR_REGISTRATION_TIMEOUT="$EXECUTOR_REGISTRATION_TIMEOUT"

View File

@ -26,6 +26,7 @@ import pecan
import testscenarios
from magnum.common import context as magnum_context
from magnum.common import keystone as magnum_keystone
from magnum.objects import base as objects_base
from magnum.tests import conf_fixture
from magnum.tests import fake_notifier
@ -63,11 +64,18 @@ class TestCase(base.BaseTestCase):
}
}
}
trustee_domain_id = '12345678-9012-3456-7890-123456789abc'
self.context = magnum_context.RequestContext(
auth_token_info=token_info,
project_id='fake_project',
user_id='fake_user')
self.global_mocks = {}
self.keystone_client = magnum_keystone.KeystoneClientV3(self.context)
self.policy = self.useFixture(policy_fixture.PolicyFixture())
self.useFixture(fixtures.MockPatchObject(
@ -89,9 +97,22 @@ class TestCase(base.BaseTestCase):
p = mock.patch.object(magnum_context, 'make_context',
side_effect=make_context)
self.global_mocks['magnum.common.context.make_context'] = p
q = mock.patch.object(magnum_keystone.KeystoneClientV3,
'trustee_domain_id',
return_value=trustee_domain_id)
self.global_mocks[
'magnum.common.keystone.KeystoneClientV3.trustee_domain_id'] = q
self.mock_make_context = p.start()
self.addCleanup(p.stop)
self.mock_make_trustee_domain_id = q.start()
self.addCleanup(q.stop)
self.useFixture(conf_fixture.ConfFixture())
self.useFixture(fixtures.NestedTempfile())
@ -104,6 +125,12 @@ class TestCase(base.BaseTestCase):
self.addCleanup(reset_pecan)
def start_global(self, name):
self.global_mocks[name].start()
def stop_global(self, name):
self.global_mocks[name].stop()
def _restore_obj_registry(self):
objects_base.MagnumObjectRegistry._registry._obj_classes \
= self._base_test_obj_backup

View File

@ -55,6 +55,19 @@ class KeystoneClientTest(base.TestCase):
admin_tenant_name='service',
group=ksconf.CFG_LEGACY_GROUP)
# Disable global mocking for trustee_domain_id
self.stop_global(
'magnum.common.keystone.KeystoneClientV3.trustee_domain_id')
def tearDown(self):
# Re-enable global mocking for trustee_domain_id. We need this because
# mock blows up when trying to stop an already stopped patch (which it
# will do due to the addCleanup() in base.TestCase).
self.start_global(
'magnum.common.keystone.KeystoneClientV3.trustee_domain_id')
super(KeystoneClientTest, self).tearDown()
def test_client_with_password(self, mock_ks):
self.ctx.is_admin = True
ks_client = keystone.KeystoneClientV3(self.ctx)
@ -136,6 +149,7 @@ class KeystoneClientTest(base.TestCase):
ks_client.create_trust(trustee_user='888888')
mock_ks.return_value.trusts.create.assert_called_once_with(
delegation_depth=0,
trustor_user='123456', project='654321',
trustee_user='888888', role_names=['role1', 'role2'],
impersonation=True)
@ -152,6 +166,7 @@ class KeystoneClientTest(base.TestCase):
ks_client.create_trust(trustee_user='888888')
mock_ks.return_value.trusts.create.assert_called_once_with(
delegation_depth=0,
trustor_user='123456', project='654321',
trustee_user='888888', role_names=['role3'],
impersonation=True)

View File

@ -37,6 +37,7 @@ class TrustManagerTestCase(base.BaseTestCase):
mock_generate_password.return_value = mock_password
mock_cluster = mock.MagicMock()
mock_cluster.uuid = 'mock_cluster_uuid'
mock_cluster.project_id = 'mock_cluster_project_id'
mock_keystone = mock.MagicMock()
mock_trustee = mock.MagicMock()
mock_trustee.id = 'mock_trustee_id'
@ -52,7 +53,7 @@ class TrustManagerTestCase(base.BaseTestCase):
trust_manager.create_trustee_and_trust(self.osc, mock_cluster)
mock_keystone.create_trustee.assert_called_once_with(
mock_cluster.uuid,
'%s_%s' % (mock_cluster.uuid, mock_cluster.project_id),
mock_password,
)
mock_keystone.create_trust.assert_called_once_with(

View File

@ -181,6 +181,11 @@ class TestHandler(db_base.DbTestCase):
mock_poller.poll_and_check.return_value = loopingcall.LoopingCallDone()
mock_heat_poller_class.return_value = mock_poller
osc = mock.sentinel.osc
def return_keystone():
return self.keystone_client
osc.keystone = return_keystone
mock_openstack_client_class.return_value = osc
mock_dr = mock.MagicMock()
mock_driver.return_value = mock_dr

View File

@ -194,7 +194,7 @@ class TestClusterConductorWithK8s(base.TestCase):
'trustee_username': 'fake_trustee',
'trustee_password': 'fake_trustee_password',
'trustee_user_id': '7b489f04-b458-4541-8179-6a48a553e656',
'trust_id': 'bd11efc5-d4e2-4dac-bbce-25e348ddf7de',
'trust_id': '',
'auth_url': 'http://192.168.10.10:5000/v3',
'insecure_registry_url': '10.0.0.1:5000',
'kube_version': 'fake-version',
@ -236,6 +236,10 @@ class TestClusterConductorWithK8s(base.TestCase):
'RegionOne',
group='docker_registry')
CONF.set_override('cluster_user_trust',
True,
group='trust')
(template_path,
definition,
env_files) = mock_driver()._extract_template_definition(self.context,
@ -350,7 +354,7 @@ class TestClusterConductorWithK8s(base.TestCase):
'ssh_key_name': 'keypair_id',
'tenant_name': 'fake_tenant',
'tls_disabled': False,
'trust_id': 'bd11efc5-d4e2-4dac-bbce-25e348ddf7de',
'trust_id': '',
'trustee_domain_id': 'trustee_domain_id',
'trustee_password': 'fake_trustee_password',
'trustee_user_id': '7b489f04-b458-4541-8179-6a48a553e656',
@ -421,7 +425,7 @@ class TestClusterConductorWithK8s(base.TestCase):
'trustee_username': 'fake_trustee',
'trustee_password': 'fake_trustee_password',
'trustee_user_id': '7b489f04-b458-4541-8179-6a48a553e656',
'trust_id': 'bd11efc5-d4e2-4dac-bbce-25e348ddf7de',
'trust_id': '',
'auth_url': 'http://192.168.10.10:5000/v3',
'cluster_uuid': self.cluster_dict['uuid'],
'magnum_url': self.mock_osc.magnum_url.return_value,
@ -488,7 +492,7 @@ class TestClusterConductorWithK8s(base.TestCase):
'trustee_username': 'fake_trustee',
'trustee_password': 'fake_trustee_password',
'trustee_user_id': '7b489f04-b458-4541-8179-6a48a553e656',
'trust_id': 'bd11efc5-d4e2-4dac-bbce-25e348ddf7de',
'trust_id': '',
'auth_url': 'http://192.168.10.10:5000/v3',
'cluster_uuid': self.cluster_dict['uuid'],
'magnum_url': self.mock_osc.magnum_url.return_value,
@ -686,7 +690,7 @@ class TestClusterConductorWithK8s(base.TestCase):
'trustee_username': 'fake_trustee',
'trustee_password': 'fake_trustee_password',
'trustee_user_id': '7b489f04-b458-4541-8179-6a48a553e656',
'trust_id': 'bd11efc5-d4e2-4dac-bbce-25e348ddf7de',
'trust_id': '',
'auth_url': 'http://192.168.10.10:5000/v3',
'insecure_registry_url': '10.0.0.1:5000',
'kube_version': 'fake-version',

View File

@ -37,6 +37,7 @@ class TestClusterConductorWithMesos(base.TestCase):
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy',
'registry_enabled': False,
'server_type': 'vm',
'volume_driver': 'volume_driver',
'labels': {'rexray_preempt': 'False',
@ -117,7 +118,7 @@ class TestClusterConductorWithMesos(base.TestCase):
'trustee_username': 'fake_trustee',
'trustee_password': 'fake_trustee_password',
'trustee_user_id': '7b489f04-b458-4541-8179-6a48a553e656',
'trust_id': 'bd11efc5-d4e2-4dac-bbce-25e348ddf7de',
'trust_id': '',
'volume_driver': 'volume_driver',
'auth_url': 'http://192.168.10.10:5000/v3',
'region_name': self.mock_osc.cinder_region_name.return_value,
@ -171,7 +172,7 @@ class TestClusterConductorWithMesos(base.TestCase):
'trustee_username': 'fake_trustee',
'trustee_password': 'fake_trustee_password',
'trustee_user_id': '7b489f04-b458-4541-8179-6a48a553e656',
'trust_id': 'bd11efc5-d4e2-4dac-bbce-25e348ddf7de',
'trust_id': '',
'auth_url': 'http://192.168.10.10:5000/v3',
'region_name': self.mock_osc.cinder_region_name.return_value,
'username': 'mesos_user',
@ -227,7 +228,7 @@ class TestClusterConductorWithMesos(base.TestCase):
'trustee_username': 'fake_trustee',
'trustee_password': 'fake_trustee_password',
'trustee_user_id': '7b489f04-b458-4541-8179-6a48a553e656',
'trust_id': 'bd11efc5-d4e2-4dac-bbce-25e348ddf7de',
'trust_id': '',
'volume_driver': 'volume_driver',
'auth_url': 'http://192.168.10.10:5000/v3',
'region_name': self.mock_osc.cinder_region_name.return_value,
@ -285,7 +286,7 @@ class TestClusterConductorWithMesos(base.TestCase):
'trustee_username': 'fake_trustee',
'trustee_password': 'fake_trustee_password',
'trustee_user_id': '7b489f04-b458-4541-8179-6a48a553e656',
'trust_id': 'bd11efc5-d4e2-4dac-bbce-25e348ddf7de',
'trust_id': '',
'volume_driver': 'volume_driver',
'auth_url': 'http://192.168.10.10:5000/v3',
'region_name': self.mock_osc.cinder_region_name.return_value,

View File

@ -74,6 +74,12 @@ class TestClusterConductorWithSwarm(base.TestCase):
'trust_id': 'bd11efc5-d4e2-4dac-bbce-25e348ddf7de',
'coe_version': 'fake-version'
}
# We need this due to volume_driver=rexray
CONF.set_override('cluster_user_trust',
True,
group='trust')
osc_patcher = mock.patch('magnum.common.clients.OpenStackClients')
self.mock_osc_class = osc_patcher.start()
self.addCleanup(osc_patcher.stop)
@ -280,7 +286,7 @@ class TestClusterConductorWithSwarm(base.TestCase):
'trustee_username': 'fake_trustee',
'trustee_password': 'fake_trustee_password',
'trustee_user_id': '7b489f04-b458-4541-8179-6a48a553e656',
'trust_id': 'bd11efc5-d4e2-4dac-bbce-25e348ddf7de',
'trust_id': '',
'auth_url': 'http://192.168.10.10:5000/v3',
'swarm_version': 'fake-version',
'swarm_strategy': u'spread',