This commit introduces OpenID Connect (OIDC) authentication
support to the DCManager API, allowing requests to be authenticated
using either Keystone tokens or OIDC tokens.
Behavior overview:
- The REST API will authenticate using Keystone when the
`X-Auth-Token` header is provided (existing behavior).
- When the `OIDC-Token` header is provided, OIDC authentication
is performed instead.
- If both tokens are present, default to Keystone authentication.
For OIDC authentication:
- The REST API retrieves OIDC IDP configuration parameters from
`system service-parameters` under `kube-apiserver`:
- `oidc-issuer-url`
- `oidc-client-id`
- `oidc-username-claim`
- `oidc-groups-claim`
- If OIDC parameters are not configured, authentication fails
with an unauthenticated response.
- If configured, the REST API validates the OIDC token with the
IDP issuer and extracts claims.
- OIDC arguments and claims are cached.
- External users and groups are mapped to internal
Project+Role tuples based on StarlingX rolebindings.
Test Plan:
PASS: Authenticate REST API requests with Keystone (`X-Auth-Token`).
PASS: Authenticate REST API requests with OIDC (`OIDC-Token`).
PASS: Verify Keystone is used when both tokens are present.
PASS: Verify unauthenticated response when OIDC parameters are
missing.
PASS: Validate token claims and role mappings are applied correctly.
PASS: Confirm cached tokens continue to authorize during temporary
IDP connectivity loss.
PASS: Force to OIDC token validation to return claims as None and verify
the api returns a NotAuthorized exception.
Depends-On: https://review.opendev.org/c/starlingx/integ/+/970455
Story: 2011646
Task: 53594
Change-Id: I830084fcad9b6413477e703514325030c7dc58a2
Signed-off-by: Hugo Brito <hugo.brito@windriver.com>
99 lines
2.8 KiB
Python
99 lines
2.8 KiB
Python
# Copyright (c) 2015 Huawei, Tech. Co,. Ltd.
|
|
# Copyright (c) 2017, 2019, 2021, 2024, 2026 Wind River Systems, Inc.
|
|
# 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 os
|
|
import pecan
|
|
|
|
from keystonemiddleware import auth_token
|
|
from oslo_config import cfg
|
|
from oslo_middleware import request_id
|
|
from oslo_service import service
|
|
|
|
from dcmanager.common import context as ctx
|
|
from dcmanager.common.i18n import _
|
|
|
|
|
|
def setup_app(*args, **kwargs):
|
|
|
|
opts = cfg.CONF.pecan
|
|
config = {
|
|
"server": {"port": cfg.CONF.bind_port, "host": cfg.CONF.bind_host},
|
|
"app": {
|
|
"root": "dcmanager.api.controllers.root.RootController",
|
|
"modules": ["dcmanager.api"],
|
|
"debug": opts.debug,
|
|
"auth_enable": opts.auth_enable,
|
|
"errors": {400: "/error", "__force_dict__": True},
|
|
},
|
|
}
|
|
|
|
pecan_config = pecan.configuration.conf_from_dict(config)
|
|
|
|
# app_hooks = [], hook collection will be put here later
|
|
|
|
app = pecan.make_app(
|
|
pecan_config.app.root,
|
|
debug=False,
|
|
wrap_app=_wrap_app,
|
|
force_canonical=False,
|
|
hooks=lambda: [ctx.AuthHook(), ctx.AuditLoggingHook()],
|
|
guess_content_type_from_ext=True,
|
|
)
|
|
|
|
return app
|
|
|
|
|
|
def _wrap_app(app):
|
|
app = request_id.RequestId(app)
|
|
|
|
if cfg.CONF.pecan.auth_enable:
|
|
auth_type = _get_auth_type()
|
|
|
|
# For Keystone or OIDC, we still need Keystone middleware
|
|
# OIDC validation is handled in AuthHook
|
|
if auth_type in ["keystone", "oidc"]:
|
|
conf = dict(cfg.CONF.keystone_authtoken)
|
|
conf.update({"delay_auth_decision": True})
|
|
|
|
# Policy enforcement works for both Keystone and OIDC
|
|
return auth_token.AuthProtocol(app, conf)
|
|
|
|
return app
|
|
|
|
|
|
_launcher = None
|
|
|
|
|
|
def _get_auth_type():
|
|
"""Get authentication type from environment or config"""
|
|
env_auth_type = os.environ.get("STX_AUTH_TYPE")
|
|
if env_auth_type and env_auth_type.lower() in ["keystone", "oidc"]:
|
|
return env_auth_type.lower()
|
|
return cfg.CONF.auth_strategy
|
|
|
|
|
|
def serve(api_service, conf, workers=1):
|
|
global _launcher
|
|
if _launcher:
|
|
raise RuntimeError(_("serve() can only be called once"))
|
|
|
|
_launcher = service.launch(conf, api_service, workers=workers)
|
|
|
|
|
|
def wait():
|
|
_launcher.wait()
|