supprt keycloak first stage
Change-Id: Ia0b021ee404ab130c79f4d35b1210d365e9f42e0
This commit is contained in:
parent
8b2e5ceb17
commit
c0f845dfe9
@ -10,6 +10,12 @@ use = egg:Paste#urlmap
|
|||||||
/v1 = vitragev1_keystone_pipeline
|
/v1 = vitragev1_keystone_pipeline
|
||||||
/healthcheck = healthcheck
|
/healthcheck = healthcheck
|
||||||
|
|
||||||
|
[composite:vitrage+keycloak]
|
||||||
|
use = egg:Paste#urlmap
|
||||||
|
/ = vitrageversions_pipeline
|
||||||
|
/v1 = vitragev1_keycloak_pipeline
|
||||||
|
/healthcheck = healthcheck
|
||||||
|
|
||||||
[app:healthcheck]
|
[app:healthcheck]
|
||||||
use = egg:oslo.middleware#healthcheck
|
use = egg:oslo.middleware#healthcheck
|
||||||
oslo_config_project = vitrage
|
oslo_config_project = vitrage
|
||||||
@ -21,20 +27,27 @@ pipeline = cors http_proxy_to_wsgi vitrageversions
|
|||||||
paste.app_factory = vitrage.api.app:app_factory
|
paste.app_factory = vitrage.api.app:app_factory
|
||||||
root = vitrage.api.controllers.root.VersionsController
|
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:vitragev1_noauth_pipeline]
|
||||||
pipeline = cors http_proxy_to_wsgi request_id osprofiler vitragev1
|
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]
|
[app:vitragev1]
|
||||||
paste.app_factory = vitrage.api.app:app_factory
|
paste.app_factory = vitrage.api.app:app_factory
|
||||||
root = vitrage.api.controllers.v1.root.V1Controller
|
root = vitrage.api.controllers.v1.root.V1Controller
|
||||||
|
|
||||||
[filter:authtoken]
|
[filter:keystoneauthtoken]
|
||||||
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
|
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
|
||||||
oslo_config_project = vitrage
|
oslo_config_project = vitrage
|
||||||
|
|
||||||
|
[filter:keycloakauthtoken]
|
||||||
|
paste.filter_factory = vitrage.middleware.keycloak:filter_factory
|
||||||
|
oslo_config_project = vitrage
|
||||||
|
|
||||||
[filter:request_id]
|
[filter:request_id]
|
||||||
paste.filter_factory = oslo_middleware:RequestId.factory
|
paste.filter_factory = oslo_middleware:RequestId.factory
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ extensions = [
|
|||||||
'sphinx.ext.autodoc',
|
'sphinx.ext.autodoc',
|
||||||
# 'sphinx.ext.intersphinx',
|
# 'sphinx.ext.intersphinx',
|
||||||
'reno.sphinxext',
|
'reno.sphinxext',
|
||||||
'oslosphinx'
|
'openstackdocstheme'
|
||||||
]
|
]
|
||||||
|
|
||||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||||
@ -41,6 +41,12 @@ master_doc = 'index'
|
|||||||
project = u'vitrage releasenotes'
|
project = u'vitrage releasenotes'
|
||||||
copyright = u'2016, Vitrage developers'
|
copyright = u'2016, Vitrage developers'
|
||||||
|
|
||||||
|
# openstackdocstheme options
|
||||||
|
repository_name = 'openstack/vitrage'
|
||||||
|
bug_project = 'vitrage'
|
||||||
|
bug_tag = ''
|
||||||
|
html_last_updated_fmt = '%Y-%m-%d %H:%M'
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
add_function_parentheses = True
|
add_function_parentheses = True
|
||||||
|
|
||||||
@ -56,7 +62,7 @@ pygments_style = 'sphinx'
|
|||||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||||
# html_theme_path = ["."]
|
# html_theme_path = ["."]
|
||||||
# html_theme = '_theme'
|
html_theme = 'openstackdocs'
|
||||||
# html_static_path = ['static']
|
# html_static_path = ['static']
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
|
@ -14,7 +14,7 @@ python-novaclient>=9.0.0 # Apache-2.0
|
|||||||
python-heatclient>=1.6.1 # Apache-2.0
|
python-heatclient>=1.6.1 # Apache-2.0
|
||||||
pyzabbix>=0.7.4 # LGPL
|
pyzabbix>=0.7.4 # LGPL
|
||||||
networkx>=1.10 # BSD
|
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.messaging!=5.25.0,>=5.24.2 # Apache-2.0
|
||||||
oslo.log>=3.22.0 # Apache-2.0
|
oslo.log>=3.22.0 # Apache-2.0
|
||||||
oslo.policy>=1.23.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
|
voluptuous>=0.8.9 # BSD License
|
||||||
sympy>=0.7.6 # BSD
|
sympy>=0.7.6 # BSD
|
||||||
pysnmp>=4.2.3 # BSD
|
pysnmp>=4.2.3 # BSD
|
||||||
|
PyJWT>=1.0.1 # MIT
|
||||||
osprofiler>=1.4.0 # Apache-2.0
|
osprofiler>=1.4.0 # Apache-2.0
|
||||||
|
|
||||||
|
@ -14,11 +14,10 @@ python-novaclient>=9.0.0 # Apache-2.0
|
|||||||
python-heatclient>=1.6.1 # Apache-2.0
|
python-heatclient>=1.6.1 # Apache-2.0
|
||||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||||
pyzabbix>=0.7.4 # LGPL
|
pyzabbix>=0.7.4 # LGPL
|
||||||
sphinx!=1.6.1,>=1.5.1 # BSD
|
|
||||||
oslo.log>=3.22.0 # Apache-2.0
|
oslo.log>=3.22.0 # Apache-2.0
|
||||||
oslotest>=1.10.0 # Apache-2.0
|
oslotest>=1.10.0 # Apache-2.0
|
||||||
oslo.service>=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.messaging!=5.25.0,>=5.24.2 # Apache-2.0
|
||||||
oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0
|
oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0
|
||||||
oslo.policy>=1.23.0 # Apache-2.0
|
oslo.policy>=1.23.0 # Apache-2.0
|
||||||
@ -34,5 +33,5 @@ reno!=2.3.1,>=1.8.0 # Apache-2.0
|
|||||||
pysnmp>=4.2.3 # BSD
|
pysnmp>=4.2.3 # BSD
|
||||||
|
|
||||||
# Doc requirements
|
# Doc requirements
|
||||||
openstackdocstheme>=1.5.0 # Apache-2.0
|
openstackdocstheme>=1.11.0 # Apache-2.0
|
||||||
oslosphinx>=4.7.0 # Apache-2.0
|
sphinx>=1.6.2 # BSD
|
||||||
|
@ -25,8 +25,8 @@ OPTS = [
|
|||||||
help='Configuration file for WSGI definition of API.'),
|
help='Configuration file for WSGI definition of API.'),
|
||||||
cfg.IntOpt('workers', default=1, min=1,
|
cfg.IntOpt('workers', default=1, min=1,
|
||||||
help='Number of workers for vitrage API server.'),
|
help='Number of workers for vitrage API server.'),
|
||||||
cfg.BoolOpt('pecan_debug', default=False,
|
cfg.StrOpt('auth_mode', default='keystone', choices={'keystone',
|
||||||
help='Toggle Pecan Debug Middleware.'),
|
'noauth',
|
||||||
cfg.StrOpt('auth_mode', default='keystone', choices={'keystone', 'noauth'},
|
'keycloak'},
|
||||||
help='Authentication mode to use.'),
|
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…
Reference in New Issue
Block a user