ACL V2 - Selectors and key based permissions#9974
Conversation
I'm a bit uncomfortable with calling list length "metadata" in LPUSH, and the cardinality in SCARD "data" I think we should clearly state where INCR, and SETNX falls into, and maybe even a hypothetical case where INCR would have returned +OK (i.e the new data is a derivative of the existing one).
i think that in the past, the
I think this is just a bug (missing read flag).
That's an interesting downside (maybe not too late to change).
Regarding ACLCheckKey, let's make that change ASAP (API not released yet) |
|
I'm sorry. I was so happy that I finally finished reviewing the code, that I posted my comments without a summary. |
|
@oranagra Thanks for looking through, it's not as polished as I normally like to put up PRs but knew I was going to be busy this week, so probably worth getting feedback. Tests was one thing I mentioned needs more work, so I'll definitely revamp them. |
yossigo
left a comment
There was a problem hiding this comment.
@madolson Looks good to me. Did not do a very deep code review but I did play with that bit and it's cool.
Do you want to update the top comment with everything you have in mind that's still pending? I imagine there's a long tail and maybe we can parallelize this work somehow.
|
As a chore/aside to this, would it make/be against common sense to add a capability to introspect the effective ACL permissions for a given user? Ideally, this would be supported by an interface that, for a given user, would:
Mock API: |
|
@itamarhaber I have a dry-run command actually implemented locally for testing, I've been flip flopping if it's worth committing. My variant has the form: Will add it as a follow-up. Not sure about the super detailed listing of commands though. |
|
NOTE: I didn't fix all the db.c stuff, I still need to spend some more time to fully understand the right way to fetch the keyspecs. However, I don't think the prevents reviewing the rest of the decisions. |
|
@madolson i'd rather avoid rebase and force-push (please just merge from unstable), it makes it hard to review changes. anyway, for now, i'll assume the fist commit of this PR is identical to what i already reviewed. and i should review the two after it. right? |
oranagra
left a comment
There was a problem hiding this comment.
only looked at the narrow diff of the last two commits, so might have comments that are out of context or may be missing something.
didn't review the tests.
|
@oranagra sorry about the rebase, I wanted to pull in the changes from the pubsub v2 PR which included shard channels. There was quite a bit of changes all over the place, so I just decided to rebase. I normally agree, and wait until the end to rebase. |
oranagra
left a comment
There was a problem hiding this comment.
minor comments on last commit
- getKeysUsingKeySpecs avoids argv access violation in case keynum index is out of range - doesCommandHaveKeys used getkeys_proc without checking that it's not a module command - doesCommandHaveKeys support modules that declared keys via CMD_MODULE_GETKEYS - doesCommandHaveKeys skips keyspecs that are marked with CHANNEL - doesCommandHaveChannels uses key-specs for shard channels, will make it possible for modules to join that game other minor things: - getKeysUsingKeySpecs - unify error handling in a goto label - ACLSelectorCheckKey rename flags to indicate they're keyspec flags and ACL's - ACLDoesCommandHaveChannels moved to db.c (next to doesCommandHaveKeys) - ACLCheckAllUserCommandPerm has to modify both error output parameters together (we can't have one come from one error and the other from another) - aclCommand has multiple if-else cases. name the goto label appropriately (specific to one of them)
|
@madolson i've reviewed the whole thing again top to bottom, found a few bugs and fixed them, please CR the last commit. i've also re-run the tests with the code that asserts both key extraction functions give the same results (put the assert in processCommand, since putting it in I have a feeling that the I think this is ready for merge. |
| listRelease(u->patterns); | ||
| listRelease(u->channels); | ||
| ACLResetFirstArgs(u); | ||
| listRelease(u->selectors); |
There was a problem hiding this comment.
Don't we need to privately free each selector first via ACLFreeSelector ?
There was a problem hiding this comment.
No, listRelease will call the FreeMethod automatically, which is ACLFreeSelector.
| if (toupper(op[offset]) == 'R' && !(flags & ACL_READ_PERMISSION)) { | ||
| flags |= ACL_READ_PERMISSION; | ||
| } else if (toupper(op[offset]) == 'W' && !(flags & ACL_WRITE_PERMISSION)) { | ||
| flags |= ACL_WRITE_PERMISSION; |
There was a problem hiding this comment.
Why do we need to the flag check if at the end there is only one bit operation ?
There was a problem hiding this comment.
To prevent having something like '%~WWWWWWWW' be accepted, it's just tighter input validation.
| * ACL_DENIED_CHANNEL. */ | ||
| static int ACLSelectorCheckPubsubArguments(aclSelector *s, robj **argv, int idx, int count, int literal, int *idxptr) { | ||
| for (int j = idx; j < idx+count; j++) { | ||
| if (ACLCheckChannelAgainstList(s->channels, argv[j]->ptr, sdslen(argv[j]->ptr), literal != ACL_OK)) { |
There was a problem hiding this comment.
Do we split at 80 chars per line ?
There was a problem hiding this comment.
I don't split, and I've seen others not explicitly try to split it as well, when it doesn't really help readability to split.
There was a problem hiding this comment.
i think it damages readability.
- splitting at arbitrary position (offset, rather than a logical one).
- adds more lines so we get less lines fit in our screen.
- these days people have very wide monitors (and vertical screen space is needed, and there's an abundance of unused horizontal space)
src/db.c
Outdated
|
|
||
| /* Returns if a given command may possibly access channels. For this context. */ | ||
| int doesCommandHaveChannels(struct redisCommand *cmd) { | ||
| return (getAllKeySpecsFlags(cmd, 0) & CMD_KEY_CHANNEL) || /* has at least one key-spec marked as CHANNEL */ |
There was a problem hiding this comment.
@oranagra This grabs SUNSUBSCRIBE as well, which we don't want. I'm going to add an explicit check for that if that works. I'm also going to test this.
There was a problem hiding this comment.
Actually, this is missing other support for modules + channels that was also removed. We can add it back, but the functionality isn't here right now.
There was a problem hiding this comment.
I'd still argue this function belongs in db.c next to the other one.
Also, even if modules can't yet use it (so my commit comment was false), I think conceptually it's nicer to count on key spec flags (or any other flags), rather than explicitly list function names.
I.e. The transition we're making in other places like processCommand, to stop maintaining lists of functions, and instead define their behavior in flags.
* Minor renaming * Removed refactoring for "get all channels" since we don't have the corresponding code to let modules use it
|
@oranagra Just wanted to clarify the shard-channel keyspec stuff. In an earlier revision there was code to support both channels and sharded-channels in the keyspecs, but there wasn't clarity of it we wanted to do that, so I removed support for both. Right now modules have to do their own manual ACL checks for channels and there is an item in the follow up list to think this behavior through more. The issues that I saw with modules support:
|
|
Let's discuss modules later. |
yossigo
left a comment
There was a problem hiding this comment.
No CR, but played a bit with the latest version - LGTM!
SET is a R+W command, because it can also do `GET` on the data. SET without GET, is a WRITE command. SET with GET, is a READ + WRITE command. In redis#9974, we added ACL to let users define write-only access. So when the user uses SET with GET option, and the user doesn't have the READ permission on the key, we need to reject it. In this commit, we add a `getkeys_proc` function to control key flags in SET command. We also add a new key spec flag means that some keys might have different flags depending on arguments. Co-authored-by: Madelyn Olson <[email protected]> Co-authored-by: Oran Agra <[email protected]>
…ad-only ACL (#10148) SET is a R+W command, because it can also do `GET` on the data. SET without GET is a write-only command. SET with GET is a read+write command. In #9974, we added ACL to let users define write-only access. So when the user uses SET with GET option, and the user doesn't have the READ permission on the key, we need to reject it, but we rather not reject users with write-only permissions from using the SET command when they don't use GET. In this commit, we add a `getkeys_proc` function to control key flags in SET command. We also add a new key spec flag (VARIABLE_FLAGS) means that some keys might have different flags depending on arguments. We also handle BITFIELD command, add a `bitfieldGetKeys` function. BITFIELD GET is a READ ONLY command. BITFIELD SET or BITFIELD INCR are READ WRITE commands. Other changes: 1. SET GET was added in 6.2, add the missing since in set.json 2. Added tests to cover the changes in acl-v2.tcl 3. Fix some typos in server.h and cleanups in acl-v2.tcl Co-authored-by: Madelyn Olson <[email protected]> Co-authored-by: Oran Agra <[email protected]>
Implementation based off of #7291
Implementation details
This feature adds two major features to ACLs:
Key based permissions
We will introduce three sub-permissions on keys, read + write. The goal here is to make it easy to restrict specific types of operations on key values and to allow defining permissions that work with modules. This functionality also lets you reason about "future proofing operations". Key based permissions do not replace command permissions.
~<pattern>is just a shorthand for full permission on the pattern.%(R|W)~<pattern>will grant the specified permissions for that pattern.To reset, use
resetkeysjust as before. Patterns are merged within a selector.%(R|W~*)is valid syntax, and maps to granting the specified access to the entire keyspace.Multiple selectors
A selector is a set of allowed commands + first args + a set of key patterns that match against Redis commands. Each user can now have 1 or more selectors, and as long as 1 selector matches against the user, the command will be allowed. We are introducing the concept of the "root permissions" which is the selector that is applied to users when they are created. This selector is mutable in order to be maximally backwards compatible.
All new selectors by default are
-@ALLandresetkeys. Selectors are not currently deduplicated if they are identical, but could be in the future.All created selectors are immutable after initialization. The only way to update them is with a new keyword,
clearselectors. Therefor to make minor changes requires fully resetting them and adding a new ones. Callingclearselectorsdoes not remove permissions from the root segment. This dramatically reduces the chance of accidentally updating a selector to something you don't expect, since you are being explicit about the new state.With selectors, you still only need to have channel access to maintain access for subscribe. We aggregate all of the allowed channels across all selectors when doing verification you can still access the channel.
A selector is defined by a set of ACL operations surrounded by parentheses. (e.g.
(+@all ~*). The canonical form of a selector is that it should be a single string that starts with ( and ends with ) with no spacing between the boundary characters and the ACL operators. This is how it will be written out inACL LIST. However, while testing I found it was easy to mess up, and it's a little weird in the ACL file. So Redis will try to transform selectors into the canonical form by stripping whitespace and searching for valid selectors across arguments. This applies to startup args, ACL list, config file, andACL SETUSER. This code isn't strictly needed, but I think it's a nice usability issue.ACL DRYRUN command
A new dryrun command was added that allows users to "test" whether or not a given user will be able to execute a command. I was using this mostly for testing, but Itamer suggested I commit it. It also makes some tests much easier to orchestrate.
Syntax
Returns:
OK: The command can be executed.
Bulk String: The error code for the command.
Errors on invalid command or user names.
Module APIs
The RM_CheckKeyPermission API now also takes in a flag argument describing how the key is going to be modified.
ACL List
Nothing specifically interesting here, key permissions and selectors are included naturally here.
ACL GETUSER
Two changes have been made to the getuser response. The first is the new selector field, which was mentioned earlier. The second is that the GETUSER now responds with DSL for keys and channels. This was a required decision for keys, since they now had more precise definitions. I also updated channels since now everything is using DSL as opposed to channels being the one thing that wasn't.
Note that there's a breaking change here!
Example:
Keyspecs and key references
The getKeySpecs functionality returns a keyReference instead of just a position. This is used so that the ACLs can fetch the flags corresponding to the key.
The set/get problem
Set is currently marked as R+W command, because it can also do GET on the data. This pops in a couple of other commands that have both
RWflags even though they normally only do one, this was the result of us building complex commands. I just wanted to raise this as a downside. This seems like a gap, but my guess is it's not worth fixing now.I don't believe strongly this needs to be solved right now. It would require another big push into updating the keyspecs to have multiple different variants with different keys. This is being investigated in #10040.
Related conversations