From: Joel Rosdahl Date: Sun, 11 Jul 2021 18:32:08 +0000 (+0200) Subject: Implement partial support for Redis provisional URI scheme X-Git-Tag: v4.4~137 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2de287348651a8f5b416488dd97e33f7dcb8bd80;p=thirdparty%2Fccache.git Implement partial support for Redis provisional URI scheme Reference: https://www.iana.org/assignments/uri-schemes/prov/redis Supported: Username, password, host, port and db number. Not supported: connection parameters. This also drops support for Unix sockets. --- diff --git a/doc/MANUAL.adoc b/doc/MANUAL.adoc index 6d3e5316e..6b66f9aa6 100644 --- a/doc/MANUAL.adoc +++ b/doc/MANUAL.adoc @@ -956,25 +956,24 @@ Known issues and limitations: === Redis storage backend -URL format: `redis://HOST[:PORT]` or `redis://UNIX_SOCKET_PATH` +URL format: `redis://[[USERNAME:]PASSWORD@]HOST[:PORT][/DBNUMBER]` This backend stores data in a https://redis.io[Redis] (or Redis-compatible) server. There are implementations for both memory-based and disk-based storage. +*PORT* defaults to *6379* and *DBNUMBER* defaults to *0*. Note that ccache will not perform any cleanup of the Redis storage, but you can https://redis.io/topics/lru-cache[configure LRU eviction]. Examples: -* `redis://localhost:6379|connect-timeout=50` -* `redis:///var/run/redis/redis-server.sock` +* `redis://localhost` +* `redis://p4ssw0rd@cache.example.com:6379/0|connect-timeout=50` Optional attributes: * *connect-timeout*: Timeout (in ms) for network connection. The default is 100. * *operation-timeout*: Timeout (in ms) for Redis commands. The default is 10000. -* *username*: Username for the Redis AUTH command (requires Redis 6 or newer). -* *password*: Password for the Redis AUTH command. == Cache size management diff --git a/src/storage/SecondaryStorage.hpp b/src/storage/SecondaryStorage.hpp index 611f78783..6fec9c911 100644 --- a/src/storage/SecondaryStorage.hpp +++ b/src/storage/SecondaryStorage.hpp @@ -27,6 +27,8 @@ class Digest; namespace storage { +constexpr auto k_masked_password = "********"; + // This class defines the API that a secondary storage backend must implement. class SecondaryStorage { diff --git a/src/storage/secondary/RedisStorage.cpp b/src/storage/secondary/RedisStorage.cpp index 6e99daf44..a05dc1b46 100644 --- a/src/storage/secondary/RedisStorage.cpp +++ b/src/storage/secondary/RedisStorage.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include @@ -32,7 +33,7 @@ namespace secondary { const uint64_t DEFAULT_CONNECT_TIMEOUT_MS = 100; const uint64_t DEFAULT_OPERATION_TIMEOUT_MS = 10000; -const int DEFAULT_PORT = 6379; +const uint32_t DEFAULT_PORT = 6379; using RedisReply = std::unique_ptr; @@ -68,14 +69,20 @@ parse_timeout_attribute(const AttributeMap& attributes, } } -static nonstd::optional -parse_string_attribute(const AttributeMap& attributes, const std::string& name) +static std::pair, nonstd::optional> +split_user_info(const std::string& user_info) { - const auto it = attributes.find(name); - if (it == attributes.end()) { - return nonstd::nullopt; + const auto pair = util::split_once(user_info, ':'); + if (pair.first.empty()) { + // redis://HOST + return {nonstd::nullopt, nonstd::nullopt}; + } else if (pair.second) { + // redis://USERNAME:PASSWORD@HOST + return {to_string(*pair.second), to_string(pair.first)}; + } else { + // redis://PASSWORD@HOST + return {to_string(pair.first), nonstd::nullopt}; } - return it->second; } RedisStorage::RedisStorage(const Url& url, const AttributeMap& attributes) @@ -86,8 +93,6 @@ RedisStorage::RedisStorage(const Url& url, const AttributeMap& attributes) attributes, "connect-timeout", DEFAULT_CONNECT_TIMEOUT_MS)), m_operation_timeout(parse_timeout_attribute( attributes, "operation-timeout", DEFAULT_OPERATION_TIMEOUT_MS)), - m_username(parse_string_attribute(attributes, "username")), - m_password(parse_string_attribute(attributes, "password")), m_connected(false), m_invalid(false) { @@ -123,28 +128,26 @@ RedisStorage::connect() } ASSERT(m_url.scheme() == "redis"); - const auto& host = m_url.host(); - const auto& port = m_url.port(); - const auto& sock = m_url.path(); + const std::string host = m_url.host().empty() ? "localhost" : m_url.host(); + const uint32_t port = + m_url.port().empty() ? DEFAULT_PORT + : Util::parse_unsigned(m_url.port(), 1, 65535, "port"); + ASSERT(m_url.path().empty() || m_url.path()[0] == '/'); + const uint32_t db_number = + m_url.path().empty() + ? 0 + : Util::parse_unsigned(m_url.path().substr(1), + 0, + std::numeric_limits::max(), + "db number"); + const auto connect_timeout = milliseconds_to_timeval(m_connect_timeout); - if (!host.empty()) { - const int p = port.empty() ? DEFAULT_PORT - : Util::parse_unsigned(port, 1, 65535, "port"); - LOG("Redis connecting to {}:{} (timeout {} ms)", - host.c_str(), - p, - m_connect_timeout); - m_context = redisConnectWithTimeout(host.c_str(), p, connect_timeout); - } else if (!sock.empty()) { - LOG("Redis connecting to {} (timeout {} ms)", - sock.c_str(), - m_connect_timeout); - m_context = redisConnectUnixWithTimeout(sock.c_str(), connect_timeout); - } else { - LOG("Invalid Redis URL: {}", m_url.str()); - m_invalid = true; - return REDIS_ERR; - } + + LOG("Redis connecting to {}:{} (timeout {} ms)", + host.c_str(), + port, + m_connect_timeout); + m_context = redisConnectWithTimeout(host.c_str(), port, connect_timeout); if (!m_context) { LOG_RAW("Redis connection error (NULL context)"); @@ -154,48 +157,55 @@ RedisStorage::connect() LOG("Redis connection error: {}", m_context->errstr); m_invalid = true; return m_context->err; - } else { - if (m_context->connection_type == REDIS_CONN_TCP) { - LOG("Redis connection to {}:{} OK", - m_context->tcp.host, - m_context->tcp.port); - } - if (m_context->connection_type == REDIS_CONN_UNIX) { - LOG("Redis connection to {} OK", m_context->unix_sock.path); - } - m_connected = true; + } - if (redisSetTimeout(m_context, milliseconds_to_timeval(m_operation_timeout)) - != REDIS_OK) { - LOG_RAW("Failed to set operation timeout"); - } + LOG("Redis connection to {}:{} OK", m_context->tcp.host, m_context->tcp.port); + m_connected = true; - return auth(); + if (redisSetTimeout(m_context, milliseconds_to_timeval(m_operation_timeout)) + != REDIS_OK) { + LOG_RAW("Failed to set operation timeout"); } + + if (db_number != 0) { + LOG("Redis SELECT {}", db_number); + const auto reply = redis_command(m_context, "SELECT %d", db_number); + if (!reply) { + LOG_RAW("Redis SELECT failed (NULL)"); + m_invalid = true; + return REDIS_ERR; + } else if (reply->type == REDIS_REPLY_ERROR) { + LOG("Redis SELECT error: {}", reply->str); + m_invalid = true; + return REDIS_ERR; + } + } + + return auth(); } int RedisStorage::auth() { - if (m_password) { - bool log_password = false; - std::string username = m_username ? *m_username : "default"; - std::string password = log_password ? *m_password : "*******"; - LOG("Redis AUTH {} {}", username, password); - + const auto password_username_pair = split_user_info(m_url.user_info()); + const auto& password = password_username_pair.first; + if (password) { RedisReply reply(nullptr, freeReplyObject); - if (m_username) { + const auto& username = password_username_pair.second; + if (username) { + LOG("Redis AUTH {} {}", *username, storage::k_masked_password); reply = redis_command( - m_context, "AUTH %s %s", m_username->c_str(), m_password->c_str()); + m_context, "AUTH %s %s", username->c_str(), password->c_str()); } else { - reply = redis_command(m_context, "AUTH %s", m_password->c_str()); + LOG("Redis AUTH {}", storage::k_masked_password); + reply = redis_command(m_context, "AUTH %s", password->c_str()); } if (!reply) { - LOG_RAW("Failed to authenticate to Redis (NULL)"); + LOG_RAW("Redis AUTH failed (NULL)"); m_invalid = true; return REDIS_ERR; } else if (reply->type == REDIS_REPLY_ERROR) { - LOG("Failed to authenticate to Redis: {}", reply->str); + LOG("Redis AUTH error: {}", reply->str); m_invalid = true; return REDIS_ERR; } diff --git a/src/storage/secondary/RedisStorage.hpp b/src/storage/secondary/RedisStorage.hpp index 04d27de61..3e2ed4538 100644 --- a/src/storage/secondary/RedisStorage.hpp +++ b/src/storage/secondary/RedisStorage.hpp @@ -47,8 +47,6 @@ private: redisContext* m_context; const uint64_t m_connect_timeout; const uint64_t m_operation_timeout; - const nonstd::optional m_username; - const nonstd::optional m_password; bool m_connected; bool m_invalid;