distcloud/distributedcloud/dcorch/common/context.py
Joao Victor Portal 28a4b568b1 Implement access control for DC API
This commit implements access control for DC API. The reference doc can
be found at
"https://docs.starlingx.io/api-ref/distcloud/api-ref-dcmanager-v1.html".
Unit tests and YAML file support will be done in other tasks.

The access control implementation for GET requests requires the user to
have "reader" role and to be present in either "admin" or "services"
project. For other requests, it requires the user to have "admin" role
and to be present in either "admin" or "services" project. Requests
using public API URLs require no credentials.

As all default system users of StarlingX have "admin" role and are
present in either project "admin" or "services", there should be no
regression with the change introduced here.

The implementation done here is a little bit different from the one done
for sysinv and FM APIs, because the routing of requests is not done when
"before()" method of Pecan hooks are called, so the controller is not
defined at this point.

To test the access control of DC API, the following commands are used
(long list of parameters is replaced by "<params>"):

dcmanager subcloud add <params>
dcmanager subcloud manage subcloud2
dcmanager subcloud list
dcmanager subcloud delete subcloud2
dcmanager subcloud-deploy upload <params>
dcmanager subcloud-deploy show
dcmanager alarm summary
dcmanager patch-strategy create
dcmanager patch-strategy show
dcmanager patch-strategy apply
dcmanager patch-strategy abort
dcmanager patch-strategy delete
dcmanager strategy-config update <params> subcloud1
dcmanager strategy-config list
dcmanager strategy-config delete subcloud1
dcmanager subcloud-group add --name group01
dcmanager subcloud-group update --description test group01
dcmanager subcloud-group list
dcmanager subcloud-group delete group01
dcmanager subcloud-backup create --subcloud subcloud1

On test plan, these commands are reffered as "test commands".

The access control is not implemented for "dcdbsync" and "dcorch"
servers. Also, it is also not implemented for action POST
"/v1.0/notifications" in dcmanager API server, as it it is only called
indirectly by sysinv controllers.

Test Plan:

PASS: Successfully deploy a Distributed Cloud (with 1 subcloud) using a
CentOS image with this commit present. Successfully create, through
openstack CLI, the users: 'testreader' with role 'reader' in project
'admin', 'adminsvc' with role 'admin' in project 'services' and
'otheradmin' with role 'admin' in project 'notadminproject'. Create
openrc files for all new users. Note: the other user used is the already
existing 'admin' with role 'admin' in project 'admin'.
PASS: In the deployed DC, check the behavior of test commands through
different users: for "admin" and "adminsvc" users, all commands are
successful; for "testreader" user, only the test commands ending with
"list" or "summary" (GET requests) are successful; for "otheradmin"
user, all commands fail.
PASS: In the deployed DC, to assert that public API works without
authentication, execute the command "curl -v http://<MGMT_IP>:8119/" and
verify that it is accepted and that the HTTP response is 200, and
execute the command "curl -v http://<MGMT_IP>:8119/v1.0/subclouds" and
verify that it is rejected and that the HTTP response is 401.
PASS: In the deployed DC, check through Horizon interface that DC
management works correctly with default admin user.

Story: 2010149
Task: 46287

Signed-off-by: Joao Victor Portal <Joao.VictorPortal@windriver.com>
Change-Id: Icfe24fd62096c7bf0bbb1f97e819dee5aac675e4
2022-09-22 18:26:35 -03:00

151 lines
5.3 KiB
Python

# Copyright (c) 2020-2022 Wind River Systems, Inc.
# 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 pecan
from pecan import hooks
from oslo_context import context as base_context
from oslo_utils import encodeutils
from dcorch.api.policies import base as base_policy
from dcorch.api import policy
from dcorch.db import api as db_api
ALLOWED_WITHOUT_AUTH = '/'
class RequestContext(base_context.RequestContext):
"""Stores information about the security context.
The context encapsulates information related to the user accessing the
the system, as well as additional request information.
"""
def __init__(self, auth_token=None, user=None, project=None,
domain=None, user_domain=None, project_domain=None,
is_admin=None, read_only=False, show_deleted=False,
request_id=None, auth_url=None, trusts=None,
user_name=None, project_name=None, domain_name=None,
user_domain_name=None, project_domain_name=None,
auth_token_info=None, region_name=None, roles=None,
password=None, **kwargs):
"""Initializer of request context."""
# We still have 'tenant' param because oslo_context still use it.
# pylint: disable=E1123
super(RequestContext, self).__init__(
auth_token=auth_token, user=user, tenant=project,
domain=domain, user_domain=user_domain,
project_domain=project_domain, roles=roles,
read_only=read_only, show_deleted=show_deleted,
request_id=request_id)
# request_id might be a byte array
self.request_id = encodeutils.safe_decode(self.request_id)
# we save an additional 'project' internally for use
self.project = project
# Session for DB access
self._session = None
self.auth_url = auth_url
self.trusts = trusts
self.user_name = user_name
self.project_name = project_name
self.domain_name = domain_name
self.user_domain_name = user_domain_name
self.project_domain_name = project_domain_name
self.auth_token_info = auth_token_info
self.region_name = region_name
self.roles = roles or []
self.password = password
# Check user is admin or not
if is_admin is None:
self.is_admin = policy.authorize(
base_policy.ADMIN_IN_SYSTEM_PROJECTS, {}, self.to_dict(),
do_raise=False)
else:
self.is_admin = is_admin
@property
def session(self):
if self._session is None:
self._session = db_api.get_session()
return self._session
def to_dict(self):
return {
'auth_url': self.auth_url,
'auth_token': self.auth_token,
'auth_token_info': self.auth_token_info,
'user': self.user,
'user_name': self.user_name,
'user_domain': self.user_domain,
'user_domain_name': self.user_domain_name,
'project': self.project,
'project_name': self.project_name,
'project_domain': self.project_domain,
'project_domain_name': self.project_domain_name,
'domain': self.domain,
'domain_name': self.domain_name,
'trusts': self.trusts,
'region_name': self.region_name,
'roles': self.roles,
'show_deleted': self.show_deleted,
'is_admin': self.is_admin,
'request_id': self.request_id,
'password': self.password,
}
@classmethod
def from_dict(cls, values):
return cls(**values)
def get_admin_context(show_deleted=False):
return RequestContext(is_admin=True, show_deleted=show_deleted)
def get_service_context(**args):
"""An abstraction layer for getting service context.
There could be multiple cloud backends for dcorch-engine to use. This
abstraction layer provides an indirection for dcorch-engine to get the
credentials of 'dcorch' user on the specific cloud. By default,
this credential refers to the credentials built for keystone middleware
in an OpenStack cloud.
"""
pass
class AuthHook(hooks.PecanHook):
def before(self, state):
if state.request.path == ALLOWED_WITHOUT_AUTH:
return
req = state.request
identity_status = req.headers.get('X-Identity-Status')
service_identity_status = req.headers.get('X-Service-Identity-Status')
if (identity_status == 'Confirmed' or
service_identity_status == 'Confirmed'):
return
if req.headers.get('X-Auth-Token'):
msg = 'Auth token is invalid: %s' % req.headers['X-Auth-Token']
else:
msg = 'Authentication required'
msg = "Failed to validate access token: %s" % str(msg)
pecan.abort(status_code=401, detail=msg)