TLS: Improve CA certifiate configuration options.

This adds support for explicit configuration of a CA certs directory (in
addition to the previously supported bundle file).  For redis-cli, if no
explicit CA configuration is supplied the system-wide default
configuration will be adopted.
This commit is contained in:
Yossi Gottlieb 2019-10-08 17:57:05 +03:00
parent 61733ded14
commit d7f2681a0c
5 changed files with 100 additions and 13 deletions

View File

@ -148,10 +148,12 @@ tcp-keepalive 300
#
# tls-dh-params-file redis.dh
# Configure a CA certificate(s) bundle to authenticate TLS/SSL clients and
# peers.
# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL
# clients and peers. Redis requires an explicit configuration of at least one
# of these, and will not implicitly use the system wide configuration.
#
# tls-ca-cert-file ca.crt
# tls-ca-cert-dir /etc/ssl/certs
# If TLS/SSL clients are required to authenticate using a client side
# certificate, use this directive.

View File

@ -821,6 +821,9 @@ void loadServerConfigFromString(char *config) {
} else if (!strcasecmp(argv[0],"tls-ca-cert-file") && argc == 2) {
zfree(server.tls_ctx_config.ca_cert_file);
server.tls_ctx_config.ca_cert_file = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"tls-ca-cert-dir") && argc == 2) {
zfree(server.tls_ctx_config.ca_cert_dir);
server.tls_ctx_config.ca_cert_dir = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"tls-protocols") && argc >= 2) {
zfree(server.tls_ctx_config.protocols);
server.tls_ctx_config.protocols = zstrdup(argv[1]);
@ -1319,6 +1322,16 @@ void configSetCommand(client *c) {
}
zfree(server.tls_ctx_config.ca_cert_file);
server.tls_ctx_config.ca_cert_file = zstrdup(o->ptr);
} config_set_special_field("tls-ca-cert-dir") {
redisTLSContextConfig tmpctx = server.tls_ctx_config;
tmpctx.ca_cert_dir = (char *) o->ptr;
if (tlsConfigure(&tmpctx) == C_ERR) {
addReplyError(c,
"Unable to configure tls-ca-cert-dir. Check server logs.");
return;
}
zfree(server.tls_ctx_config.ca_cert_dir);
server.tls_ctx_config.ca_cert_dir = zstrdup(o->ptr);
} config_set_bool_field("tls-auth-clients", server.tls_auth_clients) {
} config_set_bool_field("tls-replication", server.tls_replication) {
} config_set_bool_field("tls-cluster", server.tls_cluster) {
@ -1439,6 +1452,7 @@ void configGetCommand(client *c) {
config_get_string_field("tls-key-file",server.tls_ctx_config.key_file);
config_get_string_field("tls-dh-params-file",server.tls_ctx_config.dh_params_file);
config_get_string_field("tls-ca-cert-file",server.tls_ctx_config.ca_cert_file);
config_get_string_field("tls-ca-cert-dir",server.tls_ctx_config.ca_cert_dir);
config_get_string_field("tls-protocols",server.tls_ctx_config.protocols);
config_get_string_field("tls-ciphers",server.tls_ctx_config.ciphers);
config_get_string_field("tls-ciphersuites",server.tls_ctx_config.ciphersuites);
@ -2347,6 +2361,7 @@ int rewriteConfig(char *path) {
rewriteConfigStringOption(state,"tls-key-file",server.tls_ctx_config.key_file,NULL);
rewriteConfigStringOption(state,"tls-dh-params-file",server.tls_ctx_config.dh_params_file,NULL);
rewriteConfigStringOption(state,"tls-ca-cert-file",server.tls_ctx_config.ca_cert_file,NULL);
rewriteConfigStringOption(state,"tls-ca-cert-dir",server.tls_ctx_config.ca_cert_dir,NULL);
rewriteConfigStringOption(state,"tls-protocols",server.tls_ctx_config.protocols,NULL);
rewriteConfigStringOption(state,"tls-ciphers",server.tls_ctx_config.ciphers,NULL);
rewriteConfigStringOption(state,"tls-ciphersuites",server.tls_ctx_config.ciphersuites,NULL);

View File

@ -48,6 +48,7 @@
#include <hiredis.h>
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <hiredis_ssl.h>
#endif
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
@ -194,6 +195,7 @@ static struct config {
int tls;
char *sni;
char *cacert;
char *cacertdir;
char *cert;
char *key;
long repeat;
@ -762,9 +764,61 @@ static int cliSelect(void) {
/* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if
* not building with TLS support.
*/
static int cliSecureConnection(redisContext *c) {
static int cliSecureConnection(redisContext *c, const char **err) {
#ifdef USE_OPENSSL
return redisSecureConnection(c, config.cacert, config.cert, config.key, config.sni);
static SSL_CTX *ssl_ctx = NULL;
if (!ssl_ctx) {
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
if (!ssl_ctx) {
*err = "Failed to create SSL_CTX";
goto error;
}
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
if (config.cacert || config.cacertdir) {
if (!SSL_CTX_load_verify_locations(ssl_ctx, config.cacert, config.cacertdir)) {
*err = "Invalid CA Certificate File/Directory";
goto error;
}
} else {
if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) {
*err = "Failed to use default CA paths";
goto error;
}
}
if (config.cert && !SSL_CTX_use_certificate_chain_file(ssl_ctx, config.cert)) {
*err = "Invalid client certificate";
goto error;
}
if (config.key && !SSL_CTX_use_PrivateKey_file(ssl_ctx, config.key, SSL_FILETYPE_PEM)) {
*err = "Invalid private key";
goto error;
}
}
SSL *ssl = SSL_new(ssl_ctx);
if (!ssl) {
*err = "Failed to create SSL object";
return REDIS_ERR;
}
if (config.sni && !SSL_set_tlsext_host_name(ssl, config.sni)) {
*err = "Failed to configure SNI";
SSL_free(ssl);
return REDIS_ERR;
}
return redisInitiateSSL(c, ssl);
error:
SSL_CTX_free(ssl_ctx);
ssl_ctx = NULL;
return REDIS_ERR;
#else
(void) c;
return REDIS_OK;
@ -788,9 +842,9 @@ static int cliConnect(int flags) {
}
if (!context->err && config.tls) {
if (cliSecureConnection(context) == REDIS_ERR && !context->err) {
/* TODO: this check should be redundant, redis-cli should set err=1 */
fprintf(stderr, "Could not negotiate a TLS connection.\n");
const char *err = NULL;
if (cliSecureConnection(context, &err) == REDIS_ERR && err) {
fprintf(stderr, "Could not negotiate a TLS connection: %s\n", err);
context = NULL;
redisFree(context);
return REDIS_ERR;
@ -1277,7 +1331,11 @@ static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ..
redisFree(c);
c = redisConnect(config.hostip,config.hostport);
if (!c->err && config.tls) {
cliSecureConnection(c);
const char *err = NULL;
if (cliSecureConnection(c, &err) == REDIS_ERR && err) {
fprintf(stderr, "TLS Error: %s\n", err);
exit(1);
}
}
usleep(1000000);
}
@ -1473,6 +1531,8 @@ static int parseOptions(int argc, char **argv) {
config.tls = 1;
} else if (!strcmp(argv[i],"--sni")) {
config.sni = argv[++i];
} else if (!strcmp(argv[i],"--cacertdir")) {
config.cacertdir = argv[++i];
} else if (!strcmp(argv[i],"--cacert")) {
config.cacert = argv[++i];
} else if (!strcmp(argv[i],"--cert")) {
@ -1571,6 +1631,9 @@ static void usage(void) {
#ifdef USE_OPENSSL
" --tls Establish a secure TLS connection.\n"
" --cacert CA Certificate file to verify with.\n"
" --cacertdir Directory where trusted CA certificates are stored.\n"
" If neither cacert nor cacertdir are specified, the default\n"
" system-wide trusted root certs configuration will apply.\n"
" --cert Client certificate to authenticate with.\n"
" --key Private key file to authenticate with.\n"
#endif
@ -2390,7 +2453,13 @@ static int clusterManagerNodeConnect(clusterManagerNode *node) {
if (node->context) redisFree(node->context);
node->context = redisConnect(node->ip, node->port);
if (!node->context->err && config.tls) {
cliSecureConnection(node->context);
const char *err = NULL;
if (cliSecureConnection(node->context, &err) == REDIS_ERR && err) {
fprintf(stderr,"TLS Error: %s\n", err);
redisFree(node->context);
node->context = NULL;
return 0;
}
}
if (node->context->err) {
fprintf(stderr,"Could not connect to Redis at ");

View File

@ -1038,6 +1038,7 @@ typedef struct redisTLSContextConfig {
char *key_file;
char *dh_params_file;
char *ca_cert_file;
char *ca_cert_dir;
char *protocols;
char *ciphers;
char *ciphersuites;

View File

@ -125,8 +125,8 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) {
goto error;
}
if (!ctx_config->ca_cert_file) {
serverLog(LL_WARNING, "No tls-ca-cert-file configured!");
if (!ctx_config->ca_cert_file && !ctx_config->ca_cert_dir) {
serverLog(LL_WARNING, "Either tls-ca-cert-file or tls-ca-cert-dir must be configured!");
goto error;
}
@ -182,9 +182,9 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) {
goto error;
}
if (SSL_CTX_load_verify_locations(ctx, ctx_config->ca_cert_file, NULL) <= 0) {
if (SSL_CTX_load_verify_locations(ctx, ctx_config->ca_cert_file, ctx_config->ca_cert_dir) <= 0) {
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
serverLog(LL_WARNING, "Failed to load CA certificate(s) file: %s: %s", ctx_config->ca_cert_file, errbuf);
serverLog(LL_WARNING, "Failed to configure CA certificate(s) file/directory: %s", errbuf);
goto error;
}