Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Redis SSL support #902

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ if(REDIS_STORAGE_BACKEND)
find_package(hiredis 0.13.3 REQUIRED)
endif()

option(REDISS_STORAGE_BACKEND "Enable Redis SSL secondary storage" OFF)
if(REDISS_STORAGE_BACKEND)
message(WARNING
"Linking against OpenSSL doubles ccache startup time. Distributions are "
"encouraged to provide two ccache binaries: one without any extra "
"backends and one with the all the desired backends enabled.")
find_package(hiredis 1.0.0 REQUIRED)
if(NOT hiredis_ssl_FOUND)
message(FATAL_ERROR "hiredis_ssl not found")
endif()
endif()

#
# Special flags
#
Expand Down
26 changes: 26 additions & 0 deletions cmake/Findhiredis.cmake
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
if(hiredis_FOUND)
return()
endif()

find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
pkg_check_modules(HIREDIS REQUIRED hiredis>=${hiredis_FIND_VERSION})
Expand All @@ -15,6 +19,18 @@ find_package_handle_standard_args(
HIREDIS_INCLUDE_DIR HIREDIS_LIBRARY)
mark_as_advanced(HIREDIS_INCLUDE_DIR HIREDIS_LIBRARY)

if(PKG_CONFIG_FOUND)
pkg_check_modules(HIREDIS_SSL hiredis_ssl>=${hiredis_FIND_VERSION})
find_library(HIREDIS_SSL_LIBRARY ${HIREDIS_SSL_LIBRARIES} HINTS ${HIREDIS_SSL_LIBDIR})
find_path(HIREDIS_SSL_INCLUDE_DIR hiredis/hiredis_ssl.h HINTS ${HIREDIS_SSL_PREFIX}/include)
else()
find_library(HIREDIS_SSL_LIBRARY hiredis_ssl)
find_path(HIREDIS_SSL_INCLUDE_DIR hiredis/hiredis_ssl.h)
endif()

mark_as_advanced(HIREDIS_SSL_INCLUDE_DIR HIREDIS_SSL_LIBRARY)
set(hiredis_ssl_FOUND HIREDIS_SSL_LIBRARY)

add_library(HIREDIS::HIREDIS UNKNOWN IMPORTED)
set_target_properties(
HIREDIS::HIREDIS
Expand All @@ -23,9 +39,19 @@ set_target_properties(
INTERFACE_COMPILE_OPTIONS "${HIREDIS_CFLAGS_OTHER}"
INTERFACE_INCLUDE_DIRECTORIES "${HIREDIS_INCLUDE_DIR}")

add_library(HIREDIS::HIREDIS_SSL UNKNOWN IMPORTED)
set_target_properties(
HIREDIS::HIREDIS_SSL
PROPERTIES
IMPORTED_LOCATION "${HIREDIS_SSL_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${HIREDIS_SSL_CFLAGS_OTHER}"
INTERFACE_INCLUDE_DIRECTORIES "${HIREDIS_SSL_INCLUDE_DIR}")

include(FeatureSummary)
set_package_properties(
hiredis
PROPERTIES
URL "https://github.com/redis/hiredis"
DESCRIPTION "Hiredis is a minimalistic C client library for the Redis database")

set(hiredis_FOUND TRUE)
3 changes: 2 additions & 1 deletion doc/MANUAL.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -958,7 +958,7 @@ Known issues and limitations:

=== Redis storage backend

URL format: `redis://[[USERNAME:]PASSWORD@]HOST[:PORT][/DBNUMBER]`
URL format: `redis[s]://[[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.
Expand All @@ -974,6 +974,7 @@ Examples:

Optional attributes:

* *cacert*: Path to a SSL Root CA file.
* *connect-timeout*: Timeout (in ms) for network connection. The default is 100.
* *operation-timeout*: Timeout (in ms) for Redis commands. The default is 10000.

Expand Down
7 changes: 7 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ if(REDIS_STORAGE_BACKEND)
PUBLIC standard_settings standard_warnings HIREDIS::HIREDIS
third_party_lib)
endif()
if(REDISS_STORAGE_BACKEND)
target_compile_definitions(ccache_lib PRIVATE -DHAVE_REDISS_STORAGE_BACKEND)
target_link_libraries(
ccache_lib
PUBLIC standard_settings standard_warnings HIREDIS::HIREDIS HIREDIS::HIREDIS_SSL
afbjorklund marked this conversation as resolved.
Show resolved Hide resolved
third_party_lib)
endif()

add_subdirectory(core)
add_subdirectory(storage)
Expand Down
8 changes: 7 additions & 1 deletion src/storage/Storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
#include <fmtmacros.hpp>
#include <storage/secondary/FileStorage.hpp>
#include <storage/secondary/HttpStorage.hpp>
#ifdef HAVE_REDIS_STORAGE_BACKEND
#if defined(HAVE_REDIS_STORAGE_BACKEND) || defined(HAVE_REDISS_STORAGE_BACKEND)
# include <storage/secondary/RedisStorage.hpp>
#endif
#include <util/Tokenizer.hpp>
Expand Down Expand Up @@ -254,6 +254,12 @@ create_storage(const ParseStorageEntryResult& storage_entry)
storage_entry.attributes);
}
#endif
#ifdef HAVE_REDISS_STORAGE_BACKEND
if (storage_entry.url.scheme() == "rediss") {
return std::make_unique<secondary::RedisStorage>(storage_entry.url,
storage_entry.attributes);
}
#endif

return {};
}
Expand Down
2 changes: 1 addition & 1 deletion src/storage/secondary/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set(
${CMAKE_CURRENT_SOURCE_DIR}/HttpStorage.cpp
)

if(REDIS_STORAGE_BACKEND)
if(REDIS_STORAGE_BACKEND OR REDISS_STORAGE_BACKEND)
list(APPEND sources ${CMAKE_CURRENT_SOURCE_DIR}/RedisStorage.cpp)
endif()

Expand Down
58 changes: 58 additions & 0 deletions src/storage/secondary/RedisStorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
#include <util/string_utils.hpp>

#include <hiredis/hiredis.h>
#ifdef HAVE_REDISS_STORAGE_BACKEND
# include <hiredis/hiredis_ssl.h>
#endif

#include <cstdarg>
#include <memory>
Expand Down Expand Up @@ -56,6 +59,16 @@ milliseconds_to_timeval(const uint64_t ms)
return tv;
}

static nonstd::optional<std::string>
parse_string_attribute(const AttributeMap& attributes, const std::string& name)
{
const auto it = attributes.find(name);
if (it == attributes.end()) {
return nonstd::nullopt;
}
return it->second;
}

static uint64_t
parse_timeout_attribute(const AttributeMap& attributes,
const std::string& name,
Expand Down Expand Up @@ -89,6 +102,8 @@ RedisStorage::RedisStorage(const Url& url, const AttributeMap& attributes)
: m_url(url),
m_prefix("ccache"), // TODO: attribute
m_context(nullptr),
m_ca_cert(parse_string_attribute(attributes, "cacert").value_or("")),
afbjorklund marked this conversation as resolved.
Show resolved Hide resolved
m_ssl_context(nullptr),
m_connect_timeout(parse_timeout_attribute(
attributes, "connect-timeout", DEFAULT_CONNECT_TIMEOUT_MS)),
m_operation_timeout(parse_timeout_attribute(
Expand All @@ -100,6 +115,15 @@ RedisStorage::RedisStorage(const Url& url, const AttributeMap& attributes)

RedisStorage::~RedisStorage()
{
#ifdef HAVE_REDISS_STORAGE_BACKEND
if (m_ssl_context) {
redisFreeSSLContext(m_ssl_context);
m_ssl_context = nullptr;
}
#else
// avoid unused-private-field warning
(void)m_ssl_context;
#endif
if (m_context) {
LOG_RAW("Redis disconnect");
redisFree(m_context);
Expand Down Expand Up @@ -127,7 +151,12 @@ RedisStorage::connect()
m_context = nullptr;
}

#ifdef HAVE_REDISS_STORAGE_BACKEND
ASSERT(m_url.scheme() == "redis" || m_url.scheme() == "rediss");
bool secure = (m_url.scheme() == "rediss");
#else
ASSERT(m_url.scheme() == "redis");
#endif
const std::string host = m_url.host().empty() ? "localhost" : m_url.host();
const uint32_t port =
m_url.port().empty() ? DEFAULT_PORT
Expand All @@ -141,6 +170,25 @@ RedisStorage::connect()
std::numeric_limits<uint32_t>::max(),
"db number");

#ifdef HAVE_REDISS_STORAGE_BACKEND
if (secure) {
if (redisInitOpenSSL() != REDIS_OK) {
LOG_RAW("Redis SSL init OpenSSL failed");
m_invalid = true;
return REDIS_ERR;
}
const char* cacert = m_ca_cert.empty() ? NULL : m_ca_cert.c_str();
redisSSLContextError ssl_error;
m_ssl_context =
redisCreateSSLContext(cacert, NULL, NULL, NULL, NULL, &ssl_error);
if (!m_ssl_context) {
LOG("Redis SSL create error: {}", redisSSLContextGetError(ssl_error));
m_invalid = true;
return REDIS_ERR;
}
}
#endif

const auto connect_timeout = milliseconds_to_timeval(m_connect_timeout);

LOG("Redis connecting to {}:{} (timeout {} ms)",
Expand All @@ -167,6 +215,16 @@ RedisStorage::connect()
LOG_RAW("Failed to set operation timeout");
}

#ifdef HAVE_REDISS_STORAGE_BACKEND
if (secure) {
if (redisInitiateSSLWithContext(m_context, m_ssl_context) != REDIS_OK) {
LOG("Redis SSL init error: {}", m_context->errstr);
m_invalid = true;
return REDIS_ERR;
}
}
#endif

if (db_number != 0) {
LOG("Redis SELECT {}", db_number);
const auto reply = redis_command(m_context, "SELECT %d", db_number);
Expand Down
3 changes: 3 additions & 0 deletions src/storage/secondary/RedisStorage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <third_party/url.hpp>

struct redisContext;
struct redisSSLContext;

namespace storage {
namespace secondary {
Expand All @@ -45,6 +46,8 @@ class RedisStorage : public storage::SecondaryStorage
Url m_url;
std::string m_prefix;
redisContext* m_context;
std::string m_ca_cert;
redisSSLContext* m_ssl_context;
const uint64_t m_connect_timeout;
const uint64_t m_operation_timeout;
bool m_connected;
Expand Down