~ubuntu-branches/ubuntu/wily/heat/wily

« back to all changes in this revision

Viewing changes to contrib/rackspace/heat_keystoneclient_v2/client.py

  • Committer: Package Import Robot
  • Author(s): Corey Bryant, Corey Bryant, James Page
  • Date: 2015-07-07 17:06:19 UTC
  • mfrom: (1.1.26) (45.1.1 vivid-proposed)
  • Revision ID: package-import@ubuntu.com-20150707170619-hra2dbjpfofpou4s
Tags: 1:5.0.0~b1-0ubuntu1
[ Corey Bryant ]
* New upstream milestone for OpenStack Liberty:
  - d/control: Align (build-)depends with upstream.
  - d/p/fix-requirements.patch: Rebased.
  - d/p/sudoers_patch.patch: Rebased.

[ James Page ]
* d/s/options: Ignore any removal of egg-info data during package clean.
* d/control: Drop MySQL and PostgreSQL related BD's, not required for unit
  testing.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 
3
#    not use this file except in compliance with the License. You may obtain
 
4
#    a copy of the License at
 
5
#
 
6
#         http://www.apache.org/licenses/LICENSE-2.0
 
7
#
 
8
#    Unless required by applicable law or agreed to in writing, software
 
9
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
10
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
11
#    License for the specific language governing permissions and limitations
 
12
#    under the License.
 
13
 
 
14
"""Client Library for Keystone Resources."""
 
15
 
 
16
from keystoneclient.v2_0 import client as kc
 
17
from oslo_config import cfg
 
18
from oslo_log import log as logging
 
19
from oslo_utils import importutils
 
20
 
 
21
from heat.common import exception
 
22
from heat.common.i18n import _LE
 
23
from heat.common.i18n import _LI
 
24
from heat.common.i18n import _LW
 
25
 
 
26
LOG = logging.getLogger('heat.common.keystoneclient')
 
27
LOG.info(_LI("Keystone V2 loaded"))
 
28
 
 
29
 
 
30
class KeystoneClientV2(object):
 
31
 
 
32
    """Wrap keystone client so we can encapsulate logic used in resources.
 
33
 
 
34
    Note: This is intended to be initialized from a resource on a per-session
 
35
    basis, so the session context is passed in on initialization
 
36
    Also note that a copy of this is created every resource as self.keystone()
 
37
    via the code in engine/client.py, so there should not be any need to
 
38
    directly instantiate instances of this class inside resources themselves.
 
39
    """
 
40
    def __init__(self, context):
 
41
        # If a trust_id is specified in the context, we immediately
 
42
        # authenticate so we can populate the context with a trust token
 
43
        # otherwise, we delay client authentication until needed to avoid
 
44
        # unnecessary calls to keystone.
 
45
        #
 
46
        # Note that when you obtain a token using a trust, it cannot be
 
47
        # used to reauthenticate and get another token, so we have to
 
48
        # get a new trust-token even if context.auth_token is set.
 
49
        #
 
50
        # - context.auth_url is expected to contain the v2.0 keystone endpoint
 
51
        self.context = context
 
52
        self._client = None
 
53
 
 
54
        if self.context.trust_id:
 
55
            # Create a connection to the v2 API, with the trust_id, this
 
56
            # populates self.context.auth_token with a trust-scoped token
 
57
            self._client = self._v2_client_init()
 
58
 
 
59
    @property
 
60
    def client(self):
 
61
        if not self._client:
 
62
            self._client = self._v2_client_init()
 
63
        return self._client
 
64
 
 
65
    def _v2_client_init(self):
 
66
        kwargs = {
 
67
            'auth_url': self.context.auth_url,
 
68
            'endpoint': self.context.auth_url,
 
69
            'region_name': cfg.CONF.region_name_for_services
 
70
        }
 
71
 
 
72
        if self.context.region_name is not None:
 
73
            kwargs['region_name'] = self.context.region_name
 
74
 
 
75
        auth_kwargs = {}
 
76
        # Note try trust_id first, as we can't reuse auth_token in that case
 
77
        if self.context.trust_id is not None:
 
78
            # We got a trust_id, so we use the admin credentials
 
79
            # to authenticate, then re-scope the token to the
 
80
            # trust impersonating the trustor user.
 
81
            # Note that this currently requires the trustor tenant_id
 
82
            # to be passed to the authenticate(), unlike the v3 call
 
83
            kwargs.update(self._service_admin_creds())
 
84
            auth_kwargs['trust_id'] = self.context.trust_id
 
85
            auth_kwargs['tenant_id'] = self.context.tenant_id
 
86
        elif self.context.auth_token is not None:
 
87
            kwargs['tenant_name'] = self.context.tenant
 
88
            kwargs['token'] = self.context.auth_token
 
89
        elif self.context.password is not None:
 
90
            kwargs['username'] = self.context.username
 
91
            kwargs['password'] = self.context.password
 
92
            kwargs['tenant_name'] = self.context.tenant
 
93
            kwargs['tenant_id'] = self.context.tenant_id
 
94
        else:
 
95
            LOG.error(_LE("Keystone v2 API connection failed, no password "
 
96
                          "or auth_token!"))
 
97
            raise exception.AuthorizationFailure()
 
98
        kwargs['cacert'] = self._get_client_option('ca_file')
 
99
        kwargs['insecure'] = self._get_client_option('insecure')
 
100
        kwargs['cert'] = self._get_client_option('cert_file')
 
101
        kwargs['key'] = self._get_client_option('key_file')
 
102
        client = kc.Client(**kwargs)
 
103
 
 
104
        client.authenticate(**auth_kwargs)
 
105
        # If we are authenticating with a trust auth_kwargs are set, so set
 
106
        # the context auth_token with the re-scoped trust token
 
107
        if auth_kwargs:
 
108
            # Sanity check
 
109
            if not client.auth_ref.trust_scoped:
 
110
                LOG.error(_LE("v2 trust token re-scoping failed!"))
 
111
                raise exception.AuthorizationFailure()
 
112
            # All OK so update the context with the token
 
113
            self.context.auth_token = client.auth_ref.auth_token
 
114
            self.context.auth_url = kwargs.get('auth_url')
 
115
            # Ensure the v2 API we're using is not impacted by keystone
 
116
            # bug #1239303, otherwise we can't trust the user_id
 
117
            if self.context.trustor_user_id != client.auth_ref.user_id:
 
118
                LOG.error(_LE("Trust impersonation failed, bug #1239303 "
 
119
                              "suspected, you may need a newer keystone"))
 
120
                raise exception.AuthorizationFailure()
 
121
 
 
122
        return client
 
123
 
 
124
    @staticmethod
 
125
    def _service_admin_creds():
 
126
        # Import auth_token to have keystone_authtoken settings setup.
 
127
        importutils.import_module('keystonemiddleware.auth_token')
 
128
 
 
129
        creds = {
 
130
            'username': cfg.CONF.keystone_authtoken.admin_user,
 
131
            'password': cfg.CONF.keystone_authtoken.admin_password,
 
132
            'auth_url': cfg.CONF.keystone_authtoken.auth_uri,
 
133
            'tenant_name': cfg.CONF.keystone_authtoken.admin_tenant_name,
 
134
        }
 
135
 
 
136
        return creds
 
137
 
 
138
    def _get_client_option(self, option):
 
139
        # look for the option in the [clients_keystone] section
 
140
        # unknown options raise cfg.NoSuchOptError
 
141
        cfg.CONF.import_opt(option, 'heat.common.config',
 
142
                            group='clients_keystone')
 
143
        v = getattr(cfg.CONF.clients_keystone, option)
 
144
        if v is not None:
 
145
            return v
 
146
        # look for the option in the generic [clients] section
 
147
        cfg.CONF.import_opt(option, 'heat.common.config', group='clients')
 
148
        return getattr(cfg.CONF.clients, option)
 
149
 
 
150
    def create_stack_user(self, username, password=''):
 
151
        """
 
152
        Create a user defined as part of a stack, either via template
 
153
        or created internally by a resource.  This user will be added to
 
154
        the heat_stack_user_role as defined in the config
 
155
        Returns the keystone ID of the resulting user
 
156
        """
 
157
        if(len(username) > 64):
 
158
            LOG.warn(_LW("Truncating the username %s to the last 64 "
 
159
                         "characters."), username)
 
160
            # get the last 64 characters of the username
 
161
            username = username[-64:]
 
162
        user = self.client.users.create(username,
 
163
                                        password,
 
164
                                        '%s@openstack.org' % username,
 
165
                                        tenant_id=self.context.tenant_id,
 
166
                                        enabled=True)
 
167
 
 
168
        # We add the new user to a special keystone role
 
169
        # This role is designed to allow easier differentiation of the
 
170
        # heat-generated "stack users" which will generally have credentials
 
171
        # deployed on an instance (hence are implicitly untrusted)
 
172
        roles = self.client.roles.list()
 
173
        stack_user_role = [r.id for r in roles
 
174
                           if r.name == cfg.CONF.heat_stack_user_role]
 
175
        if len(stack_user_role) == 1:
 
176
            role_id = stack_user_role[0]
 
177
            LOG.debug("Adding user %(user)s to role %(role)s"
 
178
                      % {'user': user.id, 'role': role_id})
 
179
            self.client.roles.add_user_role(user.id, role_id,
 
180
                                            self.context.tenant_id)
 
181
        else:
 
182
            LOG.error(_LE("Failed to add user %(user)s to role %(role)s, "
 
183
                          "check role exists!"),
 
184
                      {'user': username,
 
185
                       'role': cfg.CONF.heat_stack_user_role})
 
186
 
 
187
        return user.id
 
188
 
 
189
    def delete_stack_user(self, user_id):
 
190
        self.client.users.delete(user_id)
 
191
 
 
192
    def delete_ec2_keypair(self, user_id, accesskey):
 
193
        self.client.ec2.delete(user_id, accesskey)
 
194
 
 
195
    def get_ec2_keypair(self, access, user_id=None):
 
196
        uid = user_id or self.client.auth_ref.user_id
 
197
        return self.client.ec2.get(uid, access)
 
198
 
 
199
    def create_ec2_keypair(self, user_id=None):
 
200
        uid = user_id or self.client.auth_ref.user_id
 
201
        return self.client.ec2.create(uid, self.context.tenant_id)
 
202
 
 
203
    def disable_stack_user(self, user_id):
 
204
        self.client.users.update_enabled(user_id, False)
 
205
 
 
206
    def enable_stack_user(self, user_id):
 
207
        self.client.users.update_enabled(user_id, True)
 
208
 
 
209
    def url_for(self, **kwargs):
 
210
        return self.client.service_catalog.url_for(**kwargs)
 
211
 
 
212
    @property
 
213
    def auth_token(self):
 
214
        return self.client.auth_token
 
215
 
 
216
    # ##################### #
 
217
    # V3 Compatible Methods #
 
218
    # ##################### #
 
219
 
 
220
    def create_stack_domain_user(self, username, project_id, password=None):
 
221
        return self.create_stack_user(username, password)
 
222
 
 
223
    def delete_stack_domain_user(self, user_id, project_id):
 
224
        return self.delete_stack_user(user_id)
 
225
 
 
226
    def create_stack_domain_project(self, project_id):
 
227
        '''Use the tenant ID as domain project.'''
 
228
        return self.context.tenant_id
 
229
 
 
230
    def delete_stack_domain_project(self, project_id):
 
231
        '''Pass through method since no project was created.'''
 
232
        pass
 
233
 
 
234
    def create_stack_domain_user_keypair(self, user_id, project_id):
 
235
        return self.create_ec2_keypair(user_id)
 
236
 
 
237
    def delete_stack_domain_user_keypair(self, user_id, project_id,
 
238
                                         credential_id):
 
239
        return self.delete_ec2_keypair(user_id, credential_id)
 
240
 
 
241
    # ###################### #
 
242
    # V3 Unsupported Methods #
 
243
    # ###################### #
 
244
 
 
245
    def create_trust_context(self):
 
246
        raise exception.NotSupported(feature='Keystone Trusts')
 
247
 
 
248
    def delete_trust(self, trust_id):
 
249
        raise exception.NotSupported(feature='Keystone Trusts')