Files
distcloud/distributedcloud/dcmanager/api/app.py
Hugo Brito 2b198a8009 Add OIDC authentication support to DCManager API
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>
2026-01-12 17:55:41 -03:00

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()