}
private:
+ auto initiate_tls(redisAsyncContext *ctx) const -> bool
+ {
+ redisSSLContextError ssl_err = REDIS_SSL_CTX_NONE;
+ redisSSLOptions opts{};
+ opts.cacert_filename = ca_file.empty() ? nullptr : ca_file.c_str();
+ opts.capath = ca_dir.empty() ? nullptr : ca_dir.c_str();
+ opts.cert_filename = cert_file.empty() ? nullptr : cert_file.c_str();
+ opts.private_key_filename = key_file.empty() ? nullptr : key_file.c_str();
+ opts.server_name = sni.empty() ? nullptr : sni.c_str();
+ opts.verify_mode = no_ssl_verify ? REDIS_SSL_VERIFY_NONE : REDIS_SSL_VERIFY_PEER;
+ auto *ssl_ctx = redisCreateSSLContextWithOptions(&opts, &ssl_err);
+ if (!ssl_ctx || redisInitiateSSLWithContext(&ctx->c, ssl_ctx) != REDIS_OK) {
+ msg_err("cannot start TLS for redis %s:%d: %s", ip.c_str(), port, ctx->errstr);
+ if (ssl_ctx) redisFreeSSLContext(ssl_ctx);
+ return false;
+ }
+ redisFreeSSLContext(ssl_ctx);
+ return true;
+ }
+
auto redis_async_new() -> redisAsyncContext *
{
struct redisAsyncContext *ctx;
}
}
-static bool ensure_ssl_inited()
-{
- static bool inited = false;
- if (!inited) {
- if (redisInitOpenSSL() == REDIS_OK) {
- inited = true;
- }
- else {
- /* still try, hiredis might work if already inited elsewhere */
- inited = true;
- }
- }
- return inited;
-}
+/* OpenSSL is initialised by core before Redis pool is used */
auto redis_pool_elt::new_connection() -> redisAsyncContext *
{
conn->ctx->errstr, ip.c_str(), port, nctx);
if (nctx) {
- /* If TLS is configured for this element, initiate it now */
- if (use_tls && !is_unix) {
- if (ensure_ssl_inited()) {
- redisSSLContextError ssl_err = REDIS_SSL_CTX_NONE;
- redisSSLOptions opts{};
- opts.cacert_filename = ca_file.empty() ? nullptr : ca_file.c_str();
- opts.capath = ca_dir.empty() ? nullptr : ca_dir.c_str();
- opts.cert_filename = cert_file.empty() ? nullptr : cert_file.c_str();
- opts.private_key_filename = key_file.empty() ? nullptr : key_file.c_str();
- opts.server_name = sni.empty() ? nullptr : sni.c_str();
- opts.verify_mode = no_ssl_verify ? REDIS_SSL_VERIFY_NONE : REDIS_SSL_VERIFY_PEER;
-
- auto *ssl_ctx = redisCreateSSLContextWithOptions(&opts, &ssl_err);
- if (!ssl_ctx || redisInitiateSSLWithContext(&nctx->c, ssl_ctx) != REDIS_OK) {
- msg_err("cannot start TLS for redis %s:%d: %s", ip.c_str(), port, nctx->errstr);
- if (ssl_ctx) redisFreeSSLContext(ssl_ctx);
+ /* If TLS is configured for this element, initiate it now */
+ if (use_tls && !is_unix) {
+ if (!initiate_tls(nctx)) {
redisAsyncFree(nctx);
nctx = nullptr;
}
- else {
- redisFreeSSLContext(ssl_ctx);
- }
}
- }
if (nctx) {
active.emplace_front(std::make_unique<redis_pool_connection>(pool, this,
auto *nctx = redis_async_new();
if (nctx) {
- /* If TLS is configured for this element, initiate it now */
- if (use_tls && !is_unix) {
- if (ensure_ssl_inited()) {
- redisSSLContextError ssl_err = REDIS_SSL_CTX_NONE;
- redisSSLOptions opts{};
- opts.cacert_filename = ca_file.empty() ? nullptr : ca_file.c_str();
- opts.capath = ca_dir.empty() ? nullptr : ca_dir.c_str();
- opts.cert_filename = cert_file.empty() ? nullptr : cert_file.c_str();
- opts.private_key_filename = key_file.empty() ? nullptr : key_file.c_str();
- opts.server_name = sni.empty() ? nullptr : sni.c_str();
- opts.verify_mode = no_ssl_verify ? REDIS_SSL_VERIFY_NONE : REDIS_SSL_VERIFY_PEER;
-
- auto *ssl_ctx = redisCreateSSLContextWithOptions(&opts, &ssl_err);
- if (!ssl_ctx || redisInitiateSSLWithContext(&nctx->c, ssl_ctx) != REDIS_OK) {
- msg_err("cannot start TLS for redis %s:%d: %s", ip.c_str(), port, nctx->errstr);
- if (ssl_ctx) redisFreeSSLContext(ssl_ctx);
+ /* If TLS is configured for this element, initiate it now */
+ if (use_tls && !is_unix) {
+ if (!initiate_tls(nctx)) {
redisAsyncFree(nctx);
nctx = nullptr;
}
- else {
- redisFreeSSLContext(ssl_ctx);
- }
}
- }
if (nctx) {
active.emplace_front(std::make_unique<redis_pool_connection>(pool, this,
g_assert(p != NULL);
auto *pool = reinterpret_cast<class rspamd::redis_pool *>(p);
- if (tls && tls->use_tls) {
- return pool->new_connection_ext(db, username, password, ip, port,
- true, tls->no_ssl_verify != 0,
+ if (tls && tls->use_tls) {
+ return pool->new_connection_ext(db, username, password, ip, port,
+ true, tls->no_ssl_verify,
tls->ca_file, tls->ca_dir,
tls->cert_file, tls->key_file,
tls->sni);
struct redisAsyncContext;
struct ev_loop;
struct rspamd_redis_tls_opts {
- int use_tls; /* 0/1 */
- int no_ssl_verify; /* 0/1 */
+ bool use_tls; /* enable TLS */
+ bool no_ssl_verify; /* disable peer verify */
const char *ca_file; /* optional */
const char *ca_dir; /* optional */
const char *cert_file; /* optional */
const char *host = NULL;
const char *username = NULL, *password = NULL, *dbname = NULL, *log_tag = NULL;
struct rspamd_redis_tls_opts tls_opts;
+ /* Duplicated TLS strings to ensure lifetime beyond Lua stack */
+ char *dup_ca_file = NULL, *dup_ca_dir = NULL, *dup_cert_file = NULL,
+ *dup_key_file = NULL, *dup_sni = NULL;
int cbref = -1;
struct rspamd_config *cfg = NULL;
struct rspamd_async_session *session = NULL;
gboolean ret = FALSE;
unsigned int flags = 0;
- memset(&tls_opts, 0, sizeof(tls_opts));
+ memset(&tls_opts, 0, sizeof(tls_opts));
if (lua_istable(L, 1)) {
/* Table version */
}
lua_pop(L, 1);
- /* TLS options (optional) */
- lua_pushstring(L, "ssl");
- lua_gettable(L, -2);
- if (!!lua_toboolean(L, -1)) {
- tls_opts.use_tls = 1;
- }
- lua_pop(L, 1);
-
- lua_pushstring(L, "no_ssl_verify");
- lua_gettable(L, -2);
- if (!!lua_toboolean(L, -1)) {
- tls_opts.no_ssl_verify = 1;
- }
- lua_pop(L, 1);
-
- lua_pushstring(L, "ssl_ca");
- lua_gettable(L, -2);
- if (lua_type(L, -1) == LUA_TSTRING) {
- tls_opts.ca_file = lua_tostring(L, -1);
- }
- lua_pop(L, 1);
-
- lua_pushstring(L, "ssl_ca_dir");
- lua_gettable(L, -2);
- if (lua_type(L, -1) == LUA_TSTRING) {
- tls_opts.ca_dir = lua_tostring(L, -1);
- }
- lua_pop(L, 1);
-
- lua_pushstring(L, "ssl_cert");
- lua_gettable(L, -2);
- if (lua_type(L, -1) == LUA_TSTRING) {
- tls_opts.cert_file = lua_tostring(L, -1);
- }
- lua_pop(L, 1);
-
- lua_pushstring(L, "ssl_key");
- lua_gettable(L, -2);
- if (lua_type(L, -1) == LUA_TSTRING) {
- tls_opts.key_file = lua_tostring(L, -1);
- }
- lua_pop(L, 1);
-
- lua_pushstring(L, "sni");
- lua_gettable(L, -2);
- if (lua_type(L, -1) == LUA_TSTRING) {
- tls_opts.sni = lua_tostring(L, -1);
- }
- lua_pop(L, 1);
+ /* TLS options (optional) */
+ lua_pushstring(L, "ssl");
+ lua_gettable(L, -2);
+ if (!!lua_toboolean(L, -1)) {
+ tls_opts.use_tls = true;
+ }
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "no_ssl_verify");
+ lua_gettable(L, -2);
+ if (!!lua_toboolean(L, -1)) {
+ tls_opts.no_ssl_verify = true;
+ }
+ lua_pop(L, 1);
+
+ /* Duplicate string options to avoid ephemeral Lua string pointers */
+ lua_pushstring(L, "ssl_ca");
+ lua_gettable(L, -2);
+ if (lua_type(L, -1) == LUA_TSTRING) {
+ dup_ca_file = g_strdup(lua_tostring(L, -1));
+ tls_opts.ca_file = dup_ca_file;
+ }
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "ssl_ca_dir");
+ lua_gettable(L, -2);
+ if (lua_type(L, -1) == LUA_TSTRING) {
+ dup_ca_dir = g_strdup(lua_tostring(L, -1));
+ tls_opts.ca_dir = dup_ca_dir;
+ }
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "ssl_cert");
+ lua_gettable(L, -2);
+ if (lua_type(L, -1) == LUA_TSTRING) {
+ dup_cert_file = g_strdup(lua_tostring(L, -1));
+ tls_opts.cert_file = dup_cert_file;
+ }
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "ssl_key");
+ lua_gettable(L, -2);
+ if (lua_type(L, -1) == LUA_TSTRING) {
+ dup_key_file = g_strdup(lua_tostring(L, -1));
+ tls_opts.key_file = dup_key_file;
+ }
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "sni");
+ lua_gettable(L, -2);
+ if (lua_type(L, -1) == LUA_TSTRING) {
+ dup_sni = g_strdup(lua_tostring(L, -1));
+ tls_opts.sni = dup_sni;
+ }
+ lua_pop(L, 1);
lua_pushstring(L, "no_pool");
lua_gettable(L, -2);
if (ret) {
ud->terminated = 0;
- ud->ctx = rspamd_redis_pool_connect_ext(ud->pool,
- dbname, username, password,
- rspamd_inet_address_to_string(addr->addr),
- rspamd_inet_address_get_port(addr->addr),
- &tls_opts);
-
- if (ip) {
- rspamd_inet_address_free(ip);
- }
+ ud->ctx = rspamd_redis_pool_connect_ext(ud->pool,
+ dbname, username, password,
+ rspamd_inet_address_to_string(addr->addr),
+ rspamd_inet_address_get_port(addr->addr),
+ &tls_opts);
+
+ /* Free temporary TLS strings after they have been consumed */
+ g_free(dup_ca_file);
+ g_free(dup_ca_dir);
+ g_free(dup_cert_file);
+ g_free(dup_key_file);
+ g_free(dup_sni);
+
+ if (ip) {
+ rspamd_inet_address_free(ip);
+ }
if (ud->ctx == NULL || ud->ctx->err) {
if (ud->ctx) {
msg_debug_lua_redis("opened redis connection host=%s; lua_ctx=%p; redis_ctx=%p; ud=%p",
host, ctx, ud->ctx, ud);
- return ctx;
- }
+ return ctx;
+ }
- if (ip) {
- rspamd_inet_address_free(ip);
- }
+ if (ip) {
+ rspamd_inet_address_free(ip);
+ }
+
+ /* Free any duplicated TLS strings on error path */
+ g_free(dup_ca_file);
+ g_free(dup_ca_dir);
+ g_free(dup_cert_file);
+ g_free(dup_key_file);
+ g_free(dup_sni);
- return NULL;
+ return NULL;
}
/***
+++ /dev/null
--- Simple unit test to validate TLS options propagation in lua_redis
-local lua_redis = require "lua_redis"
-
--- Build a minimal options table; no network connections are made here
-local opts = {
- servers = '127.0.0.1:6379',
- timeout = 1.0,
- ssl = true,
- no_ssl_verify = true,
- ssl_ca = '/tmp/ca.crt',
- ssl_ca_dir = '/tmp/ca',
- ssl_cert = '/tmp/client.crt',
- ssl_key = '/tmp/client.key',
- sni = 'example.test',
-}
-
-local params = lua_redis.try_load_redis_servers(opts, nil, true)
-
-assert(params, 'try_load_redis_servers returned nil')
-assert(params.ssl == true, 'ssl flag not propagated')
-assert(params.no_ssl_verify == true, 'no_ssl_verify flag not propagated')
-assert(params.ssl_ca == '/tmp/ca.crt', 'ssl_ca not propagated')
-assert(params.ssl_ca_dir == '/tmp/ca', 'ssl_ca_dir not propagated')
-assert(params.ssl_cert == '/tmp/client.crt', 'ssl_cert not propagated')
-assert(params.ssl_key == '/tmp/client.key', 'ssl_key not propagated')
-assert(params.sni == 'example.test', 'sni not propagated')
-
--- Also ensure request helpers pass these options through (no execution)
--- This part only checks that the table has values set that would be consumed
--- by rspamd_redis.make_request/connect in runtime code paths.
-local req_attrs = { task = nil, config = nil, ev_base = nil } -- not used here
-local req_tbl = { 'PING' }
-
--- If we got here, options are present; actual network tests are covered by functional tests.
-return true
-