summaryrefslogtreecommitdiff
path: root/src/acl.c
diff options
context:
space:
mode:
authorKarthikSubbarao <104098378+KarthikSubbarao@users.noreply.github.com>2023-03-15 15:18:42 -0700
committerGitHub <noreply@github.com>2023-03-15 15:18:42 -0700
commitf8a5a4f70ccada85943af90f6f2db3250ee50b27 (patch)
tree3ba462b1c1be69a4a04ba72c760d301f03db2cfc /src/acl.c
parent58285a6e9258412d0c99c8d0300620d43b209204 (diff)
downloadredis-f8a5a4f70ccada85943af90f6f2db3250ee50b27.tar.gz
Custom authentication for Modules (#11659)
This change adds new module callbacks that can override the default password based authentication associated with ACLs. With this, Modules can register auth callbacks through which they can implement their own Authentication logic. When `AUTH` and `HELLO AUTH ...` commands are used, Module based authentication is attempted and then normal password based authentication is attempted if needed. The new Module APIs added in this PR are - `RM_RegisterCustomAuthCallback` and `RM_BlockClientOnAuth` and `RedisModule_ACLAddLogEntryByUserName `. Module based authentication will be attempted for all Redis users (created through the ACL SETUSER cmd or through Module APIs) even if the Redis user does not exist at the time of the command. This gives a chance for the Module to create the RedisModule user and then authenticate via the RedisModule API - from the custom auth callback. For the AUTH command, we will support both variations - `AUTH <username> <password>` and `AUTH <password>`. In case of the `AUTH <password>` variation, the custom auth callbacks are triggered with “default” as the username and password as what is provided. ### RedisModule_RegisterCustomAuthCallback ``` void RM_RegisterCustomAuthCallback(RedisModuleCtx *ctx, RedisModuleCustomAuthCallback cb) { ``` This API registers a callback to execute to prior to normal password based authentication. Multiple callbacks can be registered across different modules. These callbacks are responsible for either handling the authentication, each authenticating the user or explicitly denying, or deferring it to other authentication mechanisms. Callbacks are triggered in the order they were registered. When a Module is unloaded, all the auth callbacks registered by it are unregistered. The callbacks are attempted, in the order of most recently registered callbacks, when the AUTH/HELLO (with AUTH field is provided) commands are called. The callbacks will be called with a module context along with a username and a password, and are expected to take one of the following actions: (1) Authenticate - Use the RM_Authenticate* API successfully and return `REDISMODULE_AUTH_HANDLED`. This will immediately end the auth chain as successful and add the OK reply. (2) Block a client on authentication - Use the `RM_BlockClientOnAuth` API and return `REDISMODULE_AUTH_HANDLED`. Here, the client will be blocked until the `RM_UnblockClient `API is used which will trigger the auth reply callback (provided earlier through the `RM_BlockClientOnAuth`). In this reply callback, the Module should authenticate, deny or skip handling authentication. (3) Deny Authentication - Return `REDISMODULE_AUTH_HANDLED` without authenticating or blocking the client. Optionally, `err` can be set to a custom error message. This will immediately end the auth chain as unsuccessful and add the ERR reply. (4) Skip handling Authentication - Return `REDISMODULE_AUTH_NOT_HANDLED` without blocking the client. This will allow the engine to attempt the next custom auth callback. If none of the callbacks authenticate or deny auth, then password based auth is attempted and will authenticate or add failure logs and reply to the clients accordingly. ### RedisModule_BlockClientOnAuth ``` RedisModuleBlockedClient *RM_BlockClientOnAuth(RedisModuleCtx *ctx, RedisModuleCustomAuthCallback reply_callback, void (*free_privdata)(RedisModuleCtx*,void*)) ``` This API can only be used from a Module from the custom auth callback. If a client is not in the middle of custom module based authentication, ERROR is returned. Otherwise, the client is blocked and the `RedisModule_BlockedClient` is returned similar to the `RedisModule_BlockClient` API. ### RedisModule_ACLAddLogEntryByUserName ``` int RM_ACLAddLogEntryByUserName(RedisModuleCtx *ctx, RedisModuleString *username, RedisModuleString *object, RedisModuleACLLogEntryReason reason) ``` Adds a new entry in the ACL log with the `username` RedisModuleString provided. This simplifies the Module usage because now, developers do not need to create a Module User just to add an error ACL Log entry. Aside from accepting username (RedisModuleString) instead of a RedisModuleUser, it is the same as the existing `RedisModule_ACLAddLogEntry` API. ### Breaking changes - HELLO command - Clients can now only set the client name and RESP protocol from the `HELLO` command if they are authenticated. Also, we now finish command arg validation first and return early with a ERR reply if any arg is invalid. This is to avoid mutating the client name / RESP from a command that would have failed on invalid arguments. ### Notable behaviors - Module unblocking - Now, we will not allow Modules to block the client from inside the context of a reply callback (triggered from the Module unblock flow `moduleHandleBlockedClients`). --------- Co-authored-by: Madelyn Olson <34459052+madolson@users.noreply.github.com>
Diffstat (limited to 'src/acl.c')
-rw-r--r--src/acl.c47
1 files changed, 38 insertions, 9 deletions
diff --git a/src/acl.c b/src/acl.c
index e5dcf56b9..e225b2a39 100644
--- a/src/acl.c
+++ b/src/acl.c
@@ -1406,24 +1406,50 @@ int ACLCheckUserCredentials(robj *username, robj *password) {
return C_ERR;
}
+/* If `err` is provided, this is added as an error reply to the client.
+ * Otherwise, the standard Auth error is added as a reply. */
+void addAuthErrReply(client *c, robj *err) {
+ if (clientHasPendingReplies(c)) return;
+ if (!err) {
+ addReplyError(c, "-WRONGPASS invalid username-password pair or user is disabled.");
+ return;
+ }
+ addReplyError(c, err->ptr);
+}
+
/* This is like ACLCheckUserCredentials(), however if the user/pass
* are correct, the connection is put in authenticated state and the
* connection user reference is populated.
*
- * The return value is C_OK or C_ERR with the same meaning as
- * ACLCheckUserCredentials(). */
-int ACLAuthenticateUser(client *c, robj *username, robj *password) {
+ * The return value is AUTH_OK on success (valid username / password pair) & AUTH_ERR otherwise. */
+int checkPasswordBasedAuth(client *c, robj *username, robj *password) {
if (ACLCheckUserCredentials(username,password) == C_OK) {
c->authenticated = 1;
c->user = ACLGetUserByName(username->ptr,sdslen(username->ptr));
moduleNotifyUserChanged(c);
- return C_OK;
+ return AUTH_OK;
} else {
addACLLogEntry(c,ACL_DENIED_AUTH,(c->flags & CLIENT_MULTI) ? ACL_LOG_CTX_MULTI : ACL_LOG_CTX_TOPLEVEL,0,username->ptr,NULL);
- return C_ERR;
+ return AUTH_ERR;
}
}
+/* Attempt authenticating the user - first through module based authentication,
+ * and then, if needed, with normal password based authentication.
+ * Returns one of the following codes:
+ * AUTH_OK - Indicates that authentication succeeded.
+ * AUTH_ERR - Indicates that authentication failed.
+ * AUTH_BLOCKED - Indicates module authentication is in progress through a blocking implementation.
+ */
+int ACLAuthenticateUser(client *c, robj *username, robj *password, robj **err) {
+ int result = checkModuleAuthentication(c, username, password, err);
+ /* If authentication was not handled by any Module, attempt normal password based auth. */
+ if (result == AUTH_NOT_HANDLED) {
+ result = checkPasswordBasedAuth(c, username, password);
+ }
+ return result;
+}
+
/* For ACL purposes, every user has a bitmap with the commands that such
* user is allowed to execute. In order to populate the bitmap, every command
* should have an assigned ID (that is used to index the bitmap). This function
@@ -3046,11 +3072,14 @@ void authCommand(client *c) {
redactClientCommandArgument(c, 2);
}
- if (ACLAuthenticateUser(c,username,password) == C_OK) {
- addReply(c,shared.ok);
- } else {
- addReplyError(c,"-WRONGPASS invalid username-password pair or user is disabled.");
+ robj *err = NULL;
+ int result = ACLAuthenticateUser(c, username, password, &err);
+ if (result == AUTH_OK) {
+ addReply(c, shared.ok);
+ } else if (result == AUTH_ERR) {
+ addAuthErrReply(c, err);
}
+ if (err) decrRefCount(err);
}
/* Set the password for the "default" ACL user. This implements supports for