Add keycloak auth support
Change-Id: Ie1b1a6467991f3c2cff6cc823f73103655844452
This commit is contained in:
parent
2280b332a4
commit
9c30a9497e
|
@ -6,6 +6,10 @@ pipeline = cors faultwrapper healthcheck http_proxy_to_wsgi versionnegotiation o
|
|||
[pipeline:glare-api-keystone]
|
||||
pipeline = cors faultwrapper healthcheck http_proxy_to_wsgi versionnegotiation osprofiler authtoken context glarev1api
|
||||
|
||||
# Use this pipeline for Keycloak auth
|
||||
[pipeline:glare-api-keystone]
|
||||
pipeline = cors faultwrapper healthcheck http_proxy_to_wsgi versionnegotiation osprofiler keycloak context glarev1api
|
||||
|
||||
[app:glarev1api]
|
||||
paste.app_factory = glare.api.v1.router:API.factory
|
||||
|
||||
|
@ -30,6 +34,9 @@ paste.filter_factory = glare.api.middleware.context:UnauthenticatedContextMiddle
|
|||
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
|
||||
delay_auth_decision = true
|
||||
|
||||
[filter:keycloak]
|
||||
paste.filter_factory = glare.api.middleware.keycloak_auth:KeycloakAuthMiddleware.factory
|
||||
|
||||
[filter:osprofiler]
|
||||
paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ from oslo_middleware import base as base_middleware
|
|||
from oslo_middleware import request_id
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from glare.common import exception
|
||||
from glare.common import policy
|
||||
from glare.i18n import _
|
||||
|
||||
|
@ -84,7 +85,7 @@ class ContextMiddleware(base_middleware.ConfigurableMiddleware):
|
|||
elif CONF.allow_anonymous_access:
|
||||
req.context = ContextMiddleware._get_anonymous_context()
|
||||
else:
|
||||
raise webob.exc.HTTPUnauthorized()
|
||||
raise exception.Unauthorized()
|
||||
|
||||
@staticmethod
|
||||
def _get_anonymous_context():
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
# Copyright 2010 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 memcache
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import pprint
|
||||
import requests
|
||||
import webob.dec
|
||||
|
||||
from glare.common import exception
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
keycloak_oidc_opts = [
|
||||
cfg.StrOpt(
|
||||
'auth_url',
|
||||
help='Keycloak base url (e.g. https://my.keycloak:8443/auth)'
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'insecure',
|
||||
default=False,
|
||||
help='If True, SSL/TLS certificate verification is disabled'
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'memcached_server',
|
||||
default=None,
|
||||
help='Url of memcached server to use for caching'
|
||||
),
|
||||
cfg.IntOpt(
|
||||
'token_cache_time',
|
||||
default=60,
|
||||
min=0,
|
||||
help='In order to prevent excessive effort spent validating '
|
||||
'tokens, the middleware caches previously-seen tokens '
|
||||
'for a configurable duration (in seconds).'
|
||||
),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(keycloak_oidc_opts, group="keycloak_oidc")
|
||||
|
||||
|
||||
class KeycloakAuthMiddleware(object):
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
mcserv_url = CONF.memcached_server
|
||||
self.mcclient = memcache.Client(mcserv_url) if mcserv_url else None
|
||||
|
||||
def authenticate(self, request):
|
||||
realm_name = request.headers.get('X-Project-Id')
|
||||
|
||||
user_info_endpoint = (
|
||||
"%s/realms/%s/protocol/openid-connect/userinfo" %
|
||||
(CONF.keycloak_oidc.auth_url, realm_name)
|
||||
)
|
||||
|
||||
access_token = request.headers.get('X-Auth-Token')
|
||||
|
||||
info = None
|
||||
if self.mcclient:
|
||||
info = self.mcclient.get(access_token)
|
||||
|
||||
if info is None:
|
||||
resp = requests.get(
|
||||
user_info_endpoint,
|
||||
headers={"Authorization": "Bearer %s" % access_token},
|
||||
verify=not CONF.keycloak_oidc.insecure
|
||||
)
|
||||
resp.raise_for_status()
|
||||
if self.mcclient:
|
||||
self.mcclient.set(access_token, resp.json(),
|
||||
time=CONF.token_cache_time)
|
||||
info = resp.json()
|
||||
|
||||
LOG.debug(
|
||||
"HTTP response from OIDC provider: %s" %
|
||||
pprint.pformat(info)
|
||||
)
|
||||
|
||||
return info
|
||||
|
||||
def get_roles(self, request):
|
||||
realm_name = request.headers.get('X-Project-Id')
|
||||
|
||||
user_roles_endpoint = (
|
||||
"%s/realms/%s/roles" %
|
||||
(CONF.keycloak_oidc.auth_url, realm_name)
|
||||
)
|
||||
|
||||
access_token = request.headers.get('X-Auth-Token')
|
||||
|
||||
roles = None
|
||||
if self.mcclient:
|
||||
roles = self.mcclient.get(realm_name)
|
||||
|
||||
if roles is None:
|
||||
resp = requests.get(
|
||||
user_roles_endpoint,
|
||||
headers={"Authorization": "Bearer %s" % access_token}
|
||||
)
|
||||
roles = [role['name'] for role in resp.json()]
|
||||
if self.mcclient:
|
||||
self.mcclient.set(realm_name, roles,
|
||||
time=CONF.token_cache_time)
|
||||
|
||||
LOG.debug(
|
||||
"Roles for realm %s: %s" %
|
||||
(realm_name, pprint.pformat(roles))
|
||||
)
|
||||
|
||||
return roles
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, request):
|
||||
if 'X-Project-Id' not in request.headers:
|
||||
raise exception.Unauthorized()
|
||||
self.authenticate(request)
|
||||
roles = ','.join(self.get_roles(request))
|
||||
request.headers["X-Identity-Status"] = "Confirmed"
|
||||
request.headers["X-Roles"] = roles
|
||||
return request.get_response(self.app)
|
|
@ -22,6 +22,7 @@ import itertools
|
|||
from osprofiler import opts as profiler
|
||||
|
||||
import glare.api.middleware.context
|
||||
import glare.api.middleware.keycloak_auth
|
||||
import glare.api.v1.resource
|
||||
import glare.api.versions
|
||||
import glare.common.config
|
||||
|
@ -33,6 +34,7 @@ import glare.objects.meta.registry
|
|||
_artifacts_opts = [
|
||||
(None, list(itertools.chain(
|
||||
glare.api.middleware.context.context_opts,
|
||||
glare.api.middleware.keycloak_auth.keycloak_oidc_opts,
|
||||
glare.api.v1.resource.list_configs,
|
||||
glare.api.versions.versions_opts,
|
||||
glare.common.config.common_opts,
|
||||
|
|
|
@ -20,6 +20,7 @@ oslo.utils>=3.18.0 # Apache-2.0
|
|||
futurist!=0.15.0,>=0.11.0 # Apache-2.0
|
||||
keystoneauth1>=2.18.0 # Apache-2.0
|
||||
keystonemiddleware>=4.12.0 # Apache-2.0
|
||||
python-memcached>=1.56 # PSF
|
||||
WSME>=0.8 # MIT
|
||||
|
||||
# For paste.util.template used in keystone.common.template
|
||||
|
|
Loading…
Reference in New Issue