Просмотр исходного кода

Adds support for Keystone Trusts

Alessandro Pilotti 9 лет назад
Родитель
Сommit
5bc9abeeec
4 измененных файлов с 142 добавлено и 42 удалено
  1. 12 6
      coriolis/conductor/rpc/server.py
  2. 4 1
      coriolis/context.py
  3. 119 35
      coriolis/keystone.py
  4. 7 0
      etc/coriolis/coriolis.conf

+ 12 - 6
coriolis/conductor/rpc/server.py

@@ -11,6 +11,7 @@ from coriolis import constants
 from coriolis.db import api as db_api
 from coriolis.db.sqlalchemy import models
 from coriolis import exception
+from coriolis import keystone
 from coriolis import utils
 from coriolis.worker.rpc import client as rpc_worker_client
 
@@ -82,6 +83,8 @@ class ConductorServerEndpoint(object):
         return task
 
     def _begin_tasks(self, ctxt, execution, task_info={}):
+        keystone.create_trust(ctxt)
+
         for task in execution.tasks:
             if (not task.depends_on and
                     task.status == constants.TASK_STATUS_PENDING):
@@ -440,9 +443,16 @@ class ConductorServerEndpoint(object):
                         has_running_tasks = True
 
         if not has_running_tasks:
-            db_api.set_execution_status(
+            self._set_tasks_execution_status(
                 ctxt, execution.id, constants.EXECUTION_STATUS_ERROR)
 
+    @staticmethod
+    def _set_tasks_execution_status(ctxt, execution_id, execution_status):
+        LOG.info("Tasks execution %(id)s completed with status: %(status)s",
+                 {"id": execution_id, "status": execution_status})
+        db_api.set_execution_status(ctxt, execution_id, execution_status)
+        keystone.delete_trust(ctxt)
+
     @task_synchronized
     def set_task_host(self, ctxt, task_id, host, process_id):
         db_api.set_task_host(ctxt, task_id, host, process_id)
@@ -552,11 +562,7 @@ class ConductorServerEndpoint(object):
                     else:
                         execution_status = constants.EXECUTION_STATUS_COMPLETED
 
-                    LOG.info("Tasks execution %(execution_id)s completed "
-                             "with status: %(status)s",
-                             {"execution_id": execution.id,
-                              "status": execution_status})
-                    db_api.set_execution_status(
+                    self._set_tasks_execution_status(
                         ctxt, execution.id, execution_status)
 
     @task_synchronized

+ 4 - 1
coriolis/context.py

@@ -12,7 +12,8 @@ class RequestContext(context.RequestContext):
                  roles=None, project_name=None, remote_address=None,
                  timestamp=None, request_id=None, auth_token=None,
                  overwrite=True, domain=None, user_domain=None,
-                 project_domain=None, show_deleted=None, **kwargs):
+                 project_domain=None, show_deleted=None, trust_id=None,
+                 **kwargs):
 
         super(RequestContext, self).__init__(auth_token=auth_token,
                                              user=user,
@@ -32,6 +33,7 @@ class RequestContext(context.RequestContext):
         elif isinstance(timestamp, str):
             timestamp = timeutils.parse_isotime(timestamp)
         self.timestamp = timestamp
+        self.trust_id = trust_id
 
     def to_dict(self):
         result = super(RequestContext, self).to_dict()
@@ -44,6 +46,7 @@ class RequestContext(context.RequestContext):
         result['timestamp'] = self.timestamp.isoformat()
         result['request_id'] = self.request_id
         result['show_deleted'] = self.show_deleted
+        result['trust_id'] = self.trust_id
         return result
 
     @classmethod

+ 119 - 35
coriolis/keystone.py

@@ -1,9 +1,12 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 
+from keystoneauth1 import exceptions as ks_exceptions
 from keystoneauth1 import loading
-from keystoneauth1 import session
+from keystoneauth1 import session as ks_session
+from keystoneclient.v3 import client as kc_v3
 from oslo_config import cfg
+from oslo_log import log as logging
 
 from coriolis import exception
 
@@ -23,51 +26,132 @@ opts = [
 CONF = cfg.CONF
 CONF.register_opts(opts, 'keystone')
 
+LOG = logging.getLogger(__name__)
 
-def create_keystone_session(ctxt, connection_info={}):
-    keystone_version = connection_info.get(
-        "identity_api_version", CONF.keystone.identity_api_version)
-    auth_url = connection_info.get("auth_url", CONF.keystone.auth_url)
+TRUSTEE_CONF_GROUP = 'trustee'
+loading.register_auth_conf_options(CONF, TRUSTEE_CONF_GROUP, )
 
-    if not auth_url:
-        raise exception.CoriolisException(
-            '"auth_url" not provided in "connection_info" and option '
-            '"auth_url" in group "[openstack_migration_provider]" '
-            'not set')
 
-    username = connection_info.get("username")
-    password = connection_info.get("password")
-    project_name = connection_info.get("project_name", ctxt.project_name)
-    project_domain_name = connection_info.get(
-        "project_domain_name", ctxt.project_domain)
-    user_domain_name = connection_info.get(
-        "user_domain_name", ctxt.user_domain)
+def _get_trusts_auth_plugin(trust_id=None):
+    return loading.load_auth_from_conf_options(
+        CONF, TRUSTEE_CONF_GROUP, trust_id=trust_id)
+
+
+def create_trust(ctxt):
+    if ctxt.trust_id:
+        return
+
+    LOG.debug("Creating Keystone trust")
+
+    trusts_auth_plugin = _get_trusts_auth_plugin()
+
+    loader = loading.get_plugin_loader("v3token")
+    auth = loader.load_from_options(
+        auth_url=trusts_auth_plugin.auth_url,
+        token=ctxt.auth_token,
+        project_name=ctxt.project_name,
+        project_domain_name=ctxt.project_domain)
+    session = ks_session.Session(
+        auth=auth, verify=not CONF.keystone.allow_untrusted)
+
+    try:
+        trustee_user_id = trusts_auth_plugin.get_user_id(session)
+    except ks_exceptions.Unauthorized as ex:
+        LOG.exception(ex)
+        raise exception.NotAuthorized("Trustee authentication failed")
+
+    trustor_user_id = ctxt.user
+    trustor_proj_id = ctxt.tenant
+    roles = ctxt.roles
+
+    LOG.debug("Granting Keystone trust. Trustor: %(trustor_user_id)s, trustee:"
+              " %(trustee_user_id)s, project: %(trustor_proj_id)s, roles:"
+              " %(roles)s",
+              {"trustor_user_id": trustor_user_id,
+               "trustee_user_id": trustee_user_id,
+               "trustor_proj_id": trustor_proj_id,
+               "roles": roles})
+
+    # Trusts are not supported before Keystone v3
+    client = kc_v3.Client(session=session)
+    trust = client.trusts.create(trustor_user=trustor_user_id,
+                                 trustee_user=trustee_user_id,
+                                 project=trustor_proj_id,
+                                 impersonation=True,
+                                 role_names=roles)
+    LOG.debug("Trust id: %s" % trust.id)
+    ctxt.trust_id = trust.id
+
+
+def delete_trust(ctxt):
+    if ctxt.trust_id:
+        LOG.debug("Deleting trust id: %s", ctxt.trust_id)
+
+        auth = _get_trusts_auth_plugin(ctxt.trust_id)
+        session = ks_session.Session(
+            auth=auth, verify=not CONF.keystone.allow_untrusted)
+        client = kc_v3.Client(session=session)
+        try:
+            client.trusts.delete(ctxt.trust_id)
+        except ks_exceptions.NotFound:
+            LOG.debug("Trust id not found: %s", ctxt.trust_id)
+        ctxt.trust_id = None
+
+
+def create_keystone_session(ctxt, connection_info={}):
     allow_untrusted = connection_info.get(
         "allow_untrusted", CONF.keystone.allow_untrusted)
-
     # TODO: add "ca_cert" to connection_info
     verify = not allow_untrusted
 
-    plugin_args = {
-        "auth_url": auth_url,
-        "project_name": project_name,
-    }
+    username = connection_info.get("username")
 
-    if username:
-        plugin_name = "password"
-        plugin_args["username"] = username
-        plugin_args["password"] = password
+    if not username:
+        # Using directly the caller's token is not feasible for long running
+        # tasks as once it expires it cannot be automatically renewed. This is
+        # solved by using a Keystone trust, which must have been set priorly.
+        if not ctxt.trust_id:
+            raise exception.InvalidConfigurationValue(
+                "Trust id not set in context")
+
+        auth = _get_trusts_auth_plugin(ctxt.trust_id)
+        session = ks_session.Session(auth=auth, verify=verify)
     else:
-        plugin_name = "token"
-        plugin_args["token"] = ctxt.auth_token
+        password = connection_info.get("password")
+        project_name = connection_info.get("project_name", ctxt.project_name)
+
+        auth_url = connection_info.get("auth_url", CONF.keystone.auth_url)
+        if not auth_url:
+            raise exception.CoriolisException(
+                '"auth_url" not provided in "connection_info" and option '
+                '"auth_url" in group "[openstack_migration_provider]" '
+                'not set')
+
+        plugin_name = "password"
+
+        plugin_args = {
+            "auth_url": auth_url,
+            "project_name": project_name,
+            "username": username,
+            "password": password,
+        }
+
+        keystone_version = connection_info.get(
+            "identity_api_version", CONF.keystone.identity_api_version)
+
+        if keystone_version == 3:
+            plugin_name = "v3" + plugin_name
+
+            project_domain_name = connection_info.get(
+                "project_domain_name", ctxt.project_domain)
+            plugin_args["project_domain_name"] = project_domain_name
 
-    if keystone_version == 3:
-        plugin_name = "v3" + plugin_name
-        plugin_args["project_domain_name"] = project_domain_name
-        if username:
+            user_domain_name = connection_info.get(
+                "user_domain_name", ctxt.user_domain)
             plugin_args["user_domain_name"] = user_domain_name
 
-    loader = loading.get_plugin_loader(plugin_name)
-    auth = loader.load_from_options(**plugin_args)
+        loader = loading.get_plugin_loader(plugin_name)
+        auth = loader.load_from_options(**plugin_args)
+        session = ks_session.Session(auth=auth, verify=verify)
 
-    return session.Session(auth=auth, verify=verify)
+    return session

+ 7 - 0
etc/coriolis/coriolis.conf

@@ -12,6 +12,13 @@ user_domain_id = default
 project_name = service
 project_domain_id = default
 
+[trustee]
+auth_type = password
+auth_url = http://10.89.13.195:35357/v3
+username = coriolis
+user_domain_id = default
+password = mysecret
+
 [keystone]
 auth_url = http://127.0.0.1:5000/v2.0