diff --git a/configure.ac b/configure.ac index cf3903ab16..91c166aa76 100644 --- a/configure.ac +++ b/configure.ac @@ -2638,6 +2638,16 @@ AC_ARG_ENABLE(redis_tests, ) AM_CONDITIONAL(ENABLE_REDIS_TESTS, test x$enable_redis_tests = xyes) +AC_ARG_ENABLE(redis_ssl, + [AS_HELP_STRING([--enable-redis-ssl],[Enable redis ssl support @<:@default=no@:>@])], + [case "${enableval}" in + yes) enable_redis_ssl="yes" ;; + no) enable_redis_ssl="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-redis-ssl) ;; + esac], + [enable_redis_ssl=no] +) + if test "x$enable_omhiredis" = "xyes" -o "x$enable_imhiredis" = "xyes" ; then PKG_CHECK_MODULES(HIREDIS, hiredis >= 0.10.1, [], [AC_SEARCH_LIBS(redisConnectWithTimeout, hiredis, @@ -2662,6 +2672,39 @@ if test "x$enable_omhiredis" = "xyes" -o "x$enable_imhiredis" = "xyes" ; then [AC_MSG_ERROR([hiredis not found])] )] ) + if test "x$enable_redis_ssl" = "xyes"; then + PKG_CHECK_MODULES(HIREDIS_SSL, hiredis_ssl >= 1.0.0, + # hiredis_ssl found using pkg-config + [ + AC_DEFINE(HIREDIS_SSL, 1, [TLS support enabled in hiredis]) + ], + # hiredis_ssl not found with pkg-static, searching for it manually + [AC_SEARCH_LIBS(redisCreateSSLContext, hiredis_ssl, + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[ #include + #include + ]], + [[ #define major 1 + #define minor 0 + #define patch 0 + #if (( HIREDIS_MAJOR > major ) || \ + (( HIREDIS_MAJOR == major ) && ( HIREDIS_MINOR > minor )) || \ + (( HIREDIS_MAJOR == major ) && ( HIREDIS_MINOR == minor ) && ( HIREDIS_PATCH >= patch ))) \ + /* OK */ + #else + # error Hiredis_ssl version must be >= major.minor.path + #endif + ]] + )], + [], + [AC_MSG_ERROR([hiredis_ssl version must be >= 1.0.0])] + )], + [AC_MSG_WARN([hiredis_ssl not found, no TLS support in hiredis])] + )] + ) + fi + fi if test "x$enable_imhiredis" = "xyes" ; then @@ -2672,6 +2715,7 @@ if test "x$enable_imhiredis" = "xyes" ; then ], # libevent not found [AC_MSG_ERROR([no libevent >= 2.0 found with pthreads support, imhiredis cannot use pub/sub])]) + fi if test "x$enable_imhiredis" = "xyes" || test "x$enable_omhiredis" = "xyes"; then @@ -2982,6 +3026,7 @@ echo " kafka static linking enabled: $enable_kafka_static" echo " qpid proton static linking enabled: $enable_qpidproton_static" echo " atomic operations enabled: $enable_atomic_operations" echo " libcap-ng support enabled: $enable_libcapng" +echo " redis ssl support enabled: $enable_redis_ssl" echo echo "---{ input plugins }---" diff --git a/contrib/omhiredis/Makefile.am b/contrib/omhiredis/Makefile.am index 2332be4b83..09aed69d62 100644 --- a/contrib/omhiredis/Makefile.am +++ b/contrib/omhiredis/Makefile.am @@ -1,7 +1,7 @@ pkglib_LTLIBRARIES = omhiredis.la omhiredis_la_SOURCES = omhiredis.c -omhiredis_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) $(HIREDIS_CFLAGS) +omhiredis_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) $(HIREDIS_CFLAGS) $(HIREDIS_SSL_CFLAGS) omhiredis_la_LDFLAGS = -module -avoid-version -omhiredis_la_LIBADD = $(HIREDIS_LIBS) +omhiredis_la_LIBADD = $(HIREDIS_LIBS) $(HIREDIS_SSL_LIBS) EXTRA_DIST = diff --git a/contrib/omhiredis/omhiredis.c b/contrib/omhiredis/omhiredis.c index eb22ed859d..4e682bd73f 100644 --- a/contrib/omhiredis/omhiredis.c +++ b/contrib/omhiredis/omhiredis.c @@ -18,6 +18,9 @@ * * Author: Brian Knox * +* +* Author: Jérémie Jourdin (TLS support) +* */ @@ -32,6 +35,9 @@ #include #include #include +#ifdef HIREDIS_SSL +#include +#endif #include "rsyslog.h" #include "conf.h" @@ -93,6 +99,14 @@ typedef struct _instanceData { - $.redis!index Those 2 infos can either be provided through usage of imhiredis or set manually with Rainerscript */ +#ifdef HIREDIS_SSL + sbool use_tls; /* Should we use TLS to connect to redis ? */ + char *ca_cert_bundle; /* CA bundle file */ + char *ca_cert_dir; /* Path of trusted certificates */ + char *client_cert; /* Client certificate */ + char *client_key; /* Client private key */ + char *sni; /* TLS Server Name Indication */ +#endif } instanceData; @@ -100,6 +114,10 @@ typedef struct wrkrInstanceData { instanceData *pData; /* instanc data */ redisContext *conn; /* redis connection */ int count; /* count of command sent for current batch */ +#ifdef HIREDIS_SSL + redisSSLContext *ssl_conn; /* redis ssl connection */ + redisSSLContextError ssl_error; /* ssl error handler */ +#endif } wrkrInstanceData_t; static struct cnfparamdescr actpdescr[] = { @@ -122,6 +140,14 @@ static struct cnfparamdescr actpdescr[] = { { "stream.dynaKeyAck", eCmdHdlrBinary, 0 }, { "stream.dynaGroupAck", eCmdHdlrBinary, 0 }, { "stream.dynaIndexAck", eCmdHdlrBinary, 0 }, +#ifdef HIREDIS_SSL + { "use_tls", eCmdHdlrBinary, 0 }, + { "ca_cert_bundle", eCmdHdlrGetWord, 0 }, + { "ca_cert_dir", eCmdHdlrGetWord, 0 }, + { "client_cert", eCmdHdlrGetWord, 0 }, + { "client_key", eCmdHdlrGetWord, 0 }, + { "sni", eCmdHdlrGetWord, 0 }, +#endif }; static struct cnfparamblk actpblk = { @@ -137,6 +163,10 @@ ENDcreateInstance BEGINcreateWrkrInstance CODESTARTcreateWrkrInstance pWrkrData->conn = NULL; /* Connect later */ +#ifdef HIREDIS_SSL + pWrkrData->ssl_conn = NULL; /* Connect later */ + pWrkrData->ssl_error = REDIS_SSL_CTX_NONE; +#endif ENDcreateWrkrInstance BEGINisCompatibleWithFeature @@ -152,6 +182,12 @@ static void closeHiredis(wrkrInstanceData_t *pWrkrData) redisFree(pWrkrData->conn); pWrkrData->conn = NULL; } +#ifdef HIREDIS_SSL + if(pWrkrData->ssl_conn != NULL) { + redisFreeSSLContext(pWrkrData->ssl_conn); + pWrkrData->ssl_conn = NULL; + } +#endif } /* Free our instance data. */ @@ -168,6 +204,13 @@ CODESTARTfreeInstance free(pData->streamGroupAck); free(pData->streamIndexAck); free(pData->streamOutField); +#ifdef HIREDIS_SSL + free(pData->ca_cert_bundle); + free(pData->ca_cert_dir); + free(pData->client_cert); + free(pData->client_key); + free(pData->sni); +#endif ENDfreeInstance BEGINfreeWrkrInstance @@ -195,13 +238,34 @@ static rsRetVal initHiredis(wrkrInstanceData_t *pWrkrData, int bSilent) struct timeval timeout = { 1, 500000 }; /* 1.5 seconds */ pWrkrData->conn = redisConnectWithTimeout(server, pWrkrData->pData->port, timeout); - if (pWrkrData->conn->err) { + if (pWrkrData->conn == NULL || pWrkrData->conn->err) { if(!bSilent) LogError(0, RS_RET_SUSPENDED, "can not initialize redis handle"); ABORT_FINALIZE(RS_RET_SUSPENDED); } +#ifdef HIREDIS_SSL + if (pWrkrData->pData->use_tls) { + pWrkrData->ssl_conn = redisCreateSSLContext(pWrkrData->pData->ca_cert_bundle, pWrkrData->pData->ca_cert_dir, pWrkrData->pData->client_cert, pWrkrData->pData->client_key, pWrkrData->pData->sni, &pWrkrData->ssl_error); + if (!pWrkrData->ssl_conn || pWrkrData->ssl_error != REDIS_SSL_CTX_NONE) { + LogError(0, NO_ERRCODE, "omhiredis: SSL Context error: %s", redisSSLContextGetError(pWrkrData->ssl_error)); + if(!bSilent) + LogError(0, RS_RET_SUSPENDED, + "[TLS] can not initialize redis handle"); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + if (redisInitiateSSLWithContext(pWrkrData->conn, pWrkrData->ssl_conn) != REDIS_OK) { + LogError(0, NO_ERRCODE, "omhiredis: %s", pWrkrData->conn->errstr); + LogError(0, NO_ERRCODE, "omhiredis: SSL Context error: %s", redisSSLContextGetError(pWrkrData->ssl_error)); + if(!bSilent) + LogError(0, RS_RET_SUSPENDED, + "[TLS] can not initialize redis handle"); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + } +#endif + if (pWrkrData->pData->serverpassword != NULL) { reply = redisCommand(pWrkrData->conn, "AUTH %s", (char*) pWrkrData->pData->serverpassword); if (reply == NULL) { @@ -219,6 +283,12 @@ static rsRetVal initHiredis(wrkrInstanceData_t *pWrkrData, int bSilent) redisFree(pWrkrData->conn); pWrkrData->conn = NULL; } +#ifdef HIREDIS_SSL + if (iRet != RS_RET_OK && pWrkrData-> ssl_conn != NULL) { + redisFreeSSLContext(pWrkrData->ssl_conn); + pWrkrData->ssl_conn = NULL; + } +#endif if (reply != NULL) freeReplyObject(reply); RETiRet; } @@ -470,6 +540,14 @@ setInstParamDefaults(instanceData *pData) pData->streamCapacityLimit = 0; pData->streamAck = 0; pData->streamDel = 0; +#ifdef HIREDIS_SSL + pData->use_tls = 0; + pData->ca_cert_bundle = NULL; + pData->ca_cert_dir = NULL; + pData->client_cert = NULL; + pData->client_key = NULL; + pData->sni = NULL; +#endif } /* here is where the work to set up a new instance @@ -545,6 +623,20 @@ CODESTARTnewActInst } else if(!strcmp(actpblk.descr[i].name, "expiration")) { pData->expiration = pvals[i].val.d.n; dbgprintf("omhiredis: expiration set to %d\n", pData->expiration); +#ifdef HIREDIS_SSL + } else if(!strcmp(actpblk.descr[i].name, "use_tls")) { + pData->use_tls = pvals[i].val.d.n; + } else if(!strcmp(actpblk.descr[i].name, "ca_cert_bundle")) { + pData->ca_cert_bundle = (char*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "ca_cert_dir")) { + pData->ca_cert_dir = (char*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "client_cert")) { + pData->client_cert = (char*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "client_key")) { + pData->client_key = (char*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "sni")) { + pData->sni = (char*)es_str2cstr(pvals[i].val.d.estr, NULL); +#endif } else { dbgprintf("omhiredis: program error, non-handled " "param '%s'\n", actpblk.descr[i].name); @@ -552,6 +644,13 @@ CODESTARTnewActInst } dbgprintf("omhiredis: checking config sanity\n"); + +#ifdef HIREDIS_SSL + if((pData->client_cert == NULL) ^ (pData->client_key == NULL)){ + LogMsg(0, RS_RET_PARAM_ERROR, LOG_ERR, "omhiredis: \"client_cert\" and \"client_key\" must be specified together!"); + ABORT_FINALIZE(RS_RET_PARAM_ERROR); + } +#endif if (!pData->modeDescription) { dbgprintf("omhiredis: no mode specified, setting it to 'template'\n"); @@ -750,4 +849,10 @@ CODEmodInit_QueryRegCFSLineHdlr ABORT_FINALIZE(RS_RET_ERR); } DBGPRINTF("omhiredis: module compiled with rsyslog version %s.\n", VERSION); + +#ifdef HIREDIS_SSL + // initialize OpenSSL + redisInitOpenSSL(); +#endif + ENDmodInit