为Twemproxy 添加 Auth

来源:互联网 发布:linux运行jar文件 编辑:程序博客网 时间:2024/06/12 22:19
前言:
Twemproxy不支持Auth 指令。
而且细想之下Twemproxy不支持Auth是有道理的。
当然在一些特需下,还是需要Twemproxy支持Auth。
暂且不讨论Redis的弱验证模式与其他解决方案。

正文:
为Twemproxy添加Auth功能,实际上分为两个部分:
1.client to twemproxy (对外有安全需要)
2.twemproxy to redis  (对内redis有安全需要)

在实现2个auth功能前,我们需要为Twemproxy配置一个password

需要修改的地方如下:
1. 配置文件中添加password
password: yourpassword
2.nc_conf.h
2.1 为conf_pool 添加password成员
struct conf_pool {
...
    struct string      password;
...
}
2.2 为password配置声明解析方法
char *conf_set_password(struct conf *cf,struct command *cmd,void *conf);
3.nc_server.h
3.1 为server_pool添加成员password
struct server_pool {
...
    char               password[128];
...
}
4.nc_conf.c 
4.1 声明要解析的参数password
 static struct command conf_commands[] = {
...
    { string("password"),
      conf_set_password,
      offsetof(struct conf_pool, password) },
...
}
4.2 初始化/反初始化 conf_pool中的password参数
static rstatus_t
conf_pool_init(struct conf_pool *cp, struct string *name)
{
...
    cp->password.data = CONF_UNSET_PTR;
...
}
static void
conf_pool_deinit(struct conf_pool *cp)
{
...
    string_deinit(&cp->password);
...
}
4.3 实现对password 配置的解析
char *
conf_set_password(struct conf *cf,struct command *cmd,void *conf)
{
    uint8_t *p;
    struct string *field, *value;
    p = conf;
    field = (struct string *)(p + cmd->offset);
    if (field->data != CONF_UNSET_PTR) {
        return "is a duplicate";
    }
    value = array_top(&cf->arg);
    if (value->len == 0 ) {
        return "password is null";
    }
    if (value->len >100 ) {
        return "password is too long";
    }
    field->data = (char*)malloc(128);
    memcpy(field->data,value->data,value->len);
    field->data[value->len] = '\0';
    field->len = value->len;
    printf("password is:%s\n",field->data);
    return CONF_OK;
}
4.4 将conf_pool中的password copy到server_pool中去
rstatus_t
conf_pool_each_transform(void *elem, void *data)
{
...
    if(cp->password.data != CONF_UNSET_PTR)
    {
        strcpy(sp->password,cp->password.data);
    }else
    {
        sp->password[0] == '\0';//password 长度为0表示没有密码
    }
...
}
如此这番之后我们就为Twemproxy添加了配置密码的功能
下面我们实现两种Auth指令需求
Client to Twemproxy:
1.为Twemproxy添加Auth指令支持
1.1 nc_message.h
1.1.1 在MSG_TYPE_CODEC中添加AUTH ACTION的声明
#define MSG_TYPE_CODEC(ACTION)
...
    ACTION( REQ_REDIS_AUTH ) /* 设置密码*/\
...
1.2 nc_redis.c
1.2.1 在没有arg(不包含hash key与action key)的指令中注册
static bool
redis_arg0(struct msg *r)
{
    switch (r->type) {
...
    case MSG_REQ_REDIS_AUTH:
...
    }
    return false;
}
1.2.2 在request解析中添加action auth的解析
void
redis_parse_req(struct msg *r)
{
    ...
    for (p = r->pos; p < b->last; p++) {
        ch = *p;
        switch (state) {
        ...
        case SW_REQ_TYPE:
            ...
            switch (p - m) {
            ...
            case 4:
                ...
                if (str4icmp(m, 'a', 'u', 't', 'h'))
                {//将auth的解析放在最后,因为auth指令总不是常用的
                    r->type = MSG_REQ_REDIS_AUTH;
                    break;
                }
                break;
            ...
            default:
                break;
            }
            ...
            break;
        ...
        case SW_SENTINEL:
        default:
            NOT_REACHED();
            break;
        }
    }
    ...
}
2. 为conn添加password设置是否成功的标记
2.1 nc_connection.h
2.1.1 为conn添加password设置成功与否的标记变量
struct conn {
...
    unsigned           setpassword:1; /* 标记是否设置了密码 */
...
}
2.2 nc_connection.c
2.2.2 初始化conn的setpassword成员
struct conn *
conn_get(void *owner, bool client, bool redis)
{
...
    conn->setpassword = 0;
...
}
3. Auth功能实现
3.1 nc_request.c
3.1.1 过滤掉没有Auth的conn的请求,验证Auth
static bool
req_filter(struct context *ctx, struct conn *conn, struct msg *msg)
{
...
    if(msg->type == MSG_REQ_REDIS_AUTH)
    {//验证密码
        struct server_pool *pool = conn->owner;
        struct mbuf* p_buf = STAILQ_FIRST(&msg->mhdr);
        char* p_start =p_buf->pos;
        char* p_end = p_buf->last;
        char passwordinfo[256];
        sprintf(passwordinfo,
                "*2\r\n$4\r\nauth\r\n$%d\r\n%s\r\n",
                strlen(pool->password),
                pool->password);
        if(msg->mlen != strlen(passwordinfo)
                || p_end - p_start != msg->mlen//如果密码被分段了也认定为错误
                || memcmp(p_start+13,passwordinfo+13,msg->mlen-13)!=0)
        {//密码错误
            struct msg* pong = fGetAuthErrMsg();
            conn->setpassword = 0;
            fReturnMsgToClient(ctx,conn,pong,msg);
            return true;
        }else
        {
            conn->setpassword = 1;
            struct msg* ok = fGetOKMsg();
            fReturnMsgToClient(ctx,conn,ok,msg);
            return true;
        }
    }
    if(conn->setpassword == 0)
    {//没有密码检测下是否需要密码
        struct server_pool *pool = conn->owner;
        if(pool->password[0]!=0)
        {//表示需要密码
            struct msg* pong = fGetNeedAuthMsg();
            fReturnMsgToClient(ctx,conn,pong,msg);
            return true;
        }
    }
...
}
3.2 nc_message.h
3.2.1 声明ACTION Auth 使用到的静态响应包
struct msg* fGetNeedAuthMsg();
struct msg* fGetAuthErrMsg();
struct msg* fGetOKMsg();
3.3 nc_message.c
3.3.1 实现ACTION Auth 使用到的静态响应包
struct msg* fGetAuthErrMsg()
{
    struct msg* pong = _msg_get();
    struct mbuf *mbuf;
    if(pong == NULL)
    {
        return NULL;
    }
    pong->mlen = 0;
    mbuf = mbuf_get();
    if (mbuf == NULL) {
        msg_put(pong);
        return NULL;
    }
    mbuf_insert(&(pong->mhdr), mbuf);
    pong->p_key_info = NULL;
    static int noauthlen = strlen("-ERR invalid password\r\n");
    memcpy(mbuf->last,
           "-ERR invalid password\r\n",
           noauthlen);
    mbuf->last += noauthlen;
    pong->mlen += noauthlen;
    return pong;
}
struct msg* fGetNeedAuthMsg()
{
    struct msg* pong = _msg_get();
    struct mbuf *mbuf;
    if(pong == NULL)
    {
        return NULL;
    }
    pong->mlen = 0;
    mbuf = mbuf_get();
    if (mbuf == NULL) {
        msg_put(pong);
        return NULL;
    }
    mbuf_insert(&(pong->mhdr), mbuf);
    pong->p_key_info = NULL;
    static int noauthlen = strlen("-NOAUTH Authentication required.\r\n");
    memcpy(mbuf->last,
           "-NOAUTH Authentication required.\r\n",
           noauthlen);
    mbuf->last += noauthlen;
    pong->mlen += noauthlen;
    return pong;
}
struct msg* fGetOKMsg()
{
    struct msg* OK = _msg_get();
    struct mbuf *mbuf;
    if(OK == NULL)
    {
        return NULL;
    }
    OK->mlen = 0;
    mbuf = mbuf_get();
    if (mbuf == NULL) {
        msg_put(OK);
        return NULL;
    }
    mbuf_insert(&(OK->mhdr), mbuf);
    OK->p_key_info = NULL;
    memcpy(mbuf->last,
           "+OK\r\n",
           5);
    mbuf->last += 5;
    OK->mlen += 5;
    return OK;
}
Twemproxy To Redis:
1. 初始化conn,
1.1 nc_server.c
1.1.1 每当conn需要重连的时候都需要重置一下
rstatus_t
server_connect(struct context *ctx, struct server *server, struct conn *conn)
{
    rstatus_t status;
    ASSERT(!conn->client && !conn->proxy);
    if (conn->sd > 0) {
        /* already connected on server connection */
        return NC_OK;
    }
    conn->setpassword = 0;//标记为没有设置密码
 ...
}
2. 发送Auth指令
2.1 nc_request.c
2.2.1 每当conn发送一个message但是又没有标记Auth的时候发送Auth指令
void
req_server_enqueue_imsgq(struct context *ctx,
                         struct conn *conn,
                         struct msg *msg)
{
    ASSERT(msg->request);
    ASSERT(!conn->client && !conn->proxy);
    struct server *ser = (struct server *)conn->owner;
    if(ser->owner->password[0]!=0 && conn->setpassword==0)
    {//要设置密码,但是还没有设置成功
        struct msg* p_password = fGetAuthMsg(ser->owner->password);
        conn->setpassword = 1;//只管发送第一个请求前发送一次auth指令,其他的就不管了
        req_server_enqueue_imsgq(ctx,conn,p_password);
    }
    //广播命令只有在最后一次发送时,加入timeout中
    if ((!msg->noreply)
            &&(!msg->broadcast
               ||msg->ui32_broadcast_number==0
                )) {
        msg_tmo_insert(msg, conn);
    }
    TAILQ_INSERT_TAIL(&conn->imsg_q, msg, s_tqe);
    stats_server_incr(ctx, conn->owner, in_queue);
    stats_server_incr_by(ctx, conn->owner, in_queue_bytes, msg->mlen);
}
2.3 nc_message.h
2.3.1 声明AuthMsg
struct msg* fGetAuthMsg(char* password);
2.4 nc_message.c
2.4.1 实现AuthMsg
struct msg* fGetAuthMsg(char* password)
{
    struct msg* AuthPassword = _msg_get();
    struct mbuf *mbuf;
    if(AuthPassword == NULL)
    {
        return NULL;
    }
    AuthPassword->mlen = 0;
    mbuf = mbuf_get();
    if (mbuf == NULL) {
        msg_put(AuthPassword);
        return NULL;
    }
    mbuf_insert(&(AuthPassword->mhdr), mbuf);
    AuthPassword->p_key_info = NULL;
    sprintf(mbuf->last,"*2\r\n$4\r\nauth\r\n$%d\r\n%s\r\n",strlen(password),password);
    mbuf->last += strlen(mbuf->last);
    AuthPassword->mlen += strlen(mbuf->last);
    AuthPassword->swallow = 1;
    return AuthPassword;
}
到此为Twemproxy添加Auth功能就已经实现了
后记:
当然这里面还是有很多地方并不完善,比如Twemproxy to Reid 的Auth 并没有管失败的情况。
没有将各个Redis节点之间的Auth分开,也没有与Client to Twemproxy分离。
至于这些就根据实际需求实现吧。

0 0