Implement access control for FM API
This commit implements the access control for all FM APIs. An incomplete list of FM APIs can be found at "https://docs.starlingx.io/api-ref/fault/api-ref-fm-v1-fault.html". Unit tests will be created in other task. All access control rules can be overwritten through file "/etc/fm/policy.yaml". Any change in file "/etc/fm/policy.yaml" is automatically detected by policy engine and the rules are updated. Differently from other APIs, which have as default rule to enforce that all users using the API are present in either project "admin" or "services", all read-only actions (GET requests) of FM API are allowed for any user, so it only requires "reader" role (that is the lowest role). Other actions require the user to have "admin" role and to be present in either project "admin" or "services". As all system users of StarlingX have "admin" role and are present in either project "admin" or "services", the default rules for FM API allows any system users to execute any action, so there should be no regression with the change introduced here. To test the access control of FM API, the following commands will be used: fm alarm-list fm alarm-show <uuid> fm alarm-summary fm alarm-delete <uuid> fm event-list fm event-show <uuid> fm event-suppress --alarm_id <alarm_id> fm event-suppress-list fm event-unsuppress --alarm_id <alarm_id> fm event-unsuppress-all On test plan, these commands will be reffered as "test commands". Note: there is one FM API that is not tested by the commands above, that is the creation of alarms ("fm_api:alarm:create"). This API will be tested indirectly by observing the system successfully creating alarms in the deployed environment. Test Plan: PASS: Successfully deploy an AIO-SX using an Debian 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 that will be used is the already existing 'admin' with role 'admin' in project 'admin'. PASS: In the deployed AIO-SX, check the behavior of test commands through different users: for "admin" and "adminsvc" users, all commands are successful; for users "testreader" and "otheradmin", only the commands "alarm-delete", "event-suppress", "event-unsuppress" and "event-unsuppress-all" fail. Observe also that the system is able to create alarms during its operation. PASS: In the deployed AIO-SX, add the following lines in file "/etc/fm/policy.yaml": fm_api:alarm:create: role:admin fm_api:alarm:delete: role:admin fm_api:alarm:get: role:admin fm_api:alarm:modify: role:admin fm_api:event_log:get: role:admin fm_api:event_suppression:get: role:admin fm_api:event_suppression:modify: role:admin and check that all test commands are successful through user "otheradmin" and that all test commands fail through user "testreader". Observe also that the system is able to create alarms during its operation. PASS: In the deployed AIO-SX, to assert that public API works without authentication, execute the commands: "curl -v http://<MGMT_IP>:18002/" and "curl -v http://<MGMT_IP>:18002/v1/" and verify that they are accepted and that the HTTP response is 200, and execute the commands: "curl -v http://<MGMT_IP>:18002/v1/alarms" and "curl -v http://<MGMT_IP>:18002/v1/event_log" and verify that they are rejected and that the HTTP response is 401. PASS: In the deployed AIO-SX, check through Horizon interface that Fault Management works correctly (showing alarms and events, allowing events to be suppressed). PASS: Repeat all tests above changing the deploy to AIO-DX using an CentOS image. Story: 2010149 Task: 46123 Signed-off-by: Joao Victor Portal <Joao.VictorPortal@windriver.com> Change-Id: I3db6d0464d8d53c4dfbc761663be1712141b8b93
This commit is contained in:
parent
571b0665ae
commit
99eba3afb8
@ -2,7 +2,9 @@
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright (C) 2019 Intel Corporation
|
||||
# Copyright (c) 2019 Intel Corporation
|
||||
#
|
||||
# Copyright (c) 2022 Wind River Systems, Inc.
|
||||
#
|
||||
# lib/fault
|
||||
# Functions to control the configuration and operation of the **fault** service
|
||||
@ -37,6 +39,7 @@ GITDIR["fm-core"]=$STX_FAULT_DIR/fm-common/sources
|
||||
FM_RESTAPI_CONF=$STX_FAULT_CONF_DIR/fm.conf
|
||||
FM_RESTAPI_PASTE_INI=$STX_FAULT_CONF_DIR/api-paste.ini
|
||||
FM_EVENT_YAML=$STX_FAULT_CONF_DIR/events.yaml
|
||||
FM_POLICY_YAML=$STX_FAULT_CONF_DIR/policy.yaml
|
||||
FM_RESTAPI_AUTH_CACHE_DIR=${FM_RESTAPI_AUTH_CACHE_DIR:-/var/cache/fault}
|
||||
|
||||
FM_RESTAPI_DIR=$STX_FAULT_DIR/fm-rest-api/fm
|
||||
@ -191,7 +194,7 @@ function cleanup_fm_mgr {
|
||||
function cleanup_fm_rest_api {
|
||||
sudo pip uninstall -y fm
|
||||
|
||||
sudo rm -rf $FM_RESTAPI_AUTH_CACHE_DIR $FM_RESTAPI_CONF $FM_RESTAPI_PASTE_INI $FM_EVENT_YAML
|
||||
sudo rm -rf $FM_RESTAPI_AUTH_CACHE_DIR $FM_RESTAPI_CONF $FM_RESTAPI_PASTE_INI $FM_EVENT_YAML $FM_POLICY_YAML
|
||||
dropdb -h 127.0.0.1 -Uroot fm
|
||||
}
|
||||
|
||||
@ -208,6 +211,7 @@ function configure_fm_rest_api {
|
||||
|
||||
cp -p $STX_FAULT_DIR/devstack/files/api-paste.ini $FM_RESTAPI_PASTE_INI
|
||||
cp -p $STX_FAULT_DIR/fm-doc/fm_doc/events.yaml $FM_EVENT_YAML
|
||||
cp -p $STX_FAULT_DIR/fm-rest-api/fm/fm/policy.yaml $FM_POLICY_YAML
|
||||
|
||||
configure_auth_token_middleware $FM_RESTAPI_CONF fm $FM_RESTAPI_AUTH_CACHE_DIR
|
||||
|
||||
|
@ -69,6 +69,7 @@ install -p -D -m 644 fm-api-pmond.conf %{buildroot}%{local_etc_pmond}/fm-api.con
|
||||
# install default config files
|
||||
cd %{_builddir}/%{name}-%{version} && oslo-config-generator --config-file fm/config-generator.conf --output-file %{_builddir}/%{name}-%{version}/fm.conf.sample
|
||||
install -p -D -m 600 %{_builddir}/%{name}-%{version}/fm.conf.sample %{buildroot}%{_sysconfdir}/fm/fm.conf
|
||||
install -p -D -m 600 fm/policy.yaml %{buildroot}%{_sysconfdir}/fm/policy.yaml
|
||||
|
||||
%clean
|
||||
echo "CLEAN CALLED"
|
||||
@ -90,6 +91,7 @@ rm -rf $RPM_BUILD_ROOT
|
||||
%{pythonroot}/fm-%{version}*.egg-info
|
||||
|
||||
%config(noreplace) %attr(600,fm,fm)%{_sysconfdir}/fm/fm.conf
|
||||
%config(noreplace) %attr(600,fm,fm)%{_sysconfdir}/fm/policy.yaml
|
||||
|
||||
# systemctl service files
|
||||
%{_unitdir}/fm-api.service
|
||||
|
@ -1,4 +1,5 @@
|
||||
debian/systemd/00-fm-rest-api.preset etc/systemd/system-preset
|
||||
etc/fm/fm.conf
|
||||
etc/fm/policy.yaml
|
||||
etc/init.d
|
||||
etc/pmon.d/fm-api.conf
|
||||
|
@ -3,5 +3,6 @@
|
||||
set -e
|
||||
|
||||
chown fm:fm /etc/fm/fm.conf
|
||||
chown fm:fm /etc/fm/policy.yaml
|
||||
|
||||
#DEBHELPER#
|
||||
|
@ -18,11 +18,12 @@ override_dh_auto_install:
|
||||
oslo-config-generator --config-file fm/config-generator.conf --output-file fm.conf.sample
|
||||
install -d -m 755 $(FMCONFDIR)
|
||||
install -p -D -m 600 fm.conf.sample $(FMCONFDIR)/fm.conf
|
||||
install -p -D -m 600 fm/policy.yaml $(FMCONFDIR)/policy.yaml
|
||||
dh_auto_install
|
||||
|
||||
|
||||
override_dh_fixperms:
|
||||
dh_fixperms -Xfm.conf
|
||||
dh_fixperms -Xfm.conf -Xpolicy.yaml
|
||||
|
||||
override_dh_installsystemd:
|
||||
dh_installsystemd --no-enable --name fm-api
|
||||
|
@ -10,7 +10,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Copyright (c) 2018 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018-2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -23,6 +23,7 @@ from oslo_log import log
|
||||
import pecan
|
||||
|
||||
from fm.api import config
|
||||
from fm.api import hooks
|
||||
from fm.api import middleware
|
||||
from fm.common import policy
|
||||
from fm.common.i18n import _
|
||||
@ -48,6 +49,8 @@ def setup_app(config=None):
|
||||
|
||||
pecan.configuration.set_config(dict(config), overwrite=True)
|
||||
app_conf = dict(config.app)
|
||||
if app_conf['enable_acl']:
|
||||
app_conf['hooks'].append(hooks.AccessPolicyHook())
|
||||
|
||||
app = pecan.make_app(
|
||||
app_conf.pop('root'),
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2018, 2022 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018-2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -45,10 +45,7 @@ app = {
|
||||
hooks.DBHook(),
|
||||
hooks.AuditLogging(),
|
||||
],
|
||||
'acl_public_routes': [
|
||||
'/',
|
||||
'/v1',
|
||||
],
|
||||
'enable_acl': True
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2018-2021 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018-2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -25,7 +25,9 @@ from fm.api.controllers.v1 import types
|
||||
from fm.api.controllers.v1 import utils as api_utils
|
||||
from fm.common import exceptions
|
||||
from fm.common import constants
|
||||
from fm.common import policy
|
||||
from fm import objects
|
||||
from fm.api.policies import alarm as alarm_policy
|
||||
from fm.api.controllers.v1.query import Query
|
||||
|
||||
from fm_api import constants as fm_constants
|
||||
@ -438,3 +440,21 @@ class AlarmController(rest.RestController):
|
||||
return err
|
||||
alarm_dict = alm.as_dict()
|
||||
return json.dumps({"uuid": alarm_dict['uuid']})
|
||||
|
||||
def enforce_policy(self, method_name, request):
|
||||
"""Check policy rules for each action of this controller."""
|
||||
context_dict = request.context.to_dict()
|
||||
if method_name == "delete":
|
||||
policy.authorize(alarm_policy.POLICY_ROOT % "delete", {},
|
||||
context_dict)
|
||||
elif method_name in ["detail", "get_all", "get_one", "summary"]:
|
||||
policy.authorize(alarm_policy.POLICY_ROOT % "get", {},
|
||||
context_dict)
|
||||
elif method_name == "post":
|
||||
policy.authorize(alarm_policy.POLICY_ROOT % "create", {},
|
||||
context_dict)
|
||||
elif method_name == "put":
|
||||
policy.authorize(alarm_policy.POLICY_ROOT % "modify", {},
|
||||
context_dict)
|
||||
else:
|
||||
raise exceptions.PolicyNotFound()
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2018-2019 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018-2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -23,7 +23,9 @@ from fm.api.controllers.v1 import collection
|
||||
from fm.api.controllers.v1 import link
|
||||
from fm.api.controllers.v1.query import Query
|
||||
from fm.api.controllers.v1 import types
|
||||
from fm.api.policies import event_log as event_log_policy
|
||||
from fm.common import exceptions
|
||||
from fm.common import policy
|
||||
from fm.common.i18n import _
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
@ -292,3 +294,12 @@ class EventLogController(rest.RestController):
|
||||
pecan.request.context, id)
|
||||
|
||||
return EventLog.convert_with_links(rpc_ilog)
|
||||
|
||||
def enforce_policy(self, method_name, request):
|
||||
"""Check policy rules for each action of this controller."""
|
||||
context_dict = request.context.to_dict()
|
||||
if method_name in ["detail", "get_all", "get_one"]:
|
||||
policy.authorize(event_log_policy.POLICY_ROOT % "get", {},
|
||||
context_dict)
|
||||
else:
|
||||
raise exceptions.PolicyNotFound()
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2018 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018-2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -20,7 +20,10 @@ from fm.api.controllers.v1 import link
|
||||
from fm.api.controllers.v1.query import Query
|
||||
from fm.api.controllers.v1 import types
|
||||
from fm.api.controllers.v1 import utils as api_utils
|
||||
from fm.api.policies import event_suppression as event_suppression_policy
|
||||
from fm.common import constants
|
||||
from fm.common import exceptions
|
||||
from fm.common import policy
|
||||
from fm.common import utils as cutils
|
||||
from fm.common.i18n import _
|
||||
|
||||
@ -213,3 +216,15 @@ class EventSuppressionController(rest.RestController):
|
||||
pecan.request.dbapi.event_suppression_update(uuid, updates)
|
||||
|
||||
return EventSuppression.convert_with_links(updated_event_suppression)
|
||||
|
||||
def enforce_policy(self, method_name, request):
|
||||
"""Check policy rules for each action of this controller."""
|
||||
context_dict = request.context.to_dict()
|
||||
if method_name in ["get_all", "get_one"]:
|
||||
policy.authorize(event_suppression_policy.POLICY_ROOT % "get",
|
||||
{}, context_dict)
|
||||
elif method_name == "patch":
|
||||
policy.authorize(event_suppression_policy.POLICY_ROOT % "modify",
|
||||
{}, context_dict)
|
||||
else:
|
||||
raise exceptions.PolicyNotFound()
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2018, 2022 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018-2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -13,6 +13,7 @@ from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import uuidutils
|
||||
from webob import exc
|
||||
|
||||
from fm.common import context
|
||||
from fm.db import api as dbapi
|
||||
@ -59,7 +60,7 @@ class ContextHook(hooks.PecanHook):
|
||||
environ = state.request.environ
|
||||
user_name = headers.get('X-User-Name')
|
||||
user_id = headers.get('X-User-Id')
|
||||
project = headers.get('X-Project-Name')
|
||||
project_name = headers.get('X-Project-Name')
|
||||
project_id = headers.get('X-Project-Id')
|
||||
domain_id = headers.get('X-User-Domain-Id')
|
||||
domain_name = headers.get('X-User-Domain-Name')
|
||||
@ -83,7 +84,7 @@ class ContextHook(hooks.PecanHook):
|
||||
auth_token_info=auth_token_info,
|
||||
user_name=user_name,
|
||||
user_id=user_id,
|
||||
project_name=project,
|
||||
project_name=project_name,
|
||||
project_id=project_id,
|
||||
domain_id=domain_id,
|
||||
domain_name=domain_name,
|
||||
@ -146,7 +147,10 @@ class AuditLogging(hooks.PecanHook):
|
||||
def json_post_data(rest_state):
|
||||
if 'form-data' in rest_state.request.headers.get('Content-Type'):
|
||||
return " POST: {}".format(rest_state.request.params)
|
||||
if not hasattr(rest_state.request, 'json'):
|
||||
try:
|
||||
if not hasattr(rest_state.request, 'json'):
|
||||
return ""
|
||||
except Exception:
|
||||
return ""
|
||||
return " POST: {}".format(rest_state.request.json)
|
||||
|
||||
@ -195,3 +199,19 @@ class AuditLogging(hooks.PecanHook):
|
||||
|
||||
def on_error(self, state, e):
|
||||
auditLOG.exception("Exception in AuditLogging passed to event 'on_error': " + str(e))
|
||||
|
||||
|
||||
class AccessPolicyHook(hooks.PecanHook):
|
||||
"""Verify that the user has the needed privilege to execute the action."""
|
||||
def before(self, state):
|
||||
is_public_api = state.request.environ.get('is_public_api', False)
|
||||
if not is_public_api:
|
||||
controller = state.controller.__self__
|
||||
if hasattr(controller, 'enforce_policy'):
|
||||
try:
|
||||
controller_method = state.controller.__name__
|
||||
controller.enforce_policy(controller_method, state.request)
|
||||
except Exception:
|
||||
raise exc.HTTPForbidden()
|
||||
else:
|
||||
raise exc.HTTPForbidden()
|
||||
|
21
fm-rest-api/fm/fm/api/policies/__init__.py
Normal file
21
fm-rest-api/fm/fm/api/policies/__init__.py
Normal file
@ -0,0 +1,21 @@
|
||||
#
|
||||
# Copyright (c) 2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
import itertools
|
||||
|
||||
from fm.api.policies import base
|
||||
from fm.api.policies import alarm
|
||||
from fm.api.policies import event_log
|
||||
from fm.api.policies import event_suppression
|
||||
|
||||
|
||||
def list_rules():
|
||||
return itertools.chain(
|
||||
base.list_rules(),
|
||||
alarm.list_rules(),
|
||||
event_log.list_rules(),
|
||||
event_suppression.list_rules()
|
||||
)
|
74
fm-rest-api/fm/fm/api/policies/alarm.py
Normal file
74
fm-rest-api/fm/fm/api/policies/alarm.py
Normal file
@ -0,0 +1,74 @@
|
||||
#
|
||||
# Copyright (c) 2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
from oslo_policy import policy
|
||||
from fm.api.policies import base
|
||||
|
||||
POLICY_ROOT = 'fm_api:alarm:%s'
|
||||
|
||||
|
||||
alarm_rules = [
|
||||
policy.DocumentedRuleDefault(
|
||||
name=POLICY_ROOT % 'create',
|
||||
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
|
||||
description="Create an alarm.",
|
||||
operations=[
|
||||
{
|
||||
'method': 'POST',
|
||||
'path': '/v1/alarms'
|
||||
}
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=POLICY_ROOT % 'delete',
|
||||
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
|
||||
description="Delete an alarm.",
|
||||
operations=[
|
||||
{
|
||||
'method': 'DELETE',
|
||||
'path': '/v1/alarms/{alarm_uuid}'
|
||||
}
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=POLICY_ROOT % 'get',
|
||||
check_str='role:reader',
|
||||
description="Get alarms.",
|
||||
operations=[
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/v1/alarms'
|
||||
},
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/v1/alarms/{alarm_uuid}'
|
||||
},
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/v1/alarms/detail'
|
||||
},
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/v1/alarms/summary'
|
||||
}
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=POLICY_ROOT % 'modify',
|
||||
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
|
||||
description="Modify an alarm.",
|
||||
operations=[
|
||||
{
|
||||
'method': 'PUT',
|
||||
'path': '/v1/alarms'
|
||||
}
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return alarm_rules
|
30
fm-rest-api/fm/fm/api/policies/base.py
Normal file
30
fm-rest-api/fm/fm/api/policies/base.py
Normal file
@ -0,0 +1,30 @@
|
||||
#
|
||||
# Copyright (c) 2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
from oslo_policy import policy
|
||||
|
||||
ADMIN_IN_SYSTEM_PROJECTS = 'admin_in_system_projects'
|
||||
READER_IN_SYSTEM_PROJECTS = 'reader_in_system_projects'
|
||||
|
||||
|
||||
base_rules = [
|
||||
policy.RuleDefault(
|
||||
name=ADMIN_IN_SYSTEM_PROJECTS,
|
||||
check_str='role:admin and (project_name:admin or ' +
|
||||
'project_name:services)',
|
||||
description="Base rule.",
|
||||
),
|
||||
policy.RuleDefault(
|
||||
name=READER_IN_SYSTEM_PROJECTS,
|
||||
check_str='role:reader and (project_name:admin or ' +
|
||||
'project_name:services)',
|
||||
description="Base rule."
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return base_rules
|
36
fm-rest-api/fm/fm/api/policies/event_log.py
Normal file
36
fm-rest-api/fm/fm/api/policies/event_log.py
Normal file
@ -0,0 +1,36 @@
|
||||
#
|
||||
# Copyright (c) 2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
from oslo_policy import policy
|
||||
|
||||
POLICY_ROOT = 'fm_api:event_log:%s'
|
||||
|
||||
|
||||
event_log_rules = [
|
||||
policy.DocumentedRuleDefault(
|
||||
name=POLICY_ROOT % 'get',
|
||||
check_str='role:reader',
|
||||
description="Get event logs.",
|
||||
operations=[
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/v1/event_log'
|
||||
},
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/v1/event_log/{log_uuid}'
|
||||
},
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/v1/event_log/detail'
|
||||
}
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return event_log_rules
|
44
fm-rest-api/fm/fm/api/policies/event_suppression.py
Normal file
44
fm-rest-api/fm/fm/api/policies/event_suppression.py
Normal file
@ -0,0 +1,44 @@
|
||||
#
|
||||
# Copyright (c) 2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
from oslo_policy import policy
|
||||
from fm.api.policies import base
|
||||
|
||||
POLICY_ROOT = 'fm_api:event_suppression:%s'
|
||||
|
||||
|
||||
event_suppression_rules = [
|
||||
policy.DocumentedRuleDefault(
|
||||
name=POLICY_ROOT % 'get',
|
||||
check_str='role:reader',
|
||||
description="Get event suppressions.",
|
||||
operations=[
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/v1/event_suppression'
|
||||
},
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/v1/event_suppression/{event_suppression_uuid}'
|
||||
}
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=POLICY_ROOT % 'modify',
|
||||
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
|
||||
description="Modify the value of an event suppression.",
|
||||
operations=[
|
||||
{
|
||||
'method': 'PATCH',
|
||||
'path': '/v1/event_suppression/{event_suppression_uuid}'
|
||||
}
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return event_suppression_rules
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2018 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018-2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -9,6 +9,7 @@ from oslo_config import cfg
|
||||
from keystoneauth1 import plugin
|
||||
from keystoneauth1.access import service_catalog as k_service_catalog
|
||||
|
||||
from fm.api.policies import base as base_policy
|
||||
from fm.common import policy
|
||||
|
||||
|
||||
@ -95,7 +96,9 @@ class RequestContext(context.RequestContext):
|
||||
|
||||
self.user_auth_plugin = user_auth_plugin
|
||||
if is_admin is None:
|
||||
self.is_admin = policy.check_is_admin(self)
|
||||
self.is_admin = policy.authorize(
|
||||
base_policy.ADMIN_IN_SYSTEM_PROJECTS, {}, self.to_dict(),
|
||||
do_raise=False)
|
||||
else:
|
||||
self.is_admin = is_admin
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2018 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018-2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -104,6 +104,10 @@ class PolicyNotAuthorized(ApiError):
|
||||
code = webob.exc.HTTPUnauthorized.code
|
||||
|
||||
|
||||
class PolicyNotFound(Invalid):
|
||||
message = _("Policy not found for requested action.")
|
||||
|
||||
|
||||
class Conflict(ApiError):
|
||||
message = _('HTTP Conflict.')
|
||||
# 409 - HTTPConflict
|
||||
|
@ -13,77 +13,47 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Copyright (c) 2018 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018-2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
"""Policy Engine For FM."""
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from oslo_policy import policy
|
||||
from oslo_log import log
|
||||
from fm.api import policies as controller_policies
|
||||
|
||||
|
||||
base_rules = [
|
||||
policy.RuleDefault('admin_required', 'role:admin or is_admin:1',
|
||||
description='Who is considered an admin'),
|
||||
policy.RuleDefault('admin_api', 'is_admin_required:True',
|
||||
description='admin API requirement'),
|
||||
policy.RuleDefault('default', 'rule:admin_api',
|
||||
description='default rule'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
_ENFORCER = None
|
||||
|
||||
|
||||
# we can get a policy enforcer by this init.
|
||||
# oslo policy support change policy rule dynamically.
|
||||
# at present, policy.enforce will reload the policy rules when it checks
|
||||
# the policy files have been touched.
|
||||
def init(policy_file=None, rules=None,
|
||||
default_rule=None, use_conf=True, overwrite=True):
|
||||
def reset():
|
||||
"""Discard current Enforcer object."""
|
||||
global _ENFORCER
|
||||
_ENFORCER = None
|
||||
|
||||
|
||||
def init(policy_file='policy.yaml'):
|
||||
"""Init an Enforcer class.
|
||||
|
||||
:param policy_file: Custom policy file to use, if none is
|
||||
specified, ``conf.policy_file`` will be
|
||||
used.
|
||||
:param rules: Default dictionary / Rules to use. It will be
|
||||
considered just in the first instantiation. If
|
||||
:meth:`load_rules` with ``force_reload=True``,
|
||||
:meth:`clear` or :meth:`set_rules` with
|
||||
``overwrite=True`` is called this will be overwritten.
|
||||
:param default_rule: Default rule to use, conf.default_rule will
|
||||
be used if none is specified.
|
||||
:param use_conf: Whether to load rules from cache or config file.
|
||||
:param overwrite: Whether to overwrite existing rules when reload rules
|
||||
from config file.
|
||||
:param policy_file: Custom policy file to be used.
|
||||
|
||||
:return: Returns a Enforcer instance.
|
||||
"""
|
||||
global _ENFORCER
|
||||
if not _ENFORCER:
|
||||
# https://docs.openstack.org/oslo.policy/latest/user/usage.html
|
||||
_ENFORCER = policy.Enforcer(CONF,
|
||||
policy_file=policy_file,
|
||||
rules=rules,
|
||||
default_rule=default_rule,
|
||||
use_conf=use_conf,
|
||||
overwrite=overwrite)
|
||||
_ENFORCER.register_defaults(base_rules)
|
||||
default_rule='default',
|
||||
use_conf=True,
|
||||
overwrite=True)
|
||||
_ENFORCER.register_defaults(controller_policies.list_rules())
|
||||
return _ENFORCER
|
||||
|
||||
|
||||
def check_is_admin(context):
|
||||
"""Whether or not role contains 'admin' role according to policy setting.
|
||||
|
||||
"""
|
||||
def authorize(rule, target, creds, do_raise=True):
|
||||
"""A wrapper around 'authorize' from 'oslo_policy.policy'."""
|
||||
init()
|
||||
|
||||
target = {}
|
||||
credentials = context.to_dict()
|
||||
|
||||
return _ENFORCER.enforce('context_is_admin', target, credentials)
|
||||
return _ENFORCER.authorize(rule, target, creds, do_raise=do_raise)
|
||||
|
16
fm-rest-api/fm/fm/policy.yaml
Normal file
16
fm-rest-api/fm/fm/policy.yaml
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
# The commented lines below contains the default values for presented rules.
|
||||
|
||||
# admin_in_system_projects: role:admin and (project_name:admin or project_name:services)
|
||||
# reader_in_system_projects: role:reader and (project_name:admin or project_name:services)
|
||||
|
||||
# fm_api:alarm:create: rule:admin_in_system_projects
|
||||
# fm_api:alarm:delete: rule:admin_in_system_projects
|
||||
# fm_api:alarm:get: role:reader
|
||||
# fm_api:alarm:modify: rule:admin_in_system_projects
|
||||
|
||||
# fm_api:event_log:get: role:reader
|
||||
|
||||
# fm_api:event_suppression:get: role:reader
|
||||
# fm_api:event_suppression:modify: rule:admin_in_system_projects
|
||||
|
@ -12,6 +12,11 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Copyright (c) 2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
"""Base classes for API tests."""
|
||||
|
||||
from oslo_config import cfg
|
||||
@ -45,7 +50,7 @@ class FunctionalTest(base.TestCase):
|
||||
'app': {
|
||||
'root': 'fm.api.controllers.root.RootController',
|
||||
'modules': ['fm.api'],
|
||||
'acl_public_routes': ['/', '/v1'],
|
||||
'enable_acl': False
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -79,6 +79,7 @@ install -m 640 fm/db/sqlalchemy/migrate_repo/migrate.cfg %{buildroot}%{pythonroo
|
||||
# install default config files
|
||||
oslo-config-generator --config-file fm/config-generator.conf --output-file %{_builddir}/fm.conf.sample
|
||||
install -p -D -m 644 %{_builddir}/fm.conf.sample %{buildroot}%{_sysconfdir}/fm/fm.conf
|
||||
install -p -D -m 600 fm/policy.yaml %{buildroot}%{_sysconfdir}/fm/policy.yaml
|
||||
|
||||
%fdupes %{buildroot}%{pythonroot}/fm
|
||||
|
||||
@ -99,6 +100,7 @@ install -p -D -m 644 %{_builddir}/fm.conf.sample %{buildroot}%{_sysconfdir}/fm/f
|
||||
|
||||
%dir %{_sysconfdir}/fm
|
||||
%config(noreplace) %{_sysconfdir}/fm/fm.conf
|
||||
%config(noreplace) %attr(600,fm,fm)%{_sysconfdir}/fm/policy.yaml
|
||||
|
||||
# systemctl service files
|
||||
%{_unitdir}/fm-api.service
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2018 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018-2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -96,10 +96,10 @@ def event_suppression_update(cc, data, suppress=False):
|
||||
patch = []
|
||||
for event_id in event_suppression_list:
|
||||
if event_id.alarm_id in alarm_id_list:
|
||||
print("Alarm ID: {} {}.".format(event_id.alarm_id, patch_value))
|
||||
uuid = event_id.uuid
|
||||
patch.append(dict(path='/' + 'suppression_status', value=patch_value, op='replace'))
|
||||
cc.event_suppression.update(uuid, patch)
|
||||
print("Alarm ID: {} {}.".format(event_id.alarm_id, patch_value))
|
||||
|
||||
|
||||
@utils.arg('--include-unsuppressed', action='store_true',
|
||||
@ -196,8 +196,8 @@ def do_event_unsuppress_all(cc, args):
|
||||
if suppression_status == 'suppressed':
|
||||
uuid = alarm_type.uuid
|
||||
patch.append(dict(path='/' + 'suppression_status', value='unsuppressed', op='replace'))
|
||||
print("Alarm ID: {} unsuppressed.".format(alarm_type.alarm_id))
|
||||
cc.event_suppression.update(uuid, patch)
|
||||
print("Alarm ID: {} unsuppressed.".format(alarm_type.alarm_id))
|
||||
|
||||
no_paging = args.nopaging
|
||||
includeUUID = args.uuid
|
||||
|
Loading…
Reference in New Issue
Block a user