redis集群的密码信息存在哪?是如何更新的?关于requirepass和masterauth的问题


问题:redis集群的密码信息存在哪?是如何更新的?
在使用redis集群做测试的时候,因为之前的集群加过密码,后面新加的节点没有加上密码,忘了之后又做了什么操作的,导致密码错误,集群密码一直报错,用不了。

之后经过这种折腾还是不行,只好手动重启各个节点,然后密码就失效了,不用输密码就能正常使用redis集群了。

事后分析应该是因为之前设置redis密码的时候并没有rewrite保存到配置文件中,所以重启之后失效了。

所以借这个机会,百度之后又看了一下源码,稍微深入的看了一下redis集群的密码认证原理。

一、关于requirepass和masterauth的问题

这是重启各个节点,能正常使用集群之后,尝试输入密码报的错误:

10.172.18.26:32495> auth 123456
(error) ERR Client sent AUTH, but no password is set
(1ms)
10.172.18.26:32495> auth
(error) ERR wrong number of arguments for 'auth' command
(1ms)

好像 AUTH 和 password 还不一样?
百度之后发现,requirepass和masterauth是不一样的,requirepass是配置在主节点的,masterauth是配置在从节点的,两边配置要一样从节点才能和主节点连接上进行主从复制。

出现 (error) ERR Client sent AUTH, but no password is set报错是因为客户端想输入密码,但是服务端并不需要密码验证。

参考别人的博客,其归纳如下:

  • 1、是否只设置requirepass就可以?masterauth是否需要同步设置?
    答:redis启用密码认证一定要requirepass和masterauth同时设置。
    如果主节点设置了requirepass登录验证,在主从切换,slave在和master做数据同步的时候首先需要发送一个ping的消息给主节点判断主节点是否存活,再监听主节点的端口是否联通,发送数据同步等都会用到master的登录密码,否则无法登录,log会出现响应的报错。也就是说slave的masterauth和master的requirepass是对应的,所以建议redis启用密码时将各个节点的masterauth和requirepass设置为相同的密码,降低运维成本。当然设置为不同也是可以的,注意slave节点masterauth和master节点requirepass的对应关系就行。
  • 2、requreipass和master的作用?
    masterauth作用:主要是针对master对应的slave节点设置的,在slave节点数据同步的时候用到。
    requirepass作用:对登录权限做限制,redis每个节点的requirepass可以是独立、不同的。

在看了别人的归纳之后,觉得基本已经回答了我的疑问。但是还是想看看源码,从源码方面了解一下这个过程,可能在之后跟人家讨论的时候能更好的沟(zhuang)通(bi)。

二、查看源码

2.1 requirepass的使用

在客户端读入一个完整的命令时要做什么操作?

int processCommand(redisClient *c) {
    /* The QUIT command is handled separately. Normal command procs will
     * go through checking for replication and QUIT will cause trouble
     * when FORCE_REPLICATION is enabled and would be implemented in
     * a regular command proc. */
    // 特别处理 quit 命令
    if (!strcasecmp(c->argv[0]->ptr,"quit")) {
        addReply(c,shared.ok);
        c->flags |= REDIS_CLOSE_AFTER_REPLY;
        return REDIS_ERR;
    }

    /* Now lookup the command and check ASAP about trivial error conditions
     * such as wrong arity, bad command name and so forth. */
    // 查找命令,并进行命令合法性检查,以及命令参数个数检查
    c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
    if (!c->cmd) {
        // 没找到指定的命令
        flagTransaction(c);
        addReplyErrorFormat(c,"unknown command '%s'",
            (char*)c->argv[0]->ptr);
        return REDIS_OK;
    } else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) ||
               (c->argc < -c->cmd->arity)) {
        // 参数个数错误
        flagTransaction(c);
        addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
            c->cmd->name);
        return REDIS_OK;
    }

    /* Check if the user is authenticated */
    // 检查认证信息
    if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand)
    {
        flagTransaction(c);
        addReply(c,shared.noautherr);
        return REDIS_OK;
    }
    ...
    ...
}

可以看到,在客户端读入完整的命令后,会先判断命令是否是“quit”,然后检查命令的合法性,不合法的话返回响应的错误。

之后,检查用户是否认证

  /* Check if the user is authenticated */
    // 检查认证信息
    // 如果服务端要求认证,该客户端没有认证,并且用户输入的命令没有通过auth认证时
    if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand)
    {
        //每次在入队命令出错时调用
        //将事务状态设为 DIRTY_EXEC ,让之后的 EXEC 命令失败
        flagTransaction(c);
        // 返回没有认证的错误信息
        addReply(c,shared.noautherr);
        return REDIS_OK;
    }

在redisServer结构体中定义了requirepass,用于存储设置的requirepass

struct redisServer {

    ...
    // 是否设置了密码
    char *requirepass;          /* Pass for AUTH command, or NULL */
    ....
}

其中的authCommand认证函数如下:(redis.c)

void authCommand(redisClient *c) {
    //如果服务端没要求密码,则回复如下错误消息
    if (!server.requirepass) {
        addReplyError(c,"Client sent AUTH, but no password is set");
    } 
    //使用time_independent_strcmp函数,判断用户传入的密码和服务端设置的密码是否相同
    // time_independent_strcmp 返回0 ,则说明两个值相同,返回成功消息
    else if (!time_independent_strcmp(c->argv[1]->ptr, server.requirepass)) {
      c->authenticated = 1;
      addReply(c,shared.ok);
    } 
    //密码错误,回复如下错误消息
    else {
      c->authenticated = 0;
      addReplyError(c,"invalid password");
    }
}

判断两个值是否相同的 time_independent_strcmp 函数如下:

// 用来判断传入的 a 和 b 是否相同,相同则返回 0
int time_independent_strcmp(char *a, char *b) {
    char bufa[REDIS_AUTHPASS_MAX_LEN], bufb[REDIS_AUTHPASS_MAX_LEN];
    /* The above two strlen perform len(a) + len(b) operations where either
     * a or b are fixed (our password) length, and the difference is only
     * relative to the length of the user provided string, so no information
     * leak is possible in the following two lines of code. */
    int alen = strlen(a);
    int blen = strlen(b);
    int j;
    int diff = 0;

    /* We can't compare strings longer than our static buffers.
     * Note that this will never pass the first test in practical circumstances
     * so there is no info leak. */
    if (alen > sizeof(bufa) || blen > sizeof(bufb)) return 1;

    memset(bufa,0,sizeof(bufa));        /* Constant time. */
    memset(bufb,0,sizeof(bufb));        /* Constant time. */
    /* Again the time of the following two copies is proportional to
     * len(a) + len(b) so no info is leaked. */
    memcpy(bufa,a,alen);
    memcpy(bufb,b,blen);

    /* Always compare all the chars in the two buffers without
     * conditional expressions. */
    for (j = 0; j < sizeof(bufa); j++) {
        diff |= (bufa[j] ^ bufb[j]);
    }
    /* Length must be equal as well. */
    diff |= alen ^ blen;
    return diff; /* If zero strings are the same. */
}

通过以上的分析,基本就可以确定requirepass是怎么回事了。

requirepass可以写在redis.conf配置文件或者在客户端命令行输入进行设置,设置之后redisServer结构体用 char *requirepass 保存它,当客户端登录的时候就进行验证。

验证通过就将client.authenticated 置为1,并返回成功消息;不通过就置为0,并返回错误消息。

2.2 masterauth的使用

同requirepass一样,masterauth也在redisServer结构体中进行定义了

struct redisServer {
    ...
    /* Replication (slave) */
    // 主服务器的验证密码
    char *masterauth;               /* AUTH with this password with master */
    // 主服务器的地址
    char *masterhost;               /* Hostname of master */
    // 主服务器的端口
    int masterport;                 /* Port of master */
    // 超时时间
    ...
}

masterauth是在进行主从复制是用于身份验证的,在 replication.c 中的使用如下:

// 从服务器用于同步主服务器的回调函数
void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
    char tmpfile[256], *err;
    int dfd, maxtries = 5;
    int sockerr = 0, psync_result;
    socklen_t errlen = sizeof(sockerr);
    REDIS_NOTUSED(el);
    REDIS_NOTUSED(privdata);
    REDIS_NOTUSED(mask);
    
    // 如果处于 SLAVEOF NO ONE 模式,那么关闭 fd
    if (server.repl_state == REDIS_REPL_NONE) {...}
    
    // 检查套接字错误
    if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1)
        sockerr = errno;
    
    //确认主服务器真的能访问
    if (server.repl_state == REDIS_REPL_CONNECTING) {...}
    
    // 接收 PONG 命令
    if (server.repl_state == REDIS_REPL_RECEIVE_PONG) {...}
    ...
   
    /* AUTH with the master if required. */
    // 进行身份验证 !!!
    if(server.masterauth) {
        err = sendSynchronousCommand(fd,"AUTH",server.masterauth,NULL);
        if (err[0] == '-') {
            redisLog(REDIS_WARNING,"Unable to AUTH to MASTER: %s",err);
            sdsfree(err);
            goto error;
        }
        sdsfree(err);
    }
    ...
}

其中的一个重要函数 sendSynchronousCommand 函数的定义实现如下:

/* Send a synchronous command to the master. Used to send AUTH and
 * REPLCONF commands before starting the replication with SYNC.
 *
 * The command returns an sds string representing the result of the
 * operation. On error the first byte is a "-".
 */
// Redis 通常情况下是将命令的发送和回复用不同的事件处理器来异步处理的
// 但这里是同步地发送然后读取
//(c)当无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
char * sendSynchronousCommand(int fd, ...) {
    va_list ap;
    //创建并返回一个只保存了空字符串 "" 的 sds
    sds cmd = sdsempty();
    char *arg, buf[256];

    /* Create the command to send to the master, we use simple inline
     * protocol for simplicity as currently we only send simple strings. */
    va_start(ap,fd);
    while(1) {
        arg = va_arg(ap, char*);
        if (arg == NULL) break;

        if (sdslen(cmd) != 0) cmd = sdscatlen(cmd," ",1);
        cmd = sdscat(cmd,arg);
    }
    //将长度为 len 的字符串 t 追加到 sds 的字符串末尾
    cmd = sdscatlen(cmd,"\r\n",2);

    /* Transfer command to the server. */
    // 发送命令到主服务器
    if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000) == -1) {
        sdsfree(cmd);
        //打印任意数量个字符串,并将这些字符串追加到给定 sds 的末尾
        return sdscatprintf(sdsempty(),"-Writing to master: %s",
                strerror(errno));
    }
    sdsfree(cmd);

    /* Read the reply from the server. */
    // 从主服务器中读取回复
    if (syncReadLine(fd,buf,sizeof(buf),server.repl_syncio_timeout*1000) == -1)
    {
        return sdscatprintf(sdsempty(),"-Reading from master: %s",
                strerror(errno));
    }
    return sdsnew(buf);
}

由上述函数逻辑基本可以看出,主从在做复制的时候,如果有masterauth,从库会先发送masterauth给主库进行验证,验证无误后再进行下面的主从复制,验证不通过时就跳转到error错误处理。

三、总结

由上面的分析,更加确定之前关于requirepass和masterauth的作用

requirepass作用:对登录权限做限制,redis每个节点的requirepass可以是独立、不同的。

masterauth作用:主要是针对master对应的slave节点设置的,在slave节点数据同步的时候用到。

现在基本上可以回答之前的问题了:

redis集群的密码信息存在哪?是如何更新的?

存在哪?

  • requirepass和masterauth都保存在 redisServer 结构体中,供 redis主库进行验证。
  • requirepass验证客户端,masterauth验证从库。

如何更新?

  • 可以在redis.conf中配置,不过要重启服务才能生效
  • 在redis命令进行更新,不过要注意rewrite到配置中,不然重启之后就会失效

文章若有表述不当或者欠缺的地方,欢迎指正~~~

  • 17
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值