~smoser/simplestreams/trunk.openstack-v3-auth

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#   Copyright (C) 2013 Canonical Ltd.
#
#   Author: Scott Moser <scott.moser@canonical.com>
#
#   Simplestreams is free software: you can redistribute it and/or modify it
#   under the terms of the GNU Affero General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or (at your
#   option) any later version.
#
#   Simplestreams is distributed in the hope that it will be useful, but
#   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
#   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
#   License for more details.
#
#   You should have received a copy of the GNU Affero General Public License
#   along with Simplestreams.  If not, see <http://www.gnu.org/licenses/>.

try:
    from keystoneclient.v2_0.client import Client as ksv2_client
    HAS_KEYSTONE_V2 = True
except ImportError:
    ksclient_v2 = None
    HAS_KEYSTONE_V2 = False

try:
    from keystoneauth1.identity.v3 import Password as ksv3_password
    from keystoneauth1.session import Session as ksv3_session
    from keystoneclient.v3.client import Client as ksv3_client
    HAS_KEYSTONE_V3 = True
except ImportError:
    HAS_KEYSTONE_V3 = False

import os

OS_ENV_VARS = (
    'OS_AUTH_TOKEN', 'OS_AUTH_URL', 'OS_CACERT', 'OS_IMAGE_API_VERSION',
    'OS_IMAGE_URL', 'OS_PASSWORD', 'OS_REGION_NAME', 'OS_STORAGE_URL',
    'OS_TENANT_ID', 'OS_TENANT_NAME', 'OS_USERNAME', 'OS_INSECURE'
)


def load_keystone_creds(**kwargs):
    # either via arguments or OS_* values in environment, the kwargs
    # that are required are:
    #   'username', 'auth_url',
    #   ('auth_token' or 'password')
    #   ('tenant_id' or 'tenant_name')
    ret = {}
    for name in OS_ENV_VARS:
        lc = name.lower()
        # take off 'os_'
        short = lc[3:]
        if short in kwargs:
            ret[lc] = kwargs.get(lc)
        elif name in os.environ:
            # take off 'os_'
            ret[short] = os.environ[name]

    if 'insecure' in ret:
        if isinstance(ret['insecure'], str):
            ret['insecure'] = (ret['insecure'].lower() not in
                               ("", "0", "no", "off"))
        else:
            ret['insecure'] = bool(ret['insecure'])

    missing = []
    for req in ('username', 'auth_url'):
        if not ret.get(req):
            missing.append(req)

    if not (ret.get('auth_token') or ret.get('password')):
        missing.append("(auth_token or password)")

    if not (ret.get('tenant_id') or ret.get('tenant_name')):
        raise ValueError("(tenant_id or tenant_name)")

    if missing:
        raise ValueError("Need values for: %s" % missing)

    return ret


def load_openstack_settings(**kwargs):
    # return a dictionary of sanitized values.
    # given an environment with OS_FOO=bar, return {'foo': 'bar'}
    # kwargs other than 'env' will take precedence over the
    # values found in environment.
    if 'env' in kwargs:
        env = kwargs.pop('env')
    else:
        env = os.environ

    ret = {}
    for name in [k for k in env if k.startswith('OS_')]:
        # take of 'os_'
        short = name.lower()[3:]
        ret[short] = kwargs.get(short, env[name])

    bools = ('insecure',)
    for k in bools:
        if k not in ret:
            continue
        if isinstance(ret[k], str):
            ret[k] = (ret[k].lower() not in ("", "0", "no", "off"))
        else:
            ret[k] = bool(ret[k])

    return ret


def get_keystone_token(**kwargs):
    if not HAS_KEYSTONE_V2:
        raise RuntimeError("Cannot use keystone v2. No library support.")


def get_keystone_session(**kwargs):
    if not HAS_KEYSTONE_V3:
        raise RuntimeError("Cannot use keystone v3. No library support.")
    pt = ('user_domain_name', 'username', 'password', 'project_domain_name',
          'project_name', 'auth_url')
    auth = ksv3_password(**{k: v for k, v in kwargs.items() if k in pt})
    return ksv3_session(auth)


def get_keystone_auth(**kwargs):
    # return a dictionary with 'version' and either 'token' or 'session'
    # based on 'auth_version' in kwargs.
    # if auth_version is 3 (or '3'):
    #    {'version': 3, 'session': <session>}
    # otherwise:
    #    {'version': 2, 'token': <token>}
    # for v2 auth, required kwargs are:
    #   username, auth_url.
    #   auth_token or password.
    #   tenant_id or tenant_name.
    # for v3 auth, required are:
    #    auth_version=3 or identity_api_version=3: or v2 will be attempted.
    #    user_domain_name, username, password, project_domain_name,
    #    project_name
    if kwargs.get('auth_version'):
        version = int(kwargs['auth_version'])
        if version != 3:
            raise ValueError("Unable to handle auth version %s" % version)
        return {'version': version, 'session': get_keystone_session(**kwargs)}
    else:
        return {'version': 2, 'token': get_keystone_token(**kwargs)}


def get_keystone_client_from_env():
    return get_keystone_client(**load_openstack_settings())


def get_keystone_client(**kwargs):
    auth = get_keystone_auth(**kwargs)
    if auth['version'] == 2:
        pt = ('username', 'password', 'tenant_id', 'tenant_name',
              'auth_url', 'cacert', 'insecure')
        kskw = {k: kwargs.get(k) for k in pt if k in kwargs}
        return ksv2_client(**kskw)
    elif auth['version'] == 3:
        client = ksv3_client(session=auth['session'], **kwargs)
        if not client.authenticate():
            raise RuntimeError("Failed to authenticate with v3 client.")
        return client
    else:
        raise RuntimeError("Unable to handle version %s" % auth['version'])
    

def get_regions(client=None, services=None, os_settings=None):
    # if kscreds had 'region_name', then return that
    if os_settings and os_settings.get('region_name'):
        return [os_settings.get('region_name')]

    if client is None:
        client = get_keystone_client_from_env()

    endpoints = client.service_catalog.get_endpoints()
    if services is None:
        services = list(endpoints.keys())
    regions = set()
    for service in services:
        for r in endpoints.get(service, {}):
            regions.add(r['region'])

    return list(regions)


def get_ksclient(**kwargs):
    pt = ('username', 'password', 'tenant_id', 'tenant_name', 'auth_url',
          'cacert', 'insecure')
    kskw = {k: kwargs.get(k) for k in pt if k in kwargs}
    return ksclient.Client(**kskw)


def get_service_conn_info(service='image', client=None, **kwargs):
    # return a dict with token, insecure, cacert, endpoint
    if not client:
        client = get_keystone_client(**kwargs)

    endpoint = _get_endpoint(client, service, **kwargs)
    return {'token': client.auth_token, 'insecure': kwargs.get('insecure'),
            'cacert': kwargs.get('cacert'), 'endpoint': endpoint,
            'tenant_id': client.tenant_id}


def _get_endpoint(client, service, **kwargs):
    """Get an endpoint using the provided keystone client."""
    endpoint_kwargs = {
        'service_type': service,
        'endpoint_type': kwargs.get('endpoint_type') or 'publicURL',
    }

    if kwargs.get('region_name'):
        endpoint_kwargs['attr'] = 'region'
        endpoint_kwargs['filter_value'] = kwargs.get('region_name')

    endpoint = client.service_catalog.url_for(**endpoint_kwargs)
    return endpoint