Skip to content

bug: module apisix.utils.redis - Redis connection reuse issue #13454

@jakubzieba

Description

@jakubzieba

Current Behavior

When we use multiple rate-limiting plugins that share the same Redis address but connect to different databases or use different credentials (username/password), connection reuse can lead to getting a connection with the wrong database or user/password.

For example, assuming I configure two such limit-count plugins on a routes,

"limit-count": {
    "count": 1,
    "time_window": 30,
    "rejected_code": 429,
    "key": "remote_addr",
    "policy": "redis",
    "redis_host": "redis1.local",
    "redis_port": 6379,
    "redis_database": 1
}

"limit-count": {
    "count": 1,
    "time_window": 30,
    "rejected_code": 429,
    "key": "remote_addr",
    "policy": "redis",
    "redis_host": "redis1.local",
    "redis_port": 6379,
    "redis_database": 2
}

I will randomly get connections from the pool that use the wrong database. The core issue here is that authentication and database selection are executed only during the initial connect, after which the connection is placed back into the pool.

To fix this problem, we could re-authenticate and re-select the database whenever we detect that the connection's current credentials or database don't match the required ones. Alternatively – which in my opinion is a better approach – we can introduce separate connection pools for such connections. We can achieve this by implementing a function that generates a pool name based on the username, password, and database number,

local function get_pool_name(conf)
    local database = conf.redis_database or 0
    local connection_string = conf.redis_host .. ":" .. conf.redis_port .. "/" .. database
    if conf.redis_username and conf.redis_username ~= "" then
        local password = conf.redis_password or ""
        local hashed_credentials = ngx.md5(conf.redis_username .. ":" .. password)
        return hashed_credentials .. "@" .. connection_string
    else
        return connection_string
    end
end

and then use it in sock_opts.

local sock_opts = {
        ...
        pool = get_pool_name(conf)
    }

Expected Behavior

Redis commands must be executed using a connection configured with the appropriate user, password, and database ID, in accordance with the plugin's configuration.

Error Logs

No response

Steps to Reproduce

No description needed.

Environment

It's irrelevant.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    📋 Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions