Add ACL role support - #3967
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds first-class ACL roles with named permission sets, user-role membership, role-aware authorization, ACL/config persistence, role administration commands, updated command schemas, and end-to-end tests. ChangesACL Roles Feature
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant ACLCheckAllUserCommandPerm
participant UserSelectors
participant RoleSelectors
Client->>ACLCheckAllUserCommandPerm: Check command permission
ACLCheckAllUserCommandPerm->>UserSelectors: Evaluate user selectors
ACLCheckAllUserCommandPerm->>RoleSelectors: Evaluate assigned-role selectors
UserSelectors-->>ACLCheckAllUserCommandPerm: Permission result
RoleSelectors-->>ACLCheckAllUserCommandPerm: Permission result
ACLCheckAllUserCommandPerm-->>Client: Allow or deny
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/commands.def (1)
7146-7152:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd a
10.0.0history entry for the newACL SETUSERrole syntax.
ACL_SETUSER_Historystill stops at9.1.0, but this PR adds+@role:<name>/-@role:<name>handling. That leaves generated metadata andCOMMAND DOCSwithout any record of the new public syntax.Based on PR objectives: role assignment/removal is exposed through
ACL SETUSERusing+@role:<name>/-@role:<name>.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/commands.def` around lines 7146 - 7152, ACL_SETUSER_History is missing the new 10.0.0 entry for the role syntax change; add a new history entry to the ACL_SETUSER_History array describing the addition of "+@role:<name> / -@role:<name>" role assignment/removal syntax (use the exact symbol ACL_SETUSER_History and append a {"10.0.0","Added role assignment/removal via +@role:<name> and -@role:<name>."} entry so generated metadata and COMMAND DOCS include the new public syntax).
🧹 Nitpick comments (2)
src/acl.c (2)
2863-2892: 💤 Low valueConsider adding rule validation for consistency with user loading.
Unlike
ACLAppendUserForLoadingwhich validates rules against a fake user (tolerating unknown commands/roles), this function only validates selector parenthesis matching viaACLMergeSelectorArgumentsbut doesn't validate the actual rules.While invalid rules will still be caught during
ACLLoadConfiguredRolesat startup, validating here would:
- Provide earlier, more precise error reporting (line number from config parsing)
- Be consistent with user loading behavior
This is a minor inconsistency since both approaches ultimately prevent startup with invalid ACLs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/acl.c` around lines 2863 - 2892, ACLAppendRoleForLoading should validate the merged rule arguments the same way ACLAppendUserForLoading does (i.e., by applying the rules to a temporary/fake ACL user) instead of only relying on ACLMergeSelectorArguments; after acl_args is produced, create a temporary user object and run the same rule-validation path used by ACLAppendUserForLoading/ACLLoadConfiguredRoles to detect invalid commands/roles and parenthesis errors, set argc_err to invalid_idx+2 (or the appropriate argument index returned by the validator) and return C_ERR on failure, freeing acl_args before returning; on success continue to copy/store the validated acl_args as currently done.
729-735: 💤 Low valueMinor: Unreachable code path with confusing error message.
The check at line 731 can only fail if a role with the same name was created between line 703 (where we checked
!r) and line 730. In single-threaded command processing, this race condition cannot occur. The error message "Role already exists" is also misleading since the function already determined the role doesn't exist.This is defensive code and harmless, but consider removing it or updating the message if kept for robustness against future changes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/acl.c` around lines 729 - 735, The defensive check after ACLCreateRole is confusing and unreachable in current single-threaded processing; either remove the entire if (!r) { r = ACLCreateRole(rolename, sdslen(rolename)); if (!r) { error = sdsnew("Role already exists"); goto cleanup; } } block, or if you want to keep a defensive fallback, change the sdsnew message to a generic failure like "Failed to create role" or "ACLCreateRole returned NULL" and ensure the code still jumps to cleanup; reference ACLCreateRole, r, rolename, sdsnew, error and cleanup when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/commands.def`:
- Around line 7215-7216: Update the summary strings for the getrole and getuser
command entries so they mention role-related fields: modify the MAKE_CMD
invocation for "getrole" (the entry using ACL_GETROLE_History, ACL_GETROLE_Tips,
ACL_GETROLE_Args) to say it returns role selectors and members in addition to
ACL rules, and modify the MAKE_CMD invocation for "getuser" (the entry using
ACL_GETUSER_History, ACL_GETUSER_Tips, ACL_GETUSER_Args) to indicate the
response includes a roles field (in addition to listing
rules/passwords/patterns); keep the rest of each command entry unchanged.
In `@src/config.c`:
- Line 580: The error message for duplicate roles is misleading because
ACLSetStringError() returns a user-specific string when errno==EALREADY; update
handling so duplicate-role errors produce a generic or role-aware message.
Either modify ACLSetStringError() (used by ACLAppendRoleForLoading and others)
to return a neutral wording for EALREADY like "Duplicate ACL definition found.
Each user or role can only be defined once in config files", or refactor callers
(e.g., where ACLAppendRoleForLoading invokes ACLSetStringError() in config.c) to
pass contextual information so the error text distinguishes roles from users;
implement the simpler fix by changing the EALREADY branch in ACLSetStringError()
to the generic message.
- Around line 577-584: Add "role" to the allowlist used by
rewriteConfigReadOldFile/lookupConfig so lines starting with "role" are not
commented out during CONFIG REWRITE (preventing data loss), and fix the
duplicate-role error text by ensuring role-specific errors are produced: either
have ACLAppendRoleForLoading set a role-specific errno/message or update
ACLSetStringError to return "Duplicate role found..." when called after
ACLAppendRoleForLoading returns EALREADY; reference the functions
rewriteConfigReadOldFile, lookupConfig (allowlist), ACLAppendRoleForLoading and
ACLSetStringError to locate and apply these changes.
---
Outside diff comments:
In `@src/commands.def`:
- Around line 7146-7152: ACL_SETUSER_History is missing the new 10.0.0 entry for
the role syntax change; add a new history entry to the ACL_SETUSER_History array
describing the addition of "+@role:<name> / -@role:<name>" role
assignment/removal syntax (use the exact symbol ACL_SETUSER_History and append a
{"10.0.0","Added role assignment/removal via +@role:<name> and -@role:<name>."}
entry so generated metadata and COMMAND DOCS include the new public syntax).
---
Nitpick comments:
In `@src/acl.c`:
- Around line 2863-2892: ACLAppendRoleForLoading should validate the merged rule
arguments the same way ACLAppendUserForLoading does (i.e., by applying the rules
to a temporary/fake ACL user) instead of only relying on
ACLMergeSelectorArguments; after acl_args is produced, create a temporary user
object and run the same rule-validation path used by
ACLAppendUserForLoading/ACLLoadConfiguredRoles to detect invalid commands/roles
and parenthesis errors, set argc_err to invalid_idx+2 (or the appropriate
argument index returned by the validator) and return C_ERR on failure, freeing
acl_args before returning; on success continue to copy/store the validated
acl_args as currently done.
- Around line 729-735: The defensive check after ACLCreateRole is confusing and
unreachable in current single-threaded processing; either remove the entire if
(!r) { r = ACLCreateRole(rolename, sdslen(rolename)); if (!r) { error =
sdsnew("Role already exists"); goto cleanup; } } block, or if you want to keep a
defensive fallback, change the sdsnew message to a generic failure like "Failed
to create role" or "ACLCreateRole returned NULL" and ensure the code still jumps
to cleanup; reference ACLCreateRole, r, rolename, sdsnew, error and cleanup when
making the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0a1a1111-8d14-43ee-9833-a72088926703
📒 Files selected for processing (11)
src/acl.csrc/commands.defsrc/commands/acl-delrole.jsonsrc/commands/acl-getrole.jsonsrc/commands/acl-getuser.jsonsrc/commands/acl-roles.jsonsrc/commands/acl-setrole.jsonsrc/config.csrc/server.htests/assets/role.acltests/unit/acl-role.tcl
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## unstable #3967 +/- ##
============================================
+ Coverage 78.55% 78.57% +0.02%
============================================
Files 166 166
Lines 88370 88866 +496
============================================
+ Hits 69416 69828 +412
- Misses 18954 19038 +84
🚀 New features to boost your workflow:
|
|
@valkey-review-bot Please review this change. |
|
One issue on role-aware permission checks: the command-execution path now honors role selectors, but three sibling permission helpers were not given the same treatment, so they diverge from the new OR-with-roles semantics. Details inline. The rest of the change — bidirectional member-list bookkeeping across |
hpatro
left a comment
There was a problem hiding this comment.
Lot of code feels redundant due to the CRUD operation already available around User. I think the code would be much simpler if we use a flag at the User struct level to distinguish between User and Role and we could reuse lot of the existing code. What do you think?
Skimmed through the rest of the code, looks decent. Will do a thorough review after we decide on the above.
| list *RolesToLoad; /* Similar to UsersToLoad, but for ACL roles. Every list | ||
| element is a NULL terminated array of SDS pointers: | ||
| the first is the role name, all the remaining pointers | ||
| are ACL rules (no passwords/on/off). */ |
There was a problem hiding this comment.
There won't be any passwords, right?
| are ACL rules (no passwords/on/off). */ | |
| are ACL rules */ |
There was a problem hiding this comment.
Yes I meant Roles are similar to ACL user but no passwords/on/off
UsersToLoad - contains passwords/on/off
RolesToLoad - no passwords/on/off
roshkhatri
left a comment
There was a problem hiding this comment.
I agree with @hpatro about using the CRUD operation already available around User
I had few more concerns, let me know wdyt?
Also, the pubsub clients are not being killed/disconnected when the channel access is revoked through roles either via SETROLE restricting the role, or via SETUSER -@role: removing the role from the user.
Agree, unifying the struct with a flag while keeping them stored separately makes sense, will rework the PR, example struct would be: |
Signed-off-by: Yang Zhao <zymy701@gmail.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
tests/unit/acl-role.tcl (1)
255-258: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winVerify ordering and restored ACL semantics, not just presence.
These checks would pass if roles were emitted after users, or if
ACL SAVErestored role names but lost selectors or memberships. Assert all roles precede users, then afterACL LOADrecheck Alice’s membership and allowed/denied commands.Also applies to: 318-322, 369-374
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/unit/acl-role.tcl` around lines 255 - 258, Strengthen the ACL LIST assertions in the role-list tests and the corresponding checks at the other referenced locations: verify every role entry appears before user entries, rather than only checking that one role exists. After ACL LOAD, reassert Alice’s role membership plus her allowed and denied command behavior, preserving the existing ACL SAVE/LOAD setup and test flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/server.h`:
- Around line 1309-1312: Remove the unrelated declarations and field from
src/server.h: delete client::qb_applied, getStringObjectLen, sdsEncodedObject,
the object accessor declarations, zzlValidateScores, initLibbacktraceFrameState,
and configInfoCommand at the specified consolidated sites; make no other
changes.
---
Nitpick comments:
In `@tests/unit/acl-role.tcl`:
- Around line 255-258: Strengthen the ACL LIST assertions in the role-list tests
and the corresponding checks at the other referenced locations: verify every
role entry appears before user entries, rather than only checking that one role
exists. After ACL LOAD, reassert Alice’s role membership plus her allowed and
denied command behavior, preserving the existing ACL SAVE/LOAD setup and test
flow.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a6fba351-73b3-418a-ae08-ba8dbfbd4d07
📒 Files selected for processing (13)
src/acl.csrc/commands.defsrc/commands/acl-delrole.jsonsrc/commands/acl-getrole.jsonsrc/commands/acl-getuser.jsonsrc/commands/acl-roles.jsonsrc/commands/acl-setrole.jsonsrc/commands/acl-setuser.jsonsrc/config.csrc/server.htests/assets/role.acltests/unit/acl-role.tcltests/unit/acl.tcl
🚧 Files skipped from review as they are similar to previous changes (11)
- src/commands/acl-setuser.json
- src/commands/acl-roles.json
- tests/assets/role.acl
- src/commands/acl-getuser.json
- tests/unit/acl.tcl
- src/commands/acl-delrole.json
- src/commands/acl-setrole.json
- src/commands/acl-getrole.json
- src/config.c
- src/commands.def
- src/acl.c
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 1
🧹 Nitpick comments (1)
tests/unit/acl-role.tcl (1)
255-258: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winVerify ordering and restored ACL semantics, not just presence.
These checks would pass if roles were emitted after users, or if
ACL SAVErestored role names but lost selectors or memberships. Assert all roles precede users, then afterACL LOADrecheck Alice’s membership and allowed/denied commands.Also applies to: 318-322, 369-374
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/unit/acl-role.tcl` around lines 255 - 258, Strengthen the ACL LIST assertions in the role-list tests and the corresponding checks at the other referenced locations: verify every role entry appears before user entries, rather than only checking that one role exists. After ACL LOAD, reassert Alice’s role membership plus her allowed and denied command behavior, preserving the existing ACL SAVE/LOAD setup and test flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/server.h`:
- Around line 1309-1312: Remove the unrelated declarations and field from
src/server.h: delete client::qb_applied, getStringObjectLen, sdsEncodedObject,
the object accessor declarations, zzlValidateScores, initLibbacktraceFrameState,
and configInfoCommand at the specified consolidated sites; make no other
changes.
---
Nitpick comments:
In `@tests/unit/acl-role.tcl`:
- Around line 255-258: Strengthen the ACL LIST assertions in the role-list tests
and the corresponding checks at the other referenced locations: verify every
role entry appears before user entries, rather than only checking that one role
exists. After ACL LOAD, reassert Alice’s role membership plus her allowed and
denied command behavior, preserving the existing ACL SAVE/LOAD setup and test
flow.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a6fba351-73b3-418a-ae08-ba8dbfbd4d07
📒 Files selected for processing (13)
src/acl.csrc/commands.defsrc/commands/acl-delrole.jsonsrc/commands/acl-getrole.jsonsrc/commands/acl-getuser.jsonsrc/commands/acl-roles.jsonsrc/commands/acl-setrole.jsonsrc/commands/acl-setuser.jsonsrc/config.csrc/server.htests/assets/role.acltests/unit/acl-role.tcltests/unit/acl.tcl
🚧 Files skipped from review as they are similar to previous changes (11)
- src/commands/acl-setuser.json
- src/commands/acl-roles.json
- tests/assets/role.acl
- src/commands/acl-getuser.json
- tests/unit/acl.tcl
- src/commands/acl-delrole.json
- src/commands/acl-setrole.json
- src/commands/acl-getrole.json
- src/config.c
- src/commands.def
- src/acl.c
🛑 Comments failed to post (1)
src/server.h (1)
1309-1312: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Remove unrelated changes from this PR.
These additions appear to be unrelated to the ACL role support feature. As per coding guidelines, avoid unrelated refactors in the same change to keep the PR focused and easy to backport.
src/server.h#L1309-L1312: Remove theqb_appliedfield from theclientstruct if it belongs to a different feature.src/server.h#L3156-L3156: Remove thegetStringObjectLendeclaration.src/server.h#L3187-L3187: Remove thesdsEncodedObjectmacro.src/server.h#L3201-L3210: Remove the object accessor function declarations.src/server.h#L3436-L3436: Remove thezzlValidateScoresdeclaration.src/server.h#L3482-L3484: Remove theinitLibbacktraceFrameStatedeclaration.src/server.h#L4149-L4149: Remove theconfigInfoCommanddeclaration.📍 Affects 1 file
src/server.h#L1309-L1312(this comment)src/server.h#L3156-L3156src/server.h#L3187-L3187src/server.h#L3201-L3210src/server.h#L3436-L3436src/server.h#L3482-L3484src/server.h#L4149-L4149🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/server.h` around lines 1309 - 1312, Remove the unrelated declarations and field from src/server.h: delete client::qb_applied, getStringObjectLen, sdsEncodedObject, the object accessor declarations, zzlValidateScores, initLibbacktraceFrameState, and configInfoCommand at the specified consolidated sites; make no other changes.Source: Coding guidelines
Signed-off-by: Yang Zhao <zymy701@gmail.com>
Signed-off-by: Yang Zhao <zymy701@gmail.com>
Signed-off-by: Yang Zhao <zymy701@gmail.com>
Signed-off-by: Yang Zhao <zymy701@gmail.com>
Signed-off-by: Yang Zhao <zymy701@gmail.com>
There was a problem hiding this comment.
Great progress so far. I've left few comments. Need to take another pass at the src/ changes and then the tests. Claude found few issues, please have a look at those as well.
Critical: reproduced crashes
1. Wrong dictType — stringSetDictType is case-insensitive, ACL names are case-sensitive
src/acl.c:466,159 use stringSetDictType, which is dictCStrCaseHash / dictCStrKeyCaseCompare (src/server.c:803). Role and user names in Users/Roles are compared case-sensitively (rax), so the two views disagree.
Reproduced (ACL SETROLE Cache … + ACL SETROLE cache …, both listed by ACL ROLES):
ACL SETUSER u1 on >p +@role:Cache +@role:cache
ACL GETUSER u1 → roles: Cache # "cache" silently dropped
And on the members side it is a use-after-free:
ACL SETUSER alice on >p +@role:rr
ACL SETUSER ALICE on >p +@role:rr
ACL GETROLE rr → members: alice # ALICE missing (dictAdd collided, sdsdup leaked)
ACL DELUSER alice
ACL DELROLE rr → 1 # should fail: ALICE still references rr
ACL LIST → server crash
Fix: use a case-sensitive sds-keyed dictType (same shape as migrateCacheDictType: dictSdsHash / dictSdsKeyCompare / dictEntryDestructorSdsKey), for both roles and members.
2. ACL LOAD with a role on the default user → dangling role pointer
ACLCopyUser(DefaultUser, new_default) then ACLFreeUser(new_default) (src/acl.c:3163-3164) both key members by the name "default":
- ACLCopyRoles does dictAdd(r->members, sdsdup("default"), DefaultUser) → DICT_ERR, because new_default already registered "default". Return value is ignored; the sds leaks.
- ACLFreeUser(new_default) → ACLUserClearRoles → dictDelete(r->members, "default") removes the entry outright.
Net: DefaultUser->roles = {r} but r->members = {}. Reproduced with a 2-line ACL file (role r ~* &* +@all / user default on nopass ~* &* +@all +@role:r):
ACL GETROLE r → members: (empty) # wrong
ACL DELROLE r → 1 # should fail
ACL GETUSER default → SIGSEGV in aclCommand (si_code 2, addr 0xc044e33f03f853c5)
Root cause is broader than the default user: ACLCopyRoles (src/acl.c:97-98) ignores both dictAdd return values. Keying members by the user * pointer, or asserting on dictAdd andhandling the replace case, would
make this class of bug impossible. (Other ACLCopyUser callers escape only because ACLCreateUnlinkedUser gives the temp user a distinct __fakeuser:N__ name — that's an accidentalinvariant worth a comment at
minimum.)
High severity
Net: DefaultUser->roles = {r} but r->members = {}. Reproduced with a 2-line ACL file (role r ~* &* +@all / user default on nopass ~* &* +@all +@role:r):
ACL GETROLE r → members: (empty) # wrong
ACL DELROLE r → 1 # should fail
ACL GETUSER default → SIGSEGV in aclCommand (si_code 2, addr 0xc044e33f03f853c5)
Root cause is broader than the default user: ACLCopyRoles (src/acl.c:97-98) ignores both dictAdd return values. Keying members by the user * pointer, or asserting on dictAdd andhandling the replace case, would make this class of bug impossible. (Other ACLCopyUser callers escape only because ACLCreateUnlinkedUser gives the temp user a distinct __fakeuser:N__
name — that's an accidental invariant worth a comment at minimum.)
3. Role selectors are skipped by ACLRecomputeCommandBitsFromCommandRulesAllUsers
src/acl.c:895 iterates only the Users rax. It's called on module load and unload (src/module.c:13427,13682) precisely to keep per-selector command bits in sync with command-ID
churn. Role selectors are never recomputed, so:
- a role granting +@read won't pick up newly registered module commands in that category;
- after a module unload, a role's stale command bits can survive and grant access to a different command that later reuses the freed command ID — a silent privilege escalation.
Fix: iterate Roles too (and rename the function or add a role loop).
| @@ -0,0 +1,38 @@ | |||
| { | |||
| "DELROLE": { | |||
| "summary": "Deletes one or more ACL roles. Fails if any role has members.", | |||
There was a problem hiding this comment.
| "summary": "Deletes one or more ACL roles. Fails if any role has members.", | |
| "summary": "Deletes one or more ACL roles. Fails if any role has users.", |
| "DELROLE <rolename>", | ||
| " Delete a role (must have no members).", |
There was a problem hiding this comment.
I would like to avoid a new noun member. I would rather call out user.
| "DELROLE <rolename>", | |
| " Delete a role (must have no members).", | |
| "DELROLE <rolename>", | |
| " Delete a role (must have no users).", |
There was a problem hiding this comment.
Wondering whether we should update the user-facing text only or the struct field as well?
Roles and users share the same user struct, so if we rename the field to users we would end up with user->users, which might be a bit ambiguous? That's why I went with u->roles & r->members to keep the two directions of the membership distinguishable.
Signed-off-by: Yang Zhao <zymy701@gmail.com>
Signed-off-by: Yang Zhao <zymy701@gmail.com>
Signed-off-by: Yang Zhao <zymy701@gmail.com>
Signed-off-by: Yang Zhao <zymy701@gmail.com>
Signed-off-by: Yang Zhao <zymy701@gmail.com>
|
@valkey-io/core-team Please 👍 / 👎 for the major decision here. The new permission model is simpler, these are really just named selectors now, which is probably a bit more straightforward of an idea. |
|
Not a review of the whole PR, only the part that overlaps with A role rule change does not reach the digest. Alice gained The second one only turns up once you fix the first. Role names are checked against commands and categories but not against user names, so Both are covered by hashing the full static void ACLDigestAddTable(rax *table, const char *keyword, unsigned char *digest) {
raxIterator ri;
raxStart(&ri, table);
raxSeek(&ri, "^", NULL, 0);
while (raxNext(&ri)) {
user *u = ri.data;
robj *rules = ACLDescribeUser(u);
sds rulestr = objectGetVal(rules);
unsigned char entry[SHA256_BLOCK_SIZE];
SHA256_CTX ctx;
sha256_init(&ctx);
sha256_update(&ctx, (unsigned char *)keyword, strlen(keyword));
sha256_update(&ctx, (unsigned char *)u->name, sdslen(u->name));
sha256_update(&ctx, (unsigned char *)" ", 1);
sha256_update(&ctx, (unsigned char *)rulestr, sdslen(rulestr));
sha256_final(&ctx, entry);
decrRefCount(rules);
for (int j = 0; j < SHA256_BLOCK_SIZE; j++) digest[j] ^= entry[j];
}
raxStop(&ri);
}
static sds ACLDigest(void) {
unsigned char digest[SHA256_BLOCK_SIZE] = {0};
ACLDigestAddTable(Roles, "role ", digest);
ACLDigestAddTable(Users, "user ", digest);
return ACLHexDigest(digest, SHA256_BLOCK_SIZE);
}With that on top of the cherry-pick the digest moves on a role edit, and There is nothing to do here while the two are separate branches, it just needs picking up by whichever lands second. #4446 is approved and waiting on the major decision clock, so that may well be this one. I can send the change as a patch here, or open it as a follow-up once this merges, whichever you prefer. |
|
I see 5 thumbs up from TSC members (out of 9) on the vote above at #3967 (comment) so we can consider the API approved. Code review still pending. |
Summary
Closes #3726
Implements ACL roles as described in the issue. A role is a named, reusable set of ACL selectors that can be assigned to multiple users. This allows operators to define permission policies once and apply them to many users, avoiding per-user rule duplication.
Core design
userstruct withUSER_FLAG_ROLEset, stored in a separateRolesradix tree, so it reuses the existing selector machinery.New commands
All new in 9.2.0.
ACL SETROLE <name> <rules...>ACL DELROLE <name> [name ...]ACL DELUSER)ACL GETROLE <name>ACL ROLESACL SETUSER <user> +@role:<name>ACL SETUSER <user> -@role:<name>ACL file and config support
rolekeyword or inline invalkey.conf.valkey.confmay reference commands and categories that a module registers later — roles are loaded after modules, same as users.ACL SAVEwrites roles before users.ACL LISToutputs roles before users.CONFIG REWRITEpersists the in-memory roles, so roles created or deleted at runtime survive a restart.Tests
1. ACL commands (runtime)
SETROLE,DELROLE,GETROLE,ROLES), including deleting multiple roles at once+@role:<name>,-@role:<name>), including empty and non-existent role namesACL DRYRUNrespects role selectors; role changes are immediately visible to membersSETROLEorSETUSERrevokes channel accessACL LISTincludes roles; user reset clears role membershipsSORTBY/GEThonour full key access granted only through a role2. ACL file (
aclfileoption)ACL SAVEandACL LOADpreserve rolesACL LOAD, and a role it holds cannot be deletedroleline without a name, duplicate role definitions3. Inline directives in
valkey.confroleanduserdirectives loaded from the main config; role permissions effective for users defined in the same configCONFIG REWRITEpersists roles created at runtime and drops roles deleted at runtime4. Module API
VM_ACLCheckKeyPermissionsandVM_ACLCheckChannelPermissionshonour grants that reach the user only through a rolevalkey.confcan reference a module commandBackwards compatibility
valkey.conffiles withoutroledirectives work as before.ACL GETUSERoutput adds a newrolesfield but all other fields remain unchanged.ACL LISTprepends role entries before user entries; existing user entry format is unchanged.