Skip to content

Add ACL role support#3967

Open
yang-z-o wants to merge 4 commits into
valkey-io:unstablefrom
yang-z-o:acl-role
Open

Add ACL role support#3967
yang-z-o wants to merge 4 commits into
valkey-io:unstablefrom
yang-z-o:acl-role

Conversation

@yang-z-o

Copy link
Copy Markdown
Contributor

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

  • A role holds its own list of selectors (same structure as user selectors).
  • Users hold pointers to role objects. Permission checks iterate user's own selectors first, then each role's selectors (OR logic).
  • Role updates modify selectors in-place, immediately visible to all members (zero-cost propagation).
  • Users can only add permissions on top of roles, not restrict them (consistent with existing multi-selector OR semantics).

New commands

Command Description
ACL SETROLE <name> <rules...> Create or update a role
ACL DELROLE <name> [name ...] Delete one or more roles (fails if any have members)
ACL GETROLE <name> Show role permissions and members
ACL ROLES List all role names
ACL SETUSER <user> +@role:<name> Assign a role to a user
ACL SETUSER <user> -@role:<name> Remove a role from a user

ACL file and config support

  • Roles can be defined in the ACL file with the role keyword or inline in valkey.conf.
  • ACL file loading uses a two-pass approach (roles first, then users) so definition order doesn't matter.
  • ACL SAVE writes roles before users.
  • ACL LIST outputs roles before users.

Tests

1. ACL commands (runtime)

  • Role CRUD (SETROLE, DELROLE, GETROLE, ROLES)
  • Role rule validation (rejects passwords, on/off, nested roles)
  • Role assignment and removal (+@role:<name>, -@role:<name>)
  • Permission checks via ACL DRYRUN respect role selectors
  • Role changes are immediately visible to members
  • ACL LIST includes roles
  • User reset clears role memberships
  • Referencing non-existent role fails
  • Multiple roles with OR logic (each role is a separate selector)
  • Role with multiple selectors
  • User permissions add on top of roles (OR logic)
  • User cannot restrict role permissions
  • Deleting multiple roles at once
  • Role name validation (no spaces)

2. ACL file (aclfile option)

  • Roles and users loaded from dedicated ACL file
  • Definition order doesn't matter (two-pass loading)
  • User-level permissions add on top of role from ACL file
  • ACL SAVE and ACL LOAD preserve roles

3. Inline directives in valkey.conf

  • Both role and user directives loaded from main config
  • Role permissions effective for users defined in same config

Backwards compatibility

  • Existing ACL files without roles load correctly with no changes required.
  • Existing valkey.conf files without role directives work as before.
  • ACL GETUSER output adds a new roles field but all other fields remain unchanged.
  • ACL LIST prepends role entries before user entries; existing user entry format is unchanged.

Signed-off-by: Yang Zhao <zymy701@gmail.com>
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 71d1440a-68bc-455b-82bf-91dadbeef4f5

📥 Commits

Reviewing files that changed from the base of the PR and between 063103e and 7f27dc0.

📒 Files selected for processing (1)
  • tests/unit/acl-role.tcl

📝 Walkthrough

Walkthrough

This PR implements first-class ACL roles in Valkey by introducing named, reusable permission sets that users can be assigned to. The implementation adds role data structures, role-aware user lifecycle management, role-to-user binding, authorization updates, config/ACL file persistence, new role administration commands, and comprehensive test coverage.

Changes

ACL Roles Feature

Layer / File(s) Summary
Role data structures and initialization
src/server.h, src/acl.c
New role struct with selectors and members list; user struct extended with roles list. Global Roles radix tree and RolesToLoad staging list added. Error API renamed from ACLSetUserStringError to ACLSetStringError. All initialized during ACLInit.
User lifecycle with role membership tracking
src/acl.c
User creation initializes roles list. ACLFreeUser unlinks user from all roles' member lists before freeing. ACLCopyUser duplicates role memberships. Core role functions added: lookup by name, create/free, cache invalidation, selector setting, and description generation.
User-role binding via ACL SETUSER
src/acl.c
ACLSetUser parses +@role:<name> and -@role:<name> to assign/remove roles, maintaining bidirectional links in both user's role list and role's member list. reset now clears role memberships. ACLDescribeUser appends role memberships to output.
Authorization logic incorporating role selectors
src/acl.c
ACLCheckAllUserCommandPerm checks role-derived selectors alongside direct user selectors for command authorization using OR logic. Pub/sub "allchannels" check treats role selectors as granting unrestricted channel access. Upcoming-channel collection aggregates channels from both user and role selectors.
Configuration and startup role loading
src/config.c, src/acl.c
config.c parses role <...> directives via ACLAppendRoleForLoading. New ACLLoadConfiguredRoles loads and validates staged roles. ACLLoadUsersAtStartup validates roles are not mixed between config and ACL file, loads configured roles before users. ACLLoadRolesAtStartup placeholder added.
ACL file loading with role-aware two-pass parsing
src/acl.c
ACL LOAD saves/restores old_roles for rollback. Two-pass parsing: first pass processes role lines only (validates syntax and applies selectors), second pass processes user lines (with additional keyword validation). Rollback cleanup frees both Roles and Users consistently.
ACL file persistence with roles
src/acl.c
ACL SAVE writes role definitions first via ACLDescribeRole, then user definitions, ensuring roles are persisted before users that reference them.
ACL command output and error handling updates
src/acl.c
Error formatting for SETUSER staging and user loading consolidated to use ACLSetStringError. ACL GETUSER output includes user's role memberships as roles field. ACL LIST lists all roles with descriptions before users. User-loading errno tolerance expanded.
ACL role administration subcommands
src/acl.c
New subcommands: SETROLE (create/update roles with rules), DELROLE (delete roles, fails if members exist), GETROLE (return selectors and member list), ROLES (list all role names). ACL HELP updated to document new subcommands.
Command definitions and dispatch wiring
src/commands.def, src/commands/acl-*.json
New metadata in commands.def for DELROLE, GETROLE, ROLES, SETROLE with argument tables. ACL GETUSER and ACL SETUSER history entries added for version 10.0.0. New ACL_Subcommands entries register role subcommands. New JSON files define command schemas: acl-delrole.json, acl-getrole.json, acl-roles.json, acl-setrole.json. acl-getuser.json and acl-setuser.json updated with roles history and output schema.
Comprehensive role functionality tests
tests/assets/role.acl, tests/unit/acl-role.tcl, tests/unit/acl.tcl
Test asset defines customer and readonly roles and user assignments. Unit tests cover: role lifecycle (create, update, retrieve, delete), user-role binding/removal, permission effectiveness and ACL DRYRUN checks, role listing and membership visibility, multi-role OR semantics, selector denial semantics, user-level permission overlays, role name validation, loading from ACL files and config directives, ACL SAVE/ACL LOAD round-trip persistence. Existing test updated to expect consolidated error message format.

Sequence Diagram(s)

sequenceDiagram
  participant User as User (with roles)
  participant ACLCheck as ACLCheckAllUserCommandPerm
  participant RoleSelector as Role Selectors
  participant UserSelector as User Selectors
  participant Result as Allow/Deny
  
  User->>ACLCheck: Check command permission
  ACLCheck->>UserSelector: Evaluate user's own selectors
  UserSelector-->>ACLCheck: Allowed/Denied
  ACLCheck->>RoleSelector: For each assigned role, evaluate selectors
  RoleSelector-->>ACLCheck: Allowed/Denied
  ACLCheck->>Result: OR all results (any allow = allowed)
  Result-->>User: Permission decision
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • zuiderkwast
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Add ACL role support' directly and concisely summarizes the main change - introducing ACL roles as a new feature to the ACL system.
Description check ✅ Passed The PR description comprehensively explains the implementation of ACL roles, including core design, new commands, file/config support, tests, and backward compatibility.
Linked Issues check ✅ Passed All major requirements from #3726 are implemented: role CRUD commands (SETROLE, DELROLE, GETROLE, ROLES), role assignment/removal via SETUSER, permission checking with OR logic, flat role model, ACL file/config support, and output format updates.
Out of Scope Changes check ✅ Passed All code changes directly support ACL role implementation. No out-of-scope modifications detected across src/acl.c, src/server.h, src/config.c, command definitions, and tests.
Docstring Coverage ✅ Passed Docstring coverage is 96.77% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 win

Add a 10.0.0 history entry for the new ACL SETUSER role syntax.

ACL_SETUSER_History still stops at 9.1.0, but this PR adds +@role:<name> / -@role:<name> handling. That leaves generated metadata and COMMAND DOCS without any record of the new public syntax.

Based on PR objectives: role assignment/removal is exposed through ACL SETUSER using +@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 value

Consider adding rule validation for consistency with user loading.

Unlike ACLAppendUserForLoading which validates rules against a fake user (tolerating unknown commands/roles), this function only validates selector parenthesis matching via ACLMergeSelectorArguments but doesn't validate the actual rules.

While invalid rules will still be caught during ACLLoadConfiguredRoles at startup, validating here would:

  1. Provide earlier, more precise error reporting (line number from config parsing)
  2. 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 value

Minor: 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

📥 Commits

Reviewing files that changed from the base of the PR and between f3bdf50 and 0b3c583.

📒 Files selected for processing (11)
  • src/acl.c
  • src/commands.def
  • src/commands/acl-delrole.json
  • src/commands/acl-getrole.json
  • src/commands/acl-getuser.json
  • src/commands/acl-roles.json
  • src/commands/acl-setrole.json
  • src/config.c
  • src/server.h
  • tests/assets/role.acl
  • tests/unit/acl-role.tcl

Comment thread src/commands.def
Comment thread src/config.c
Comment thread src/config.c
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.15044% with 40 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.76%. Comparing base (719268e) to head (7f27dc0).
⚠️ Report is 14 commits behind head on unstable.

Files with missing lines Patch % Lines
src/acl.c 90.97% 40 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           unstable    #3967      +/-   ##
============================================
+ Coverage     76.68%   76.76%   +0.08%     
============================================
  Files           162      162              
  Lines         80753    81230     +477     
============================================
+ Hits          61929    62360     +431     
- Misses        18824    18870      +46     
Files with missing lines Coverage Δ
src/commands.def 100.00% <ø> (ø)
src/config.c 79.08% <100.00%> (+0.15%) ⬆️
src/server.h 100.00% <ø> (ø)
src/acl.c 92.50% <90.97%> (-0.21%) ⬇️

... and 22 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@roshkhatri roshkhatri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overall looks good. few dedup to start with

Comment thread src/acl.c Outdated
Comment thread src/acl.c Outdated
Comment thread src/acl.c
Comment thread src/server.h Outdated
Comment thread src/acl.c
Comment thread src/acl.c Outdated
Comment thread src/acl.c Outdated
Signed-off-by: Yang Zhao <zymy701@gmail.com>
@yang-z-o yang-z-o changed the title ACL Role Add ACL role support Jun 16, 2026
@yang-z-o yang-z-o requested review from hpatro and madolson June 16, 2026 00:55
Signed-off-by: Yang Zhao <zymy701@gmail.com>
@yang-z-o yang-z-o requested a review from roshkhatri June 16, 2026 04:33
Signed-off-by: Yang Zhao <zymy701@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[NEW] ACL Role

2 participants