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
219
220
|
# Copyright (c) 2013 Rackspace, Inc.
#
# 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 json
import logging
import os
from keystoneclient.auth.base import BaseAuthPlugin
from keystoneclient import exceptions
from keystoneclient import session as ks_session
from barbicanclient.common.auth import KeystoneAuthPluginWrapper
from barbicanclient.openstack.common.gettextutils import _
from barbicanclient import orders
from barbicanclient import secrets
from barbicanclient import verifications
LOG = logging.getLogger(__name__)
class HTTPError(Exception):
"""Base exception for HTTP errors."""
def __init__(self, message):
super(HTTPError, self).__init__(message)
class HTTPServerError(HTTPError):
"""Raised for 5xx responses from the server."""
pass
class HTTPClientError(HTTPError):
"""Raised for 4xx responses from the server."""
pass
class HTTPAuthError(HTTPError):
"""Raised for 401 Unauthorized responses from the server."""
pass
class Client(object):
def __init__(self, session=None, auth_plugin=None, endpoint=None,
tenant_id=None, insecure=False, service_type='keystore',
interface='public'):
"""
Barbican client object used to interact with barbican service.
:param session: This can be either requests.Session or
keystoneclient.session.Session
:param auth_plugin: Authentication backend plugin
defaults to None. This can also be a keystoneclient authentication
plugin.
:param endpoint: Barbican endpoint url. Required when not using
an auth_plugin. When not provided, the client will try to
fetch this from the auth service catalog
:param tenant_id: The tenant ID used for context in barbican.
Required when not using auth_plugin. When not provided,
the client will try to get this from the auth_plugin.
:param insecure: Explicitly allow barbicanclient to perform
"insecure" TLS (https) requests. The server's certificate
will not be verified against any certificate authorities.
This option should be used with caution.
:param service_type: Used as an endpoint filter when using a
keystone auth plugin. Defaults to 'keystore'
:param interface: Another endpoint filter. Defaults to 'public'
"""
LOG.debug(_("Creating Client object"))
self._wrap_session_with_keystone_if_required(session, insecure)
auth_plugin = self._update_session_auth_plugin(auth_plugin)
if auth_plugin:
self._barbican_url = self._session.get_endpoint(
service_type=service_type, interface=interface)
self._tenant_id = self._get_tenant_id(self._session, auth_plugin)
else:
# neither auth_plugin is provided nor it is available from session
# fallback to passed in parameters
self._validate_endpoint_and_tenant_id(endpoint, tenant_id)
self._barbican_url = self._get_normalized_endpoint(endpoint)
self._tenant_id = tenant_id
self.base_url = '{0}/{1}'.format(self._barbican_url, self._tenant_id)
self.secrets = secrets.SecretManager(self)
self.orders = orders.OrderManager(self)
self.verifications = verifications.VerificationManager(self)
def _wrap_session_with_keystone_if_required(self, session, insecure):
# if session is not a keystone session, wrap it
if not isinstance(session, ks_session.Session):
self._session = ks_session.Session(
session=session, verify=not insecure)
else:
self._session = session
def _update_session_auth_plugin(self, auth_plugin):
# if auth_plugin is not provided and the session
# has one, use it
using_auth_from_session = False
if auth_plugin is None and self._session.auth is not None:
auth_plugin = self._session.auth
using_auth_from_session = True
ks_auth_plugin = auth_plugin
# if auth_plugin is not a keystone plugin, wrap it
if auth_plugin and not isinstance(auth_plugin, BaseAuthPlugin):
ks_auth_plugin = KeystoneAuthPluginWrapper(auth_plugin)
# if auth_plugin is provided, override the session's auth with it
if not using_auth_from_session:
self._session.auth = ks_auth_plugin
return auth_plugin
def _validate_endpoint_and_tenant_id(self, endpoint, tenant_id):
if endpoint is None:
raise ValueError('Barbican endpoint url must be provided, or '
'must be available from auth_plugin or '
'keystone_client')
if tenant_id is None:
raise ValueError('Tenant ID must be provided, or must be '
'available from the auth_plugin or '
'keystone-client')
def _get_normalized_endpoint(self, endpoint):
if endpoint.endswith('/'):
endpoint = endpoint[:-1]
return endpoint
def _get_tenant_id(self, session, auth_plugin):
if isinstance(auth_plugin, BaseAuthPlugin):
# this is a keystoneclient auth plugin
if hasattr(auth_plugin, 'get_access'):
return auth_plugin.get_access(session).project_id
else:
# not an identity auth plugin and we don't know how to lookup
# the tenant_id
raise ValueError('Unable to obtain tenant_id from auth plugin')
else:
# this is a Barbican auth plugin
return auth_plugin.tenant_id
def get(self, href, params=None):
headers = {'Accept': 'application/json'}
resp = self._session.get(href, params=params, headers=headers)
self._check_status_code(resp)
return resp.json()
def get_raw(self, href, headers):
resp = self._session.get(href, headers=headers)
self._check_status_code(resp)
return resp.content
def delete(self, href):
resp = self._session.delete(href)
self._check_status_code(resp)
def post(self, path, data):
url = '{0}/{1}/'.format(self.base_url, path)
headers = {'content-type': 'application/json'}
resp = self._session.post(url, data=json.dumps(data), headers=headers)
self._check_status_code(resp)
return resp.json()
def _check_status_code(self, resp):
status = resp.status_code
LOG.debug('Response status {0}'.format(status))
if status == 401:
LOG.error('Auth error: {0}'.format(self._get_error_message(resp)))
raise HTTPAuthError('{0}'.format(self._get_error_message(resp)))
if not status or status >= 500:
LOG.error('5xx Server error: {0}'.format(
self._get_error_message(resp)
))
raise HTTPServerError('{0}'.format(self._get_error_message(resp)))
if status >= 400:
LOG.error('4xx Client error: {0}'.format(
self._get_error_message(resp)
))
raise HTTPClientError('{0}'.format(self._get_error_message(resp)))
def _get_error_message(self, resp):
try:
message = resp.json()['title']
except ValueError:
message = resp.content
return message
def env(*vars, **kwargs):
"""Search for the first defined of possibly many env vars
Returns the first environment variable defined in vars, or
returns the default defined in kwargs.
Source: Keystone's shell.py
"""
for v in vars:
value = os.environ.get(v, None)
if value:
return value
return kwargs.get('default', '')
|