Identity trust rbac tests
Add identity trust rbac tests corresponding to the policy actions
in [0].
Most of the policy actions in [0] have a rule of '' (empty string),
meaning any role can perform the action. However, the
"identity:create_trust" policy action has a rule of
base.RULE_TRUST_OWNER which translates to:
user_id:%(trust.trustor_user_id)s [1].
This is a rather unique rule, one that is not dependent on the
current user's user_id, project_id or even role. Rather, this
rule translates to: "Does the current user's user_id match
the user_id of the trustor creating a trust with a trustee?"
As should be expected, "trust.trustor_user_id" can only be
dynamically calculated at runtime, rather than immediately
retrieved from a tempest credential variable, as is the case
with user_id and project_id.
Hence, this patch not only 1) creates trust rbac tests but 2)
enhances the framework, particularly the rbac_rule_validation
decorator, as well as the rbac_policy_parser framework, to
handle additional target data that must be passed to
oslo-policy-checker in order for proper authorization
determination.
The "target" parameter in oslo.policy is a dictionary that
contains "As much information about the object being
operated on as possible" [2]. Accordingly, the
rbac_rule_validation decorator has been enhanced with a new
param called `extra_target_data` that is a dictionary
containing key-value pairs of dynamically calculated
data needed by oslo.policy to correctly determine whether
the "target" has authorization to perform a policy action.
For example,
extra_target_data={
"trust.trustor_user_id": "os.auth_provider.credentials.user_id"
})
means that trust.trustor_user_id equals the primary credential's
user_id. So, if a trustor's user_id equals the primary credential's
user_id, then the policy parser will return True for `is_allowed`.
However, if a trustor's user_id doesn'tequal to the primary credential's
user_id, but rather the alt credential's user_id, say, then `is_allowed`
returns False.
Thus, the only way to do negative testing with test_trusts_rbac is
to explicitly create a negative test, as described in the above
paragraph, which is what this patch does. Normally, negative testing
is baked in, dependent on the `rbac_test_role`, but with more
complicated policy enforcement as described above, this is not
possible.
While it is possible to create a new CONF value called trustor_user_id,
it would require using pre-provisioned credentials, which is something
Patrole doesn't explicitly use.
[0] https://github.com/openstack/keystone/blob/master/keystone/common/policies/trust.py
[1] https://github.com/openstack/keystone/blob/master/keystone/common/policies/base.py
[2] https://docs.openstack.org/developer/oslo.policy/api/oslo_policy.html
Depends-On: Ib82e8b8a0d6c8587fb0b1ce415e751c3ebc3c2f9
Change-Id: I5c00fdb345556066343bdaeb5f008d639a94bc4b
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index ec63119..6a5ed5e 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -29,7 +29,8 @@
LOG = logging.getLogger(__name__)
-def action(service, rule, admin_only=False, expected_error_code=403):
+def action(service, rule, admin_only=False, expected_error_code=403,
+ extra_target_data={}):
"""A decorator which does a policy check and matches it against test run.
A decorator which allows for positive and negative RBAC testing. Given
@@ -66,10 +67,10 @@
caller_ref = None
if args and isinstance(args[0], test.BaseTestCase):
caller_ref = args[0]
- tenant_id = caller_ref.auth_provider.credentials.tenant_id
+ project_id = caller_ref.auth_provider.credentials.project_id
user_id = caller_ref.auth_provider.credentials.user_id
except AttributeError as e:
- msg = ("{0}: tenant_id/user_id not found in "
+ msg = ("{0}: project_id/user_id not found in "
"cls.auth_provider.credentials".format(e))
LOG.error(msg)
raise rbac_exceptions.RbacResourceSetupFailed(msg)
@@ -80,10 +81,11 @@
"check for policy action {0}.".format(rule))
allowed = CONF.rbac.rbac_test_role == 'admin'
else:
- authority = rbac_auth.RbacAuthority(tenant_id, user_id,
- service)
- allowed = authority.get_permission(rule,
- CONF.rbac.rbac_test_role)
+ authority = rbac_auth.RbacAuthority(
+ project_id, user_id, service,
+ _format_extra_target_data(caller_ref, extra_target_data))
+ allowed = authority.get_permission(
+ rule, CONF.rbac.rbac_test_role)
expected_exception, irregular_msg = _get_exception_type(
expected_error_code)
@@ -146,3 +148,34 @@
raise rbac_exceptions.RbacInvalidErrorCode()
return expected_exception, irregular_msg
+
+
+def _format_extra_target_data(test_obj, extra_target_data):
+ """Formats the "extra_target_data" dictionary with correct test data.
+
+ Before being formatted, "extra_target_data" is a dictionary that maps a
+ policy string like "trust.trustor_user_id" to a nested list of BaseTestCase
+ attributes. For example, the attribute list in:
+
+ "trust.trustor_user_id": "os.auth_provider.credentials.user_id"
+
+ is parsed by iteratively calling `getattr` until the value of "user_id"
+ is resolved. The resulting dictionary returns:
+
+ "trust.trustor_user_id": "the user_id of the `primary` credential"
+
+ :param test_obj: type BaseTestCase (tempest base test class)
+ :param extra_target_data: dictionary with unresolved string literals that
+ reference nested BaseTestCase attributes
+ :returns: dictionary with resolved BaseTestCase attributes
+ """
+ attr_value = test_obj
+ formatted_target_data = {}
+
+ for user_attribute, attr_string in extra_target_data.items():
+ attrs = attr_string.split('.')
+ for attr in attrs:
+ attr_value = getattr(attr_value, attr)
+ formatted_target_data[user_attribute] = attr_value
+
+ return formatted_target_data