September 5, 2023·6 min read·engineeringdragonflyaclsecurity

Dragonfly introduces users and category-based permissions

Dragonfly 1.9 introduces named users and command-category permissions. Its initial ACL implementation exposes the difference between category grants, per-command policy, and durable configuration.

Giving two applications the same datastore password gives them the same identity at the server. Dragonfly's September 5 release introduces named users and category-based command permissions. It supports ACL SETUSER, DELUSER, WHOAMI, and LIST, with an explicit limitation that ACL configuration does not yet survive process restarts.

The interesting design choice is the granularity of the permission. This release associates users and commands with category bits. The validator permits an operation when those sets overlap. A category can represent a data type or a property of the operation, so understanding the overlap rule matters more than reading a permission name in isolation.

I value Dragonfly's ability to replace several cache processes with one larger process. Named identities make that consolidation more useful because applications no longer have to be represented by a single shared credential. The initial implementation still requires a precise account of what each grant allows.

A user has state separate from a connection

The user implementation maintains an active flag, password hash, and category mask. Updates can change these properties independently. Authentication checks both the active state and the supplied password, according to the registry implementation.

A newly constructed ordinary user begins inactive and with no categories, as the header shows. The registry separately creates the default user as active with all categories. Those initial conditions differ, so creating another username is not equivalent to cloning the default identity.

The password implementation hashes supplied bytes with SHA-256. The registry keeps user objects and exposes operations for authentication and credential lookup. That storage arrangement distinguishes the user's configured properties from the category information held on a connection after authentication.

An illustrative configuration creates an active user with a category grant:

redis-cli ACL SETUSER reader on '>example-password' +@read
redis-cli ACL LIST

The password is an example literal, and quoting prevents the shell from treating its leading greater-than sign as redirection. The rule grants the category named read. It does not add key-name restrictions or demonstrate persistence, neither of which is part of the initial parser shown in this release.

Category matching is an overlap test

The validator obtains a command's category mask and intersects it with the connection's allowed mask. The operation is allowed when the intersection is nonzero. It does not require the user to possess every category attached to the command.

The category definitions include operation categories such as read and write, alongside type categories such as string, hash, and list. Other bits describe administration, scripting, blocking, and transaction behaviour. A command can consequently belong to several categories that describe different aspects of the same operation.

This has a concrete consequence for policy composition. Granting a broad data-type category can permit operations beyond reads of that type if those commands carry the same category. Combining a read grant with a type grant expresses a union under this validator. It does not express an intersection requiring a command to be both a read and a member of that type.

Removing a category from a user's mask is also different from adding a permanent deny rule for every command carrying that category. If another remaining category overlaps a command, the validator can still permit it. The implementation evaluates the final bitmask, not a separate list of explicit prohibitions attached to each command.

The user-update implementation applies signed category changes in their supplied order. Additions set the category bits, while removals clear them. The resulting mask is what later permission checks observe. Order can therefore matter when the same category appears more than once in an update.

These details define what a category-based policy means in this version. A name such as read is useful shorthand, but the actual command membership and matching algorithm determine authority. Treating the syntax as a general policy language would imply capabilities that this mask representation does not contain.

The initial parser has a narrow vocabulary

The ACL command handler recognizes a password introduced by a greater-than sign, an on or off state, and signed category tokens. It rejects unrecognized parameters. It also rejects more than one password or multiple state settings in the same request.

This scope means that a Redis ACL example using key patterns or individual command rules cannot be assumed to work unchanged. The parser has explicit branches for the constructs it understands. Syntax familiar from a broader ACL implementation still needs an implementation here before it becomes meaningful.

Category spelling also comes from a versioned table. The table uses names including SORTED_SET and HYPERLOG, along with Dragonfly-specific categories for search, throttling, and JSON. The handler uppercases category parameters before looking them up. Matching the documented idea of a category is insufficient if the token does not match a recognized entry.

The list command renders each user's name, active state, password representation, and category information. That provides a way to inspect the configuration that the process currently holds. It does not create a durable configuration file simply by returning the rules as text.

I find the narrow initial grammar easier to reason about when it is stated plainly. It is enough to introduce distinct application identities with category grants. It is not yet evidence of full compatibility with every Redis ACL selector, password-management operation, or key-pattern rule.

Existing connections need updates too

Changing a user affects more than future authentication attempts. The ACL handler traverses connections and sends asynchronous category-update messages when an existing user's categories change. That mechanism connects the shared registry to permission state already held by open connections.

The main service also checks ACL status when executing commands. Its release-tagged implementation includes a check for rules changing between MULTI and EXEC. Queuing a command and later executing it are distinct moments, so the permission decision cannot always be treated as a property fixed at queue time.

The service accounts for batched execution contexts as well. A comment explains that a squashed command context must consult its owning connection because the temporary context does not copy the authenticated username. Optimizations that combine command work still need to use the identity of the original caller.

Deleting a user takes another path. The handler removes the registry entry and traverses connections to shut down those authenticated under that username. Deletion therefore has a direct effect on open sessions, in addition to preventing a later authentication lookup from finding the user.

These mechanisms explain why identity is part of execution state in a multithreaded server. A registry update needs to reach connections managed by different event-processing threads. The code uses asynchronous messages for permission updates and explicit connection traversal for deletion.

Restart behaviour is part of the policy

The release announcement explicitly says ACLs are not persisted across process restarts. A successful SETUSER changes the current process. It does not establish that the same identity and categories will exist after the next startup.

That limitation matters independently of dataset persistence. A snapshot can preserve application values while user configuration follows a different lifecycle. An operator cannot infer that saving the database also saves the ACL registry, and the release does not make that promise.

The default user's broad initial permissions are relevant to this distinction. The registry constructor establishes a default identity separately from later user updates. Recreating an application user after a restart is therefore a configuration task, not a consequence of loading its keys.

Version 1.9 also protects the HTTP endpoint using the token supplied through requirepass, according to the release notes. That is a separate authentication interaction from the command-category checks described here. The existence of named RESP users should not be taken as evidence that the HTTP endpoint applies the same category policy to every request.

The practical addition is a server-side identity model that reaches command validation and open connections. Its boundaries are equally concrete. Category matching uses any overlapping bit, the parser accepts a limited rule vocabulary, and configuration remains local to the current process lifetime.