ACL API
Purely functional interface for the ACL API (ACL ...), used to
inspect and manage Redis Access Control Lists —
users, their passwords, and the commands, keys and channels they are allowed to touch.
The algebra is AclCommands[F] (note: it has no key/value type parameters, since ACL operates on
usernames and rule strings rather than your codec’s K/V). It is the union of two smaller algebras:
AclManagement[F] (server-wide: WHOAMI, CAT, GENPASS, LIST, LOAD, SAVE, LOG) and
AclUserManagement[F] (per-user: USERS, GETUSER, SETUSER, DELUSER).
ACL Commands usage
Once you have acquired a connection you can start using it:
import dev.profunktor.redis4cats.effects.{ AclCategory, RawCommand }
import dev.profunktor.redis4cats.effects.AclSetUserRule._
commandsApi.use { redis => // AclCommands[IO]
for {
_ <- redis.aclWhoAmI // the current connection's user, e.g. "default"
_ <- redis.aclCat // Set[AclCategory] — the available categories
_ <- redis.aclCat(AclCategory.Read) // Set[String] — command names in the "read" category
_ <- redis.aclSetUser(
"alice",
List(
On,
AddPassword("s3cret"),
NoCommands,
AddCommand(RawCommand("get")),
AddCategory(AclCategory.Read),
KeyPattern("app:*"),
ChannelPattern("news.*")
)
)
_ <- redis.aclUsers // List[String] — all configured usernames
alice <- redis.aclGetUser("alice") // Option[AclUser]
_ <- redis.aclDelUser("alice") // Long — number of users actually deleted
} yield alice
}
Creating and modifying users with SETUSER
aclSetUser takes the username and an ordered List[AclSetUserRule]; the rules are applied in
order, exactly like the arguments to the ACL SETUSER command. The rule type is a closed sum type, so
illegal combinations are caught at compile time rather than as a runtime error from Redis:
| Rule | ACL SETUSER equivalent |
|---|---|
On / Off |
on / off |
Reset |
reset |
AddPassword(p) / RemovePassword(p) |
>p / <p |
AddHashedPassword(h) / RemoveHashedPassword(h) |
#h / !h |
NoPass / ResetPass |
nopass / resetpass |
AllKeys / ResetKeys / KeyPattern(p) |
allkeys (~*) / resetkeys / ~p |
AllChannels / ResetChannels / ChannelPattern(p) |
allchannels (&*) / resetchannels / &p |
AllCommands / NoCommands |
allcommands (+@all) / nocommands (-@all) |
AddCommand(c) / RemoveCommand(c) |
+c / -c |
AddCategory(cat) / RemoveCategory(cat) |
+@cat / -@cat |
A few notes on the typed arguments:
KeyPattern/ChannelPatterntake the bare glob (e.g."app:*","news.*"); the~/&prefix is added for you.AddCategory/RemoveCategorytake anAclCategory— a sealed enumeration of the categories Redis understands (AclCategory.Read,AclCategory.Write,AclCategory.Dangerous, …), so a typo cannot compile.AddCommand/RemoveCommandtake aRawCommand(name). The set of Redis commands is open (modules and new versions add commands), so it is modelled as a string newtype rather than a closed enum. A name the driver does not recognise fails withAclError.UnknownCommandwhen the rule is applied — it never throws.
Reading a user with GETUSER
aclGetUser returns Option[AclUser]. None means the user does not exist — and only that;
a malformed reply fails instead (see Errors below). AclUser mirrors the fields Redis reports:
final case class AclUser(
flags: List[String], // e.g. List("on", "nopass")
passwords: List[String], // SHA-256 hashes of the user's passwords
commands: String, // the command rule string, e.g. "-@all +get +@read"
keys: String, // the key rule string, e.g. "~app:*"
channels: String, // the channel rule string, e.g. "&news.*"
selectors: List[AclSelector]
)
// Redis 7+ selectors, each its own commands/keys/channels rule strings
final case class AclSelector(commands: String, keys: String, channels: String)
The commands/keys/channels fields are the rule strings exactly as Redis renders them (so the
~/&/+/- prefixes are present here, on the way out — unlike the bare patterns you pass in).
Categories and commands
aclCat returns the set of available categories as AclCategory values; aclCat(category) returns the
command names (lowercase) that belong to a category:
commandsApi.use { redis =>
for {
categories <- redis.aclCat // Set[AclCategory]
readCmds <- redis.aclCat(AclCategory.Read) // Set[String], e.g. contains "get"
} yield (categories, readCmds)
}
The ACL log
aclLog returns recent ACL security events (auth failures, denied commands/keys/channels) as
field/value maps; aclLogReset clears it:
commandsApi.use { redis =>
redis.aclLogReset >> redis.aclLog // List[Map[String, String]]
}
Errors
ACL failures surface as AclError (a Throwable, raised into F), never as thrown exceptions from
argument building or silent coercions:
AclError.UnknownCommand(name)— aRawCommandpassed toSETUSERis not known to the driver.AclError.DecodingFailure(msg)— anACL GETUSER/ACL LOGreply could not be decoded into the expected shape.
Because aclGetUser/aclLog decode replies as UTF-8 text, they assume the connection uses a
string-decoding codec. On a connection whose value codec produces non-string values (e.g.
Array[Byte]), these two commands fail with an AclError.DecodingFailure rather than returning a
partial result.
See also
For supplying credentials when connecting (as opposed to managing users on the server), see
Authentication on the Client page — in particular
RedisCredentials.UsernameAndPassword, which authenticates as a Redis 6+ ACL user.