Merge "supprt keycloak first stage"
This commit is contained in:
commit
d3a992bafc
@ -10,6 +10,12 @@ use = egg:Paste#urlmap
|
||||
/v1 = vitragev1_keystone_pipeline
|
||||
/healthcheck = healthcheck
|
||||
|
||||
[composite:vitrage+keycloak]
|
||||
use = egg:Paste#urlmap
|
||||
/ = vitrageversions_pipeline
|
||||
/v1 = vitragev1_keycloak_pipeline
|
||||
/healthcheck = healthcheck
|
||||
|
||||
[app:healthcheck]
|
||||
use = egg:oslo.middleware#healthcheck
|
||||
oslo_config_project = vitrage
|
||||
@ -21,20 +27,27 @@ pipeline = cors http_proxy_to_wsgi vitrageversions
|
||||
paste.app_factory = vitrage.api.app:app_factory
|
||||
root = vitrage.api.controllers.root.VersionsController
|
||||
|
||||
[pipeline:vitragev1_keystone_pipeline]
|
||||
pipeline = cors http_proxy_to_wsgi request_id osprofiler authtoken vitragev1
|
||||
|
||||
[pipeline:vitragev1_noauth_pipeline]
|
||||
pipeline = cors http_proxy_to_wsgi request_id osprofiler vitragev1
|
||||
|
||||
[pipeline:vitragev1_keystone_pipeline]
|
||||
pipeline = cors http_proxy_to_wsgi request_id osprofiler keystoneauthtoken vitragev1
|
||||
|
||||
[pipeline:vitragev1_keycloak_pipeline]
|
||||
pipeline = cors http_proxy_to_wsgi request_id osprofiler keycloakauthtoken vitragev1
|
||||
|
||||
[app:vitragev1]
|
||||
paste.app_factory = vitrage.api.app:app_factory
|
||||
root = vitrage.api.controllers.v1.root.V1Controller
|
||||
|
||||
[filter:authtoken]
|
||||
[filter:keystoneauthtoken]
|
||||
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
|
||||
oslo_config_project = vitrage
|
||||
|
||||
[filter:keycloakauthtoken]
|
||||
paste.filter_factory = vitrage.middleware.keycloak:filter_factory
|
||||
oslo_config_project = vitrage
|
||||
|
||||
[filter:request_id]
|
||||
paste.filter_factory = oslo_middleware:RequestId.factory
|
||||
|
||||
|
@ -14,7 +14,7 @@ python-novaclient>=9.0.0 # Apache-2.0
|
||||
python-heatclient>=1.6.1 # Apache-2.0
|
||||
pyzabbix>=0.7.4 # LGPL
|
||||
networkx>=1.10 # BSD
|
||||
oslo.config>=4.0.0 # Apache-2.0
|
||||
oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0
|
||||
oslo.messaging!=5.25.0,>=5.24.2 # Apache-2.0
|
||||
oslo.log>=3.22.0 # Apache-2.0
|
||||
oslo.policy>=1.23.0 # Apache-2.0
|
||||
@ -28,5 +28,6 @@ stevedore>=1.20.0 # Apache-2.0
|
||||
voluptuous>=0.8.9 # BSD License
|
||||
sympy>=0.7.6 # BSD
|
||||
pysnmp>=4.2.3 # BSD
|
||||
PyJWT>=1.0.1 # MIT
|
||||
osprofiler>=1.4.0 # Apache-2.0
|
||||
|
||||
|
@ -17,7 +17,7 @@ pyzabbix>=0.7.4 # LGPL
|
||||
oslo.log>=3.22.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
oslo.service>=1.10.0 # Apache-2.0
|
||||
oslo.config>=4.0.0 # Apache-2.0
|
||||
oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0
|
||||
oslo.messaging!=5.25.0,>=5.24.2 # Apache-2.0
|
||||
oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0
|
||||
oslo.policy>=1.23.0 # Apache-2.0
|
||||
|
@ -25,8 +25,8 @@ OPTS = [
|
||||
help='Configuration file for WSGI definition of API.'),
|
||||
cfg.IntOpt('workers', default=1, min=1,
|
||||
help='Number of workers for vitrage API server.'),
|
||||
cfg.BoolOpt('pecan_debug', default=False,
|
||||
help='Toggle Pecan Debug Middleware.'),
|
||||
cfg.StrOpt('auth_mode', default='keystone', choices={'keystone', 'noauth'},
|
||||
cfg.StrOpt('auth_mode', default='keystone', choices={'keystone',
|
||||
'noauth',
|
||||
'keycloak'},
|
||||
help='Authentication mode to use.'),
|
||||
]
|
||||
|
15
vitrage/middleware/__init__.py
Normal file
15
vitrage/middleware/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
# Copyright 2017 - Nokia
|
||||
#
|
||||
# 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.
|
||||
|
||||
__author__ = 'stack'
|
98
vitrage/middleware/keycloak.py
Normal file
98
vitrage/middleware/keycloak.py
Normal file
@ -0,0 +1,98 @@
|
||||
# Copyright 2017 - Nokia
|
||||
#
|
||||
# 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 jwt
|
||||
import requests
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_middleware import base
|
||||
from oslo_serialization import jsonutils
|
||||
from webob import exc
|
||||
|
||||
OPENID_CONNECT_USERINFO = '%s/realms/%s/protocol/openid-connect/userinfo'
|
||||
|
||||
KEYCLOAK_OPTS = [
|
||||
cfg.StrOpt('auth_url', default='127.0.0.1',
|
||||
help='Keycloak authentication server ip',),
|
||||
cfg.StrOpt('insecure', default=False,
|
||||
help='If True, SSL/TLS certificate verification is disabled'),
|
||||
]
|
||||
|
||||
|
||||
class KeycloakAuth(base.ConfigurableMiddleware):
|
||||
|
||||
def __init__(self, application, conf=None):
|
||||
super(KeycloakAuth, self).__init__(application, conf)
|
||||
|
||||
self.oslo_conf.register_opts(KEYCLOAK_OPTS, 'keycloak')
|
||||
self.auth_url = self._conf_get('auth_url', 'keycloak')
|
||||
self.insecure = self._conf_get('insecure', 'keycloak')
|
||||
|
||||
@property
|
||||
def reject_auth_headers(self):
|
||||
header_val = 'Keycloak uri=\'%s\'' % self.auth_url
|
||||
return [('WWW-Authenticate', header_val)]
|
||||
|
||||
@property
|
||||
def roles(self):
|
||||
try:
|
||||
decoded = jwt.decode(self.token, algorithms=['RS256'],
|
||||
verify=False)
|
||||
except jwt.DecodeError:
|
||||
message = "Token can't be decoded because of wrong format."
|
||||
self._unauthorized(message)
|
||||
|
||||
return ','.join(decoded['realm_access']['roles']) \
|
||||
if 'realm_access' in decoded else ''
|
||||
|
||||
def process_request(self, req):
|
||||
self._authenticate(req)
|
||||
|
||||
def _authenticate(self, req):
|
||||
self.token = req.headers.get('X-Auth-Token')
|
||||
if self.token:
|
||||
self._decode(req)
|
||||
else:
|
||||
message = 'Auth token must be provided in "X-Auth-Token" header.'
|
||||
self._unauthorized(message)
|
||||
|
||||
def _decode(self, req):
|
||||
realm_name = req.headers.get('X-Project-Id')
|
||||
endpoint = OPENID_CONNECT_USERINFO % (self.auth_url, realm_name)
|
||||
headers = {'Authorization": "Bearer %s' % self.token}
|
||||
|
||||
resp = requests.get(endpoint, headers=headers,
|
||||
verify=not self.insecure)
|
||||
|
||||
resp.raise_for_status()
|
||||
|
||||
self._set_req_headers(req)
|
||||
|
||||
def _set_req_headers(self, req):
|
||||
req.headers['X-Identity-Status'] = 'Confirmed'
|
||||
req.headers['X-Roles'] = self.roles
|
||||
|
||||
def _unauthorized(self, message):
|
||||
body = {'error': {
|
||||
'code': 401,
|
||||
'title': 'Unauthorized',
|
||||
'message': message,
|
||||
}}
|
||||
|
||||
raise exc.HTTPUnauthorized(body=jsonutils.dumps(body),
|
||||
headers=self.reject_auth_headers,
|
||||
content_type='application/json')
|
||||
|
||||
|
||||
filter_factory = KeycloakAuth.factory
|
Loading…
x
Reference in New Issue
Block a user