-
Notifications
You must be signed in to change notification settings - Fork 284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CA-403744: Implement other_config operations for task #6239
base: master
Are you sure you want to change the base?
CA-403744: Implement other_config operations for task #6239
Conversation
Currently, users with read-only permissions can freely manipulate their own tasks (create, cancel, destroy, etc.). However, they cannot easily manipulate the "other_config" field. Some keys are specified in the datamodel as being writeable by subjects with the VM operator role. However, RBAC checking for keys was only ever implemented for the individual "add" and "remove" operations, not the "set_other_config" operation. This makes sense because the typical scenario is that the required writer role for an "other_config" field is more privileged than each of the individually-specified key-specific roles. In the case of tasks, the desired role for the set_other_config operation is the read-only role. However, we cannot simply demote the required "writer role" for the other_config field, because: 1) We must maintain key-based RBAC checks for the operation. For example, if a read-only user attempts to modify a task's other_config using set_other_config, the operation must fail if they've attempted to modify the value mapped to by some key that they are not privileged to use. 2) Key-based RBAC checking is special cased to "add_to" and "remove_from". You can attempt to ascribe key-related role restrictions to arbitrary messages but these will all fail at runtime, when invoked - because Rbac.check is special cased to expect to be able to extract out the key name from a "key" argument it receives, which is emitted for "add_to" and "remove_from". 3) There is an additional restriction that read-only users should not be able to modify arbitrary tasks, only their own. Both of these points require a custom implementation. To this end, we mark "other_config" as read-only (DynamicRO) and implement custom handlers for the "add_to", "remove_from", and "set" operations. In doing so, we implement a subset of the RBAC protection logic for keys. This custom implementation amounts to a relaxation of the usual RBAC rules: where "add_to" and "remove_from" (both purely destructive operations) cite a protected key (that they are not permitted to write), RBAC fails. In the custom implementation of "set_other_config", an under-privileged session can cite any key so long as their change is not destructive (it must preserve what is already there). Signed-off-by: Colin James <colin.barr@cloud.com>
965ab3d
to
e49438c
Compare
What is the use case to let r/o users to manipulate the other-config field? I understand that symmetry with other operations is desirable; on the other hand, other-config is mostly an escape hatch for clients that in this case would not be available. |
The exact use case is to fuse a bunch of This PR resolves the issue but it could go as a "WONTFIX" as it is a lot of special casing for a rather niche use-case. If anything has come out of it, I may be able to document RBAC and its usage in the auto-generated |
-> session_id:[`session] Ref.t | ||
-> permissions:string list | ||
-> string option | ||
(** Given a list of permissions, determine if the given session is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the wording surprising? Usually permissions refer to actions; now we have permissions (allowed/disallowed) on permissions. A user has been granted permissions - so maybe we refer to the first permission outside (exceeding) the granted ones. That would imply an order on them. Or we assume permissions are a set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The wording could probably be improved.
The function is effectively List.exists (Fun.negate has_permission)
. It's a bit of a special case, but it exists because a set_other_config
call could cite many protected keys, but to be consistent, we only report one erroneous key in any error message. So, rather than collecting them all (redundantly, since any error implies the call cannot go through), we just find the first and report that.
type 'a node = {arrows: (string, 'a node) Hashtbl.t; mutable value: 'a option} | ||
|
||
let create_node () = | ||
let arrows = Hashtbl.create 16 in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Arrows are edges leading to the suffix of the key?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Then, any key discovered during lookup is prepended with the relevant action and used as an RBAC permission name. The format is <action>/key:<key>
.
(* Given an input key, compare against the protected keys of the | ||
task.other_config field. If a protected key matches, return it. | ||
|
||
For example, if the datamodel specifies "foo.bar.*" as a protected |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this introduce hierarchical keys? I think this needs to mention that the dot separates levels in a key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The keys are already hierarchical. Technically, the use of hierarchies and wildcards are currently unused in task's other_config
. The inclusion of the trie was cherry picked from an earlier branch that tried to tackle the RBAC problem generally (which would be viable here, if not for the added restriction that the calls are only valid for tasks the session owns - unless sufficiently privileged to manage any task).
The trie solution is a bit more structured than the current code. Of course, the benefit is clarity over much else: the protections you get with tries (overlap being deduplicated) don't really apply because it's the developers that we trust to specify the protected keys.
I generally like this. I understand this such that keys become more structured (using a dot notation) and that keys can be protected using special cases based on their prefix. This is more flexible than using flat names but is also a little fancy given that we haven't run into this problem. |
Currently, users with read-only permissions can freely manipulate their own tasks (create, cancel, destroy, etc.). However, they cannot easily manipulate the
other_config
field. Some keys are specified in the datamodel as being writeable by subjects with the VM operator role.However, RBAC checking for keys was only ever implemented for the individual "add" and "remove" operations, not the
set_other_config
operation. This makes sense because the typical scenario is that the required writer role for an "other_config" field is more privileged than each of the individually-specified key-specific roles.In the case of tasks, the desired role for the
set_other_config
operation is the read-only role. However, we cannot simply demote the required "writer role" for theother_config
field, because:We must maintain key-based RBAC checks for the operation. For
example, if a read-only user attempts to modify a task's
other_config
using
set_other_config
, the operation must fail if they've attemptedto modify the value mapped to by some key that they are not
privileged to use.
Key-based RBAC checking is special cased to
add_to
andremove_from
. You can attempt to ascribe key-related rolerestrictions to arbitrary messages but these will all fail at
runtime, when invoked - because
Rbac.check
is special cased to expectto be able to extract out the key name from a
key
argument itreceives, which is emitted for
add_to
andremove_from
.There is an additional restriction that read-only users should not be
able to modify arbitrary tasks, only their own.
Both of these points require a custom implementation. To this end, we mark
other_config
as read-only (DynamicRO) and implement custom handlers for theadd_to
,remove_from
, andset
operations. In doing so, we implement a subset of the RBAC protection logic for keys.This custom implementation amounts to a relaxation of the usual RBAC rules: where
add_to
andremove_from
(both purely destructive operations) cite a protected key (that they are not permitted to write), RBAC fails. In the custom implementation ofset_other_config
, an under-privileged session can cite any key so long as their change is not destructive (it must preserve what is already there).The motivation for this change is in fusing serial
add_to_other_config
andremove_from_other_config
operations into a singleset_other_config
call. I've included (possibly excessive) commentary within the code as the RBAC checking stuff is not touched a lot. This change may not be desirable overall, as it has low impact.