The problem/use-case that the feature addresses
Extend ACL to include a way to restrict users to a specific database. This would make it possible to assign individual databases to a user, opening the possibiity for a single Redis instance to be multi-tenant.
Description of the feature
Craft an ACL that only allows a user to connect/select a specific database.
Alternatives you've considered
I tried the suggestion in #7368, but it does not work in all scenarios:
-
Create user
127.0.0.1:6379> ACL SETUSER alice on >password +@all ~* -select +select|5 OK -
Authenticating as that user and then selecting a database does work as expected:
127.0.0.1:6379> AUTH alice password OK 127.0.0.1:6379> select 1 (error) NOPERM this user has no permissions to run the 'select' command or its subcommand 127.0.0.1:6379> select 5 OK 127.0.0.1:6379[5]> SET test 1 OK -
It does not seem to work, however, when connecting via the CLI:
$ redis-cli --user alice --pass password -n 1 Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. 127.0.0.1:6379[1]> SET test 1 OK
Additional information
Currently the only way to do this is to spin up multiple Redis instances which isn't always desirable/feasible, especially when using a hosted solution that bills per instance. My use case is for ephemeral non-production web applications that need access to a single Redis database. Ideally these could get full access to a database on a single instance similar to how you might manage multiple tenants on MySQL or Postgres.
Comment From: madolson
@ipmb So, whether or not we plan to continue supporting databases is sort of up in the air. Is there any reason you can't move to an implementation based on key prefixes?
Comment From: ipmb
Prefixes may be possible, but depends on the software implementation. Connecting via a specific database is a universal thing.
Comment From: madolson
@itamarhaber @yossigo Do you have thoughts about if we should support this? I tried to convince salvatore about this before Redis 6 launched, and he seemed pretty convinced that databases shouldn't be included. It wouldn't be that hard of an implementation.
Comment From: hpatro
@madolson It feels like a bug to me, even though the SELECT command is restricted, the user can access the database in multiple ways. One is already mentioned by @ipmb and the other way Redis fails to handle this is provided below.
$ ./redis-cli
127.0.0.1:6379> ACL LIST
1) "user default on nopass ~* +@all"
127.0.0.1:6379> ACL SETUSER alice on >password +@all ~* -select +select|5
OK
127.0.0.1:6379> ACL LIST
1) "user alice on #5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8 ~* +@all -select +select|5"
2) "user default on nopass ~* +@all"
127.0.0.1:6379> ACL WHOAMI
"default"
127.0.0.1:6379> AUTH alice password
OK
127.0.0.1:6379> ACL WHOAMI
"alice"
127.0.0.1:6379> SET A 5
OK
Should we be restricting the database level access based on the current SELECT command or do you think that breaks compatibility ?
Comment From: oranagra
The problem in both these cases is that switching a user in an existing connection retains the selected database that the connection had before. we could, in theory reset it (not sure to what), and that would "solve" these cases.
But this (restricting access to the SELECT command) is still far from database level ACL. ideally for such a feature redis should know which databases each user is allowed to access, it may allow SELECTING them, but all attempts to access keys will be rejected.
Since redis is generally looking away from multiple databases, i'm not certain we wanna add them to ACL or not.
One other interesting idea that can "solve" this problem, and maybe a few others, is to allow ACL to define a script that is executed every time the user gets activated. This script can SELECT the right DB for that user, and other user specific settings. e.g one wild idea is to allow the CLIENT command set a connection specific output buffer limit.
Comment From: hpatro
@oranagra I was trying to make changes to handle the above. And I handled it in the auth flow. So, it falls back to the already authenticated user or client stays unauthenticated and can't proceed. Currently I have used SELECT as the command to restrict the access to the db but I'm displaying it in the ACL LIST entry via db[<allowed_access_to>].
For e.g.:
$% ./redis-cli -p 6379
127.0.0.1:6379> acl list
1) "user default on nopass ~* +@all dbs[*]"
2) "user test on nopass ~* +@all -select +select|1 +select|2 dbs[1,2]"
127.0.0.1:6379> auth test a
(error) UNAUTHORIZED user doesn't have permission to access the current db.
127.0.0.1:6379> acl whoami
"default"
127.0.0.1:6379> auth test a
(error) UNAUTHORIZED user doesn't have permission to access the current db.
127.0.0.1:6379> acl whoami
"default"
In the above case, the client fails to switch to the test and continues with the already authenticated user.
$% ./redis-cli -p 6379 --user test --pass test
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
Warning: AUTH failed
127.0.0.1:6379> acl whoami
"default"
In the above case it falls back to default user.
$% ./redis-cli -p 6379 --user test --pass test
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
Warning: AUTH failed
127.0.0.1:6379> acl whoami
(error) NOAUTH Authentication required.
In the above case default user is turned off. And the client won't be authenticated and can't proceed until authenticated with a valid user.
Let me know if this works.
Comment From: oranagra
@hpatro you mean that you added an ACL feature that allows declaring the allowed databases for each user, and when AUTH is called, you fail the auth if the currently selected database is not part of that list? (i'm not sure that's a good thing to do). did you also mean you changed what happens when auth fails? (AFAIK the behavior of sticking to whatever user previously existed is what redis already does).
regarding database ACL i see two possibilities. either we implement proper support for it, which means that even if the wrong db is selected, the user can't access it. or if we just want to block select, and also provide some means which will let ACL switch user on AUTH (not by default of course).
Comment From: hpatro
@oranagra I'm still using the SELECT command provided as part of the ACL SETUSER to determine which database the user has access to.
What I meant from auth flow modification is, I'm validating if user has permission to the db (based on the SELECT values) which it will land onto on successful authentication and if it is not having access it will throw an error ("UNAUTHORIZED user doesn't have permission to access the current db.") and continue with the already authenticated user.
I will submit the PR to help understand better.
Comment From: hpatro
@oranagra @madolson Restrict user to db access. Here is the partial code to restrict a particular user from accessing the db, currently I'm using the SELECT sub command as input from customer to restrict the user's database access. However, I feel it should be through some other independent flag in ACL SETUSER command like we do for keys, commands and channels.
Comment From: itamarhaber
Hello @hpatro
Thank you for the proposal and for investing the time in this discussion. After further discussing this, the team had decided not to advance with this. The reasoning follows - please let me know if it makes sense to you.
Redis had support for logical/shared/numbered databases from version 1.0. This feature proved to be a cause for numerous problems over the years, mostly because of its name imo (a known hard problem). By calling it "databases", instead of "namespaces" perhaps, developers develop certain expectations and make sometimes-incorrect assumptions about what it is in reality. Multi-tenancy is a good example of the feature's "misuse".
The original purpose of Redis' databases was to simplify the developer's work (i.e. a DB per app) by reducing their local admin needs and providing flexible out-of-the-box one-size-fits-all defaults. Internally, however, Redis is mostly oblivious to the notion of databases, and there's no per-DB anything: process, persistence, memory limits, eviction policy, and so forth are all shared. Other than for development purposes, a single application may choose to organize its keyspace using databases. Still, other than FLUSHDB and the more-recently added SWAPDB, there's almost no reason/use-cases for doing that.
However, because the feature was useful for the developers, it eventually ended up in some production environments and, in most cases, caused real damage. Specifically, databases are a bad choice for implementing a multi-tenant setup because you end up with your tenants stepping on each other's toes. That's not unlike what you'd get if you were to use per-tenant-prefixed keys. The recommended approach is a more resilient and manageable setup made of a dedicated (possibly sandboxed) Redis instance per tenant.
Redis 3.0 introduced the cluster, which doesn't support databases at all. The primary motivation for this decision was the eventual deprecation of the feature because of all the above. Given that, further development and support of this feature seems unreasonable at this stage.
Comment From: hpatro
Not a problem. All of these above point makes sense and seems the reason behind not having lot of these per database level features and clarifies other community request(s). Thanks for the detailed response.
Comment From: jiekun
@itamarhaber Thank you very much. Great explanation.
This is related to: why Redis cluster doesn't support multiple DB ?
I assume a lot of people is curios about this and did not find answer in doc and code annotations. Would you consider link/paste your reply to document and code annotation later? :)
Comment From: itamarhaber
@hpatro my pleasure :)
@2014BDuck - we're open for PRs, feel free to suggest anything. I'm not quite sure where you want this added to though as SELECT's docs already contain a (much more succinct, admittedly) notion of the above.
Comment From: oranagra
Closing as it was decided not to go down that road.