From 0f0754dab8797d41a938257f3568800ea5f1a9f3 Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Tue, 28 Jan 2025 13:00:40 -0800 Subject: [PATCH] Add secure RBAC role as new default This add new RBAC defaults in the cloukitty API policy. There is no change in the admin policy except they are scoped to the 'project'. Adding project reader role in the read APIs which continue to be allow by the member and admin role. Change-Id: Ia693a50210a850626adcd9daab1736335ae2b015 --- cloudkitty/common/policies/base.py | 45 ++++++++++++++++++- cloudkitty/common/policies/v1/collector.py | 15 ++++--- cloudkitty/common/policies/v1/info.py | 15 ++++--- cloudkitty/common/policies/v1/rating.py | 15 ++++--- cloudkitty/common/policies/v1/report.py | 13 +++--- cloudkitty/common/policies/v1/storage.py | 5 ++- cloudkitty/common/policies/v2/dataframes.py | 8 ++-- cloudkitty/common/policies/v2/rating.py | 9 ++-- cloudkitty/common/policies/v2/scope.py | 12 +++-- cloudkitty/common/policies/v2/summary.py | 5 ++- cloudkitty/common/policies/v2/tasks.py | 6 ++- ...secure-rbac-defaults-5bb903323634a94c.yaml | 17 +++++++ 12 files changed, 127 insertions(+), 38 deletions(-) create mode 100644 releasenotes/notes/secure-rbac-defaults-5bb903323634a94c.yaml diff --git a/cloudkitty/common/policies/base.py b/cloudkitty/common/policies/base.py index 0fe48476..9866afe2 100644 --- a/cloudkitty/common/policies/base.py +++ b/cloudkitty/common/policies/base.py @@ -19,6 +19,24 @@ RULE_ADMIN_OR_OWNER = 'rule:admin_or_owner' ROLE_ADMIN = 'role:admin' UNPROTECTED = '' +DEPRECATED_REASON = """ +CloudKitty API policies are introducing new default roles with scope_type +capabilities. Old policies are deprecated and silently going to be ignored +in future release. +""" + +DEPRECATED_ADMIN_OR_OWNER_POLICY = policy.DeprecatedRule( + name=RULE_ADMIN_OR_OWNER, + check_str='is_admin:True or ' + '(role:admin and is_admin_project:True) or ' + 'project_id:%(project_id)s', + deprecated_reason=DEPRECATED_REASON, + deprecated_since='22.0.0' +) + +PROJECT_MEMBER_OR_ADMIN = 'rule:project_member_or_admin' +PROJECT_READER_OR_ADMIN = 'rule:project_reader_or_admin' + rules = [ policy.RuleDefault( name='context_is_admin', @@ -27,10 +45,33 @@ rules = [ name='admin_or_owner', check_str='is_admin:True or ' '(role:admin and is_admin_project:True) or ' - 'project_id:%(project_id)s'), + 'project_id:%(project_id)s', + deprecated_for_removal=True, + deprecated_reason=DEPRECATED_REASON, + deprecated_since='22.0.0'), policy.RuleDefault( name='default', - check_str=UNPROTECTED) + check_str=UNPROTECTED), + policy.RuleDefault( + "project_member_api", + "role:member and project_id:%(project_id)s", + "Default rule for Project level non admin APIs.", + deprecated_rule=DEPRECATED_ADMIN_OR_OWNER_POLICY), + policy.RuleDefault( + "project_reader_api", + "role:reader and project_id:%(project_id)s", + "Default rule for Project level read only APIs.", + deprecated_rule=DEPRECATED_ADMIN_OR_OWNER_POLICY), + policy.RuleDefault( + "project_member_or_admin", + "rule:project_member_api or rule:context_is_admin", + "Default rule for Project Member or admin APIs.", + deprecated_rule=DEPRECATED_ADMIN_OR_OWNER_POLICY), + policy.RuleDefault( + "project_reader_or_admin", + "rule:project_reader_api or rule:context_is_admin", + "Default rule for Project reader or admin APIs.", + deprecated_rule=DEPRECATED_ADMIN_OR_OWNER_POLICY) ] diff --git a/cloudkitty/common/policies/v1/collector.py b/cloudkitty/common/policies/v1/collector.py index afedc6c1..194aa351 100644 --- a/cloudkitty/common/policies/v1/collector.py +++ b/cloudkitty/common/policies/v1/collector.py @@ -23,13 +23,15 @@ collector_policies = [ check_str=base.ROLE_ADMIN, description='Return the list of every services mapped to a collector.', operations=[{'path': '/v1/collector/mappings', - 'method': 'LIST'}]), + 'method': 'LIST'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='collector:get_mapping', check_str=base.ROLE_ADMIN, description='Return a service to collector mapping.', operations=[{'path': '/v1/collector/mappings/{service_id}', - 'method': 'GET'}]), + 'method': 'GET'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='collector:manage_mapping', check_str=base.ROLE_ADMIN, @@ -37,19 +39,22 @@ collector_policies = [ operations=[{'path': '/v1/collector/mappings', 'method': 'POST'}, {'path': '/v1/collector/mappings/{service_id}', - 'method': 'DELETE'}]), + 'method': 'DELETE'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='collector:get_state', check_str=base.ROLE_ADMIN, description='Query the enable state of a collector.', operations=[{'path': '/v1/collector/states/{collector_id}', - 'method': 'GET'}]), + 'method': 'GET'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='collector:update_state', check_str=base.ROLE_ADMIN, description='Set the enable state of a collector.', operations=[{'path': '/v1/collector/states/{collector_id}', - 'method': 'PUT'}]) + 'method': 'PUT'}], + scope_types=['project']) ] diff --git a/cloudkitty/common/policies/v1/info.py b/cloudkitty/common/policies/v1/info.py index 838f088a..42661b74 100644 --- a/cloudkitty/common/policies/v1/info.py +++ b/cloudkitty/common/policies/v1/info.py @@ -23,31 +23,36 @@ info_policies = [ check_str=base.UNPROTECTED, description='List available services information in Cloudkitty.', operations=[{'path': '/v1/info/services', - 'method': 'LIST'}]), + 'method': 'LIST'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='info:get_service_info', check_str=base.UNPROTECTED, description='Get specified service information.', operations=[{'path': '/v1/info/services/{metric_id}', - 'method': 'GET'}]), + 'method': 'GET'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='info:list_metrics_info', check_str=base.UNPROTECTED, description='List available metrics information in Cloudkitty.', operations=[{'path': '/v1/info/metrics', - 'method': 'LIST'}]), + 'method': 'LIST'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='info:get_metric_info', check_str=base.UNPROTECTED, description='Get specified metric information.', operations=[{'path': '/v1/info/metrics/{metric_id}', - 'method': 'GET'}]), + 'method': 'GET'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='info:get_config', check_str=base.UNPROTECTED, description='Get current configuration in Cloudkitty.', operations=[{'path': '/v1/info/config', - 'method': 'GET'}]) + 'method': 'GET'}], + scope_types=['project']) ] diff --git a/cloudkitty/common/policies/v1/rating.py b/cloudkitty/common/policies/v1/rating.py index 3e164e36..a91e1f04 100644 --- a/cloudkitty/common/policies/v1/rating.py +++ b/cloudkitty/common/policies/v1/rating.py @@ -23,32 +23,37 @@ rating_policies = [ check_str=base.ROLE_ADMIN, description='Return the list of loaded modules in Cloudkitty.', operations=[{'path': '/v1/rating/modules', - 'method': 'LIST'}]), + 'method': 'LIST'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='rating:get_module', check_str=base.ROLE_ADMIN, description='Get specified module.', operations=[{'path': '/v1/rating/modules/{module_id}', - 'method': 'GET'}]), + 'method': 'GET'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='rating:update_module', check_str=base.ROLE_ADMIN, description='Change the state and priority of a module.', operations=[{'path': '/v1/rating/modules/{module_id}', - 'method': 'PUT'}]), + 'method': 'PUT'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='rating:quote', check_str=base.UNPROTECTED, description='Get an instant quote based on multiple resource ' 'descriptions.', operations=[{'path': '/v1/rating/quote', - 'method': 'POST'}]), + 'method': 'POST'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='rating:module_config', check_str=base.ROLE_ADMIN, description='Trigger a rating module list reload.', operations=[{'path': '/v1/rating/reload_modules', - 'method': 'GET'}]) + 'method': 'GET'}], + scope_types=['project']) ] diff --git a/cloudkitty/common/policies/v1/report.py b/cloudkitty/common/policies/v1/report.py index ae06dd90..1a65cc6c 100644 --- a/cloudkitty/common/policies/v1/report.py +++ b/cloudkitty/common/policies/v1/report.py @@ -23,19 +23,22 @@ report_policies = [ check_str=base.ROLE_ADMIN, description='Return the list of rated tenants.', operations=[{'path': '/v1/report/tenants', - 'method': 'GET'}]), + 'method': 'GET'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='report:get_summary', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.PROJECT_READER_OR_ADMIN, description='Return the summary to pay for a given period.', operations=[{'path': '/v1/report/summary', - 'method': 'GET'}]), + 'method': 'GET'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='report:get_total', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.PROJECT_READER_OR_ADMIN, description='Return the amount to pay for a given period.', operations=[{'path': '/v1/report/total', - 'method': 'GET'}]) + 'method': 'GET'}], + scope_types=['project']) ] diff --git a/cloudkitty/common/policies/v1/storage.py b/cloudkitty/common/policies/v1/storage.py index 47d92079..afa8e948 100644 --- a/cloudkitty/common/policies/v1/storage.py +++ b/cloudkitty/common/policies/v1/storage.py @@ -20,11 +20,12 @@ from cloudkitty.common.policies import base storage_policies = [ policy.DocumentedRuleDefault( name='storage:list_data_frames', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.PROJECT_READER_OR_ADMIN, description='Return a list of rated resources for a time period ' 'and a tenant.', operations=[{'path': '/v1/storage/dataframes', - 'method': 'GET'}]) + 'method': 'GET'}], + scope_types=['project']) ] diff --git a/cloudkitty/common/policies/v2/dataframes.py b/cloudkitty/common/policies/v2/dataframes.py index 0fcaf540..a17bdc24 100644 --- a/cloudkitty/common/policies/v2/dataframes.py +++ b/cloudkitty/common/policies/v2/dataframes.py @@ -23,13 +23,15 @@ dataframes_policies = [ check_str=base.ROLE_ADMIN, description='Add one or several DataFrames', operations=[{'path': '/v2/dataframes', - 'method': 'POST'}]), + 'method': 'POST'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='dataframes:get', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.PROJECT_READER_OR_ADMIN, description='Get DataFrames', operations=[{'path': '/v2/dataframes', - 'method': 'GET'}]), + 'method': 'GET'}], + scope_types=['project']), ] diff --git a/cloudkitty/common/policies/v2/rating.py b/cloudkitty/common/policies/v2/rating.py index 24ff5dd1..3d3b496b 100644 --- a/cloudkitty/common/policies/v2/rating.py +++ b/cloudkitty/common/policies/v2/rating.py @@ -23,19 +23,22 @@ rating_policies = [ check_str=base.ROLE_ADMIN, description='Returns the list of loaded modules in Cloudkitty.', operations=[{'path': '/v2/rating/modules', - 'method': 'GET'}]), + 'method': 'GET'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='v2_rating:get_module', check_str=base.ROLE_ADMIN, description='Get specified module.', operations=[{'path': '/v2/rating/modules/{module_id}', - 'method': 'GET'}]), + 'method': 'GET'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='v2_rating:update_module', check_str=base.ROLE_ADMIN, description='Change the state and priority of a module.', operations=[{'path': '/v2/rating/modules/{module_id}', - 'method': 'PUT'}]) + 'method': 'PUT'}], + scope_types=['project']) ] diff --git a/cloudkitty/common/policies/v2/scope.py b/cloudkitty/common/policies/v2/scope.py index c2d28eb8..56e9973e 100644 --- a/cloudkitty/common/policies/v2/scope.py +++ b/cloudkitty/common/policies/v2/scope.py @@ -23,25 +23,29 @@ scope_policies = [ check_str=base.ROLE_ADMIN, description='Get the state of one or several scopes', operations=[{'path': '/v2/scope', - 'method': 'GET'}]), + 'method': 'GET'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='scope:reset_state', check_str=base.ROLE_ADMIN, description='Reset the state of one or several scopes', operations=[{'path': '/v2/scope', - 'method': 'PUT'}]), + 'method': 'PUT'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='scope:patch_state', check_str=base.ROLE_ADMIN, description='Enables operators to patch a storage scope', operations=[{'path': '/v2/scope', - 'method': 'PATCH'}]), + 'method': 'PATCH'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='scope:post_state', check_str=base.ROLE_ADMIN, description='Enables operators to create a storage scope', operations=[{'path': '/v2/scope', - 'method': 'POST'}]), + 'method': 'POST'}], + scope_types=['project']), ] diff --git a/cloudkitty/common/policies/v2/summary.py b/cloudkitty/common/policies/v2/summary.py index 74c25166..51109732 100644 --- a/cloudkitty/common/policies/v2/summary.py +++ b/cloudkitty/common/policies/v2/summary.py @@ -19,10 +19,11 @@ from cloudkitty.common.policies import base example_policies = [ policy.DocumentedRuleDefault( name='summary:get_summary', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.PROJECT_READER_OR_ADMIN, description='Get a rating summary', operations=[{'path': '/v2/summary', - 'method': 'GET'}]), + 'method': 'GET'}], + scope_types=['project']), ] diff --git a/cloudkitty/common/policies/v2/tasks.py b/cloudkitty/common/policies/v2/tasks.py index f2d4ee44..4dc37553 100644 --- a/cloudkitty/common/policies/v2/tasks.py +++ b/cloudkitty/common/policies/v2/tasks.py @@ -22,13 +22,15 @@ schedule_policies = [ check_str=base.ROLE_ADMIN, description='Schedule a scope for reprocessing', operations=[{'path': '/v2/task/reprocesses', - 'method': 'POST'}]), + 'method': 'POST'}], + scope_types=['project']), policy.DocumentedRuleDefault( name='schedule:get_task_reprocesses', check_str=base.ROLE_ADMIN, description='Get reprocessing schedule tasks for scopes.', operations=[{'path': '/v2/task/reprocesses', - 'method': 'GET'}]), + 'method': 'GET'}], + scope_types=['project']), ] diff --git a/releasenotes/notes/secure-rbac-defaults-5bb903323634a94c.yaml b/releasenotes/notes/secure-rbac-defaults-5bb903323634a94c.yaml new file mode 100644 index 00000000..3899f0c0 --- /dev/null +++ b/releasenotes/notes/secure-rbac-defaults-5bb903323634a94c.yaml @@ -0,0 +1,17 @@ +--- +features: + - | + The CloudKitty policies implemented the scope concept and new default roles + (``admin``, ``member``, and ``reader``) provided by keystone. +upgrade: + - | + All the policies implement the ``scope_type`` and new defaults. + + * **Scope** + + Each policy is protected with ``project`` ``scope_type``. + + * **New Defaults (Admin, Member and Reader)** + + Policies are default to Admin, Member and Reader roles. Old roles are + also supported. There is no change in the legacy admin access.