Skip to content

Allow SET without GET arg on write-only ACL. Allow BITFIELD GET on read-only ACL#10148

Merged
oranagra merged 7 commits intoredis:unstablefrom
enjoy-binbin:set_wo
Jan 26, 2022
Merged

Allow SET without GET arg on write-only ACL. Allow BITFIELD GET on read-only ACL#10148
oranagra merged 7 commits intoredis:unstablefrom
enjoy-binbin:set_wo

Conversation

@enjoy-binbin
Copy link
Contributor

@enjoy-binbin enjoy-binbin commented Jan 19, 2022

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

@oranagra oranagra added 7.0-must-have release-notes indication that this issue needs to be mentioned in the release notes state:major-decision Requires core team consensus labels Jan 19, 2022
@madolson
Copy link
Contributor

I agree we probably need this command. I couldn't think of a good alternative that respects the keyspecs without adding a lot of complexity.

Copy link
Contributor

@zuiderkwast zuiderkwast left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@oranagra oranagra added the approval-needed Waiting for core team approval to be merged label Jan 20, 2022
@oranagra
Copy link
Member

@redis/core-team please approve.

Copy link
Collaborator

@yossigo yossigo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not happy with this command. Does this mean expect clients to always use SET_WO unless they use the GET option? If not, I don't see how this solves the ACL problem.

Reverting GET seems like a lesser evil to me right now.

@oranagra
Copy link
Member

@yossigo i was thinking that many people won't use ACL with key patterns limit.
and even the ones who do, will only grant each key-pattern either read-only or read+write permissions.
The write-only use case seems rare, so only these will have to revert to using SET_WO.

Reverting the GET argument we added in 6.2 would require to really disable this mechanism, not just deprecate it.

Another option we can take maybe, is handle it the same way we're gonna handle the BY and GET arguments of SORT.
i.e. at runtime.
This means to avoid flagging the SET command as a read one (flagging the command as a write-only one),
And then, inside setGenericCommand test the user access rights and if we see they used the GET argument, and they have insufficient right, reject the command before it did any modification.

I suppose this may be a valid way for ACL, and would be less valid from the perspective of exposing the right key-spec flags to clients (who knows what they're gonna do with them).
But since at the moment we don't know if anyone is gonna use it and for what, maybe that's a better approach.

@madolson WDYT?

@yossigo
Copy link
Collaborator

yossigo commented Jan 23, 2022

@oranagra There's also an option to leave the key-specs as they are and a specific hack in ACL handling to ignore that for SET and only treat it as read if it really is.

I think either one of those alternatives, or even the third one (breaking SET GET completely), is better than SET_WO. If we accept SET_WO as a solution, we basically say that using SET without GET is wrong and should be replaced with SET_WO - which is unreasonable. It also means that while technically write-only ACL is supported, in practice it will not be usable for most apps unless they are modified, which also makes little sense to me.

@oranagra
Copy link
Member

ok. i agree about SET_WO (we don't wanna go there).
i don't think we can simple delete all the code behind the GET argument in 7.0.

so the two alternatives are:

  1. avoid flagging the SET command as read.
  2. flag the SET command as read, but add some hack in ACL to ignore that.

and then on top of one of the above, add and explicit ACL check inside setGenericCommand when the GET argument is used.

i prefer [2]

@enjoy-binbin
Copy link
Contributor Author

we basically say that using SET without GET is wrong and should be replaced with SET_WO - which is unreasonable

yes... yossigo is right. breaking the SET GET is a better one than SET_WO.

if this up to me. i might want to break SET GET and add a new command to replace it (like setget?), and we can flag it with anything we want

flag the SET command as read, but add some hack in ACL to ignore that.

or this, we may write some ugly / hack code?

@zuiderkwast
Copy link
Contributor

zuiderkwast commented Jan 24, 2022

I think it boils down to how much we officially want to support the GET parameter. What's more important: promoting the GET parameter or avoiding flagging SET as read?

  1. avoid flagging the SET command as read.

This is the alternative of choice if we want to deprecate the GET parameter and add a hack to make the GET parameter work and require read permissions when it's used, to be backward compatible with 6.2.

  1. flag the SET command as read, but add some hack in ACL to ignore that.

This is a good alternative if we don't want to deprecate the GET parameter.

(3. Add SET_WO. If we go this way, we can steer users in the right direction using documentation. The first sentence in SET_WO can be "A write-only variant of SET, which should only be used if you have write but not read permission to the key". Most developers try to follow best practices after all.)

The same can be asked about SORT's STORE parameter: Is it important enough to flag SORT as a write command? If we had added an ACL hack, we wouldn't have needed to add SORT_RO. Perhaps we can add an ACL hack for STORE and then there's no need for SORT_RO anymore...?

@oranagra
Copy link
Member

note that SORT_RO (and EVAL_RO) was not added for ACL, it was added for clients to be able to send this command to replicas.

I vote for 2.
i.e.

  • don't add SET_WO.
  • keep the ACCESS flag for SET
  • add some ACL hack to only block it if GET argument is used and the user doesn't have read permission.

@oranagra
Copy link
Member

discussed this with @yossigo and we agree on my above proposal.

@enjoy-binbin
Copy link
Contributor Author

enjoy-binbin commented Jan 24, 2022

make a quick hack (b979fb7c6306eacb54cd1dab746abdaa273b6641)

SET RW

127.0.0.1:6379> ACL SETUSER key-permission-RW-selector on nopass "(%R~write* +@all)" "(%W~write* +@all)"
OK
127.0.0.1:6379> auth key-permission-RW-selector password
OK
127.0.0.1:6379> set write_string redis
OK
127.0.0.1:6379> set write_string redis
OK
127.0.0.1:6379> set write_string redis2 GET
"redis"

SET only write

127.0.0.1:6379> ACL SETUSER key-permission-W-selector on nopass "(%W~write* +@all)"
OK
127.0.0.1:6379> auth key-permission-W-selector password
OK
127.0.0.1:6379> set write_string redis
OK
127.0.0.1:6379> set write_string redis GET
(error) NOPERM this user has no permissions to access one of the keys used as arguments
127.0.0.1:6379> set write_string redis2 GET
(error) NOPERM this user has no permissions to access one of the keys used as arguments
127.0.0.1:6379> set write_string redis2
OK

before:

127.0.0.1:6379> ACL SETUSER key-permission-RW-selector on nopass "(%R~write* +@all)" "(%W~write* +@all)"
OK
127.0.0.1:6379> auth key-permission-RW-selector password
OK
127.0.0.1:6379> set write_string redis
(error) NOPERM this user has no permissions to access one of the keys used as arguments
127.0.0.1:6379> set write_string redis GET
(error) NOPERM this user has no permissions to access one of the keys used as arguments


127.0.0.1:6379> ACL SETUSER key-permission-W-selector on nopass "(%W~write* +@all)"
OK
127.0.0.1:6379> auth key-permission-W-selector password
OK
127.0.0.1:6379> set write_string redis
(error) NOPERM this user has no permissions to access one of the keys used as arguments
127.0.0.1:6379> set write_string redis GET
(error) NOPERM this user has no permissions to access one of the keys used as arguments

@oranagra
Copy link
Member

thanks.
I don't like the approach of ACLSelectorCheckKeyForSet, (iterating on argc to match them with "GET").
if for some reason one of the arguments has a value (like "EX" has the TTL as a value), and if that value is a free string, it can be the string "get".

I rather have acl.c explicitly remove one of READ flag for setCommand, and then add some code to setCommand to check ACL on it's own (we'll need similar check to be done by SORT for the BY and GET arguments, see #10106)

@zuiderkwast
Copy link
Contributor

note that SORT_RO (and EVAL_RO) was not added for ACL, it was added for clients to be able to send this command to replicas.

@oranagra Sure, but a similar hack would be possible when checking if it's allowed in replicas.

@oranagra
Copy link
Member

@zuiderkwast check where? these commands were basically added so that their command flags are different, in order to affect client libraries, so adding such a hack for that concern will need to be done on the client libraries, not reids.

you can argue that SET_WO is required for the same concern, but i don't think it's likely.
the read-only flag is used by clients. but so far the only use case i see for write-only is for ACL (and even that one is probably not popular)

@enjoy-binbin
Copy link
Contributor Author

enjoy-binbin commented Jan 24, 2022

another ugly / attempt hack... Note the key pattern check become O(2n) ffc90e4

# SET write and read
127.0.0.1:6379> ACL SETUSER key-permission-RW-selector on nopass "(%R~write* +@all)" "(%W~write* +@all)"
OK
127.0.0.1:6379> auth key-permission-RW-selector password
OK
127.0.0.1:6379> set write_string redis
OK
127.0.0.1:6379> set write_string redis GET
"redis"

# SET write only
127.0.0.1:6379> ACL SETUSER key-permission-W-selector on nopass "(%W~write* +@all)"
OK
127.0.0.1:6379> auth key-permission-W-selector password
OK
127.0.0.1:6379> set write_string redis
OK
127.0.0.1:6379> set write_string redis GET
(error) NOPERM this user has no permissions to access one of the keys used as arguments

# acl log
127.0.0.1:6379> acl log 1
1)  1) "count"
    2) (integer) 1
    3) "reason"
    4) "key"
    5) "context"
    6) "toplevel"
    7) "object"
    8) "write_string"
    9) "username"
   10) "key-permission-W-selector"
   11) "age-seconds"
   12) "139.69399999999999"
   13) "client-info"
   14) "id=3 addr=127.0.0.1:33168 laddr=127.0.0.1:6379 fd=8 name= age=103 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=52 qbuf-free=16332 argv-mem=23 multi-mem=0 obl=0 oll=0 omem=0 tot-mem=33517 events=r cmd=set user=key-permission-W-selector redir=-1 resp=2"

@oranagra
Copy link
Member

@enjoy-binbin i think the approach of your last message is right.
added some comments on the code.
i don't think the O(2N) is problematic, we don't expect many selectors.
let's see what @madolson thinks of this approach (or at all of the solution to handle the GET argument as an exception, not via key-spec flags)

@madolson
Copy link
Contributor

madolson commented Jan 25, 2022

I'm ok with skipping over SET_WO, I never liked it much anyways. I don't think there is a clear winner here though.

I'm not sure how I feel about deprecating the GET argument, although I think it's the best long term outcome. The GET argument also causes the SET command to return a different type (integer vs string) which I don't think we should be doing as well as the ACL issue that has been raised. It might be useful longterm to be able to rewrite some commands for compatibility but execute them as different commands, in this case we could rewrite SET + GET into a GETSET command (and presumably implement the needed flags). We're no doubt going to make more mistakes, maybe it's worth building some engineering to help us unwind them.

It sounds like a lot of work, so maybe the most Redis thing is just to implement a simple hack. I don't think there is that much to lose in just parsing the arguments twice and implementing a custom getKeys function. Most clients shouldn't care, since key positions are the same. madolson@e22e6c2. If we find more later we can add this hack there as well.

@oranagra
Copy link
Member

@madolson i like your approach (implementing a getkeys_proc to control the flags).
.. why didn't i think of that?

few minor notes:

  1. we probably want to set VARIABLE_FLAGS to a bunch of other commands (for COMMAND command completeness), like SUNIONSTORE etc. this will mean we also need to add flags support for their getkeys_proc, which is no big deal)
  2. the flag should probably be one word "variable"

p.s.
i'm a little uncomfortable about the location of the setGetKeys function, maybe it belongs in db.c with the rest of get getkeys functions.
this would mean parseExtendedStringArguments and the OBJ_SET_ flags needs to be declared in server.h, or instead, like the getkeys functions, it can implement argument parsing from scratch.
maybe we should some day move all these function to sit next to the commands they serve, not sure if that's today.

@madolson
Copy link
Contributor

madolson commented Jan 25, 2022

@oranagra

  1. Which flags change with SUNIONSTORE? I'm not aware of what you're talking about. BITFIELD is the only one that immediately came to my mind that also changes flags.
  2. I didn't like the single word "variable" since users might be confused, "maybe it's storing something", but I didn't have a better word and I was just putting out the idea.
  3. I'm fine with either position of setGetKeys, it was just less work when testing the idea. I don't like putting so much stuff in server.h, but such is our current architecture.

@oranagra oranagra changed the title Add getkeys_proc to control key flags in SET command Allow SET without GET arg, on write-only ACL Jan 26, 2022
Copy link
Member

@oranagra oranagra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any last words before i merge this?

@enjoy-binbin
Copy link
Contributor Author

i am done here, i am not sure the title (should we mention BITFIELD?)
and maybe we can ask @madolson take a look too, and see what she think (I think she should be up soon)

@oranagra
Copy link
Member

yes, no harm in waiting a few more hours.
please update the title and top comment (with other changes you made, like missing since and optional, etc)

@enjoy-binbin enjoy-binbin changed the title Allow SET without GET arg, on write-only ACL Allow SET without GET arg on write-only ACL. Allow BITFIELD GET subcommand on read-only ACL Jan 26, 2022
Copy link
Contributor

@madolson madolson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@oranagra oranagra changed the title Allow SET without GET arg on write-only ACL. Allow BITFIELD GET subcommand on read-only ACL Allow SET without GET arg on write-only ACL. Allow BITFIELD GET on read-only ACL Jan 26, 2022
@oranagra oranagra merged commit d616925 into redis:unstable Jan 26, 2022
@enjoy-binbin enjoy-binbin deleted the set_wo branch January 26, 2022 20:06
@oranagra oranagra mentioned this pull request Feb 28, 2022
enjoy-binbin added a commit to enjoy-binbin/redis that referenced this pull request Jun 9, 2022
SET and BITFIELD added `get_keys_function` in redis#10148, causing
them to be wrongly marked movablekeys in `populateCommandMovableKeys`.

Currently SET and BITFIELD has only one key, and key_specs are
marked as VARIABLE_FLAGS, so we can rely on the VARIABLE_FLAGS flag.

This was an unintended side effect introduced in redis#10148 (7.0 RC2)
Fixes redis#10833
oranagra added a commit that referenced this pull request Jun 12, 2022
)

The SET and BITFIELD command were added `get_keys_function` in #10148, causing
them to be wrongly marked movablekeys in `populateCommandMovableKeys`.

This was an unintended side effect introduced in #10148 (7.0 RC1)
which could cause some clients an extra round trip for these commands in cluster mode.

Since we define movablekeys as a way to determine if the legacy range [first, last, step]
doesn't find all keys, then we need a completely different approach.

The right approach should be to check if the legacy range covers all key-specs,
and if none of the key-specs have the INCOMPLETE flag. 
This way, we don't need to look at getkeys_proc of VARIABLE_FLAG at all.
Probably with the exception of modules, who may still not be using key-specs.

In this PR, we removed `populateCommandMovableKeys` and put its logic in
`populateCommandLegacyRangeSpec`.
In order to properly serve both old and new modules, we must probably keep relying
CMD_MODULE_GETKEYS, but do that only for modules that don't declare key-specs. 
For ones that do, we need to take the same approach we take with native redis commands.

This approach was proposed by Oran. Fixes #10833

Co-authored-by: Oran Agra <[email protected]>
enjoy-binbin added a commit to enjoy-binbin/redis that referenced this pull request Jul 31, 2023
…is#10837)

The SET and BITFIELD command were added `get_keys_function` in redis#10148, causing
them to be wrongly marked movablekeys in `populateCommandMovableKeys`.

This was an unintended side effect introduced in redis#10148 (7.0 RC1)
which could cause some clients an extra round trip for these commands in cluster mode.

Since we define movablekeys as a way to determine if the legacy range [first, last, step]
doesn't find all keys, then we need a completely different approach.

The right approach should be to check if the legacy range covers all key-specs,
and if none of the key-specs have the INCOMPLETE flag. 
This way, we don't need to look at getkeys_proc of VARIABLE_FLAG at all.
Probably with the exception of modules, who may still not be using key-specs.

In this PR, we removed `populateCommandMovableKeys` and put its logic in
`populateCommandLegacyRangeSpec`.
In order to properly serve both old and new modules, we must probably keep relying
CMD_MODULE_GETKEYS, but do that only for modules that don't declare key-specs. 
For ones that do, we need to take the same approach we take with native redis commands.

This approach was proposed by Oran. Fixes redis#10833

Co-authored-by: Oran Agra <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approval-needed Waiting for core team approval to be merged release-notes indication that this issue needs to be mentioned in the release notes state:major-decision Requires core team consensus

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants