Skip to content

Commit

Permalink
TLS CA directory support (#442)
Browse files Browse the repository at this point in the history
Adding the ability to configure CA certificate directory using --tls-ca-cert-dir
  • Loading branch information
fadidahanna committed Jul 7, 2022
1 parent e7ceb38 commit 3846e01
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 14 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/daily.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ jobs:
runs-on: macos-latest
if: |
(github.event_name == 'workflow_dispatch' ||
(github.event_name == 'schedule' && github.repository == 'redislabs/redisraft')) && !contains(github.event.inputs.skipjobs, 'macos')
(github.event_name == 'schedule' && github.repository == 'redislabs/redisraft')) &&
!contains(github.event.inputs.skipjobs, 'macos') && !contains(github.event.inputs.skipjobs, 'tls')
timeout-minutes: 14400
steps:
- name: prep
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ utils/create-cluster/*.db.idx
venv
buildinfo.h
build/*
jepsen/docker/store/*
tests/tls/*
3 changes: 2 additions & 1 deletion docs/TLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ RedisRaft's TLS support mirrors Redis's native TLS support.
One runs the underlying redis server in the same manner that one would run a redis server with TLS

```
redis-server --tls-port <por> --port 0 --tls-cert-file <file> --tls-key-file <file> --tls-ca-cert-file <file>
redis-server --tls-port <por> --port 0 --tls-cert-file <file> --tls-key-file <file> --tls-ca-cert-file <file> --tls-ca-cert-dir <dir>
```

In addition, one has to pass the `tls-enabled yes` option to the redisraft module
Expand All @@ -23,6 +23,7 @@ RedisRaft is configured by reusing the Redis server's TLS configuration.
| Config Key Name | Meaning |
|--------------------------|-----------------------------------------------------------------------------------------------------------|
| tls-ca-cert-file | File Containing the CA Certificate |
| tls-ca-cert-dir | Directory Containing the CA Certificate |
| tls-key-file | File Containing the PEM encoded private key file |
| tls-client-key-file | File Containing the PEM encoded private key file for client connections (overrides value of tls-key-file) |
| tls-cert-file | File Containing the PEM encoded public signed CERT |
Expand Down
19 changes: 13 additions & 6 deletions src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ static char *getRedisConfig(RedisModuleCtx *ctx, const char *name)
}

str = RedisModule_CallReplyStringPtr(reply_name, &len);
if (len == 0) {
goto exit;
}
buf = RedisModule_Alloc(len + 1);
memcpy(buf, str, len);
buf[len] = '\0';
Expand Down Expand Up @@ -197,7 +200,8 @@ static int tlsPasswordCallback(char *buf, int size, int rwflag, void *u)
return (int) pass_len;
}

static SSL_CTX *generateSSLContext(const char *cacert,
static SSL_CTX *generateSSLContext(const char *cacert_filename,
const char *capath,
const char *cert,
const char *key,
const char *keypass)
Expand All @@ -222,7 +226,7 @@ static SSL_CTX *generateSSLContext(const char *cacert,
SSL_CTX_set_default_passwd_cb_userdata(ctx, RedisModule_Strdup(keypass));
}

if (!SSL_CTX_load_verify_locations(ctx, cacert, NULL)) {
if (!SSL_CTX_load_verify_locations(ctx, cacert_filename, capath)) {
LOG_WARNING("SSL_CTX_load_verify_locations(): %s",
ERR_reason_error_string(ERR_peek_last_error()));
goto error;
Expand Down Expand Up @@ -250,9 +254,10 @@ static SSL_CTX *generateSSLContext(const char *cacert,
static RRStatus updateTLSConfig(RedisRaftConfig *config, RedisModuleCtx *ctx)
{
int ret = RR_OK;
char *clientkey, *key, *keypass, *cacert, *cert;
char *clientkey, *key, *keypass, *cacert_filename, *capath, *cert;

cacert = getRedisConfig(ctx, "tls-ca-cert-file");
cacert_filename = getRedisConfig(ctx, "tls-ca-cert-file");
capath = getRedisConfig(ctx, "tls-ca-cert-dir");

clientkey = getRedisConfig(ctx, "tls-client-key-file");
if (clientkey && *clientkey != '\0') {
Expand All @@ -265,7 +270,7 @@ static RRStatus updateTLSConfig(RedisRaftConfig *config, RedisModuleCtx *ctx)
keypass = getRedisConfig(ctx, "tls-key-file-pass");
}

SSL_CTX *ssl = generateSSLContext(cacert, cert, key, keypass);
SSL_CTX *ssl = generateSSLContext(cacert_filename, capath, cert, key, keypass);
if (!ssl) {
ret = RR_ERROR;
goto out;
Expand All @@ -278,7 +283,8 @@ static RRStatus updateTLSConfig(RedisRaftConfig *config, RedisModuleCtx *ctx)
config->ssl = ssl;

out:
RedisModule_Free(cacert);
RedisModule_Free(cacert_filename);
RedisModule_Free(capath);
RedisModule_Free(cert);
RedisModule_Free(key);
RedisModule_Free(keypass);
Expand Down Expand Up @@ -804,6 +810,7 @@ void ConfigRedisEventCallback(RedisModuleCtx *ctx,
const char *conf = ei->config_names[i];

if (!strcmp(conf, "tls-ca-cert-file") ||
!strcmp(conf, "tls-ca-cert-dir") ||
!strcmp(conf, "tls-cert-file") ||
!strcmp(conf, "tls-key-file") ||
!strcmp(conf, "tls-key-file-pass") ||
Expand Down
18 changes: 12 additions & 6 deletions tests/integration/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def run(self):

class RedisRaft(object):
def __init__(self, _id, port, config, redis_args=None, raft_args=None,
use_id_arg=True, cluster_id=0, password=None):
use_id_arg=True, cluster_id=0, password=None, tls_ca_cert_location=None):
self.id = _id
self.cluster_id = cluster_id
self.guid = str(uuid.uuid4())
Expand All @@ -82,12 +82,17 @@ def __init__(self, _id, port, config, redis_args=None, raft_args=None,
self.args += ['--requirepass', password]

self.cacert = os.getcwd() + '/tests/tls/ca.crt'
self.cacert_dir = os.getcwd() + '/tests/tls'
self.cert = os.getcwd() + '/tests/tls/redis.crt'
self.key = os.getcwd() + '/tests/tls/redis.key'

if config.tls:
if tls_ca_cert_location == 'dir' or tls_ca_cert_location == 'both':
self.args += ['--tls-ca-cert-dir', self.cacert_dir]
# default behaviour - only file configuration
if not tls_ca_cert_location or tls_ca_cert_location == 'file' or tls_ca_cert_location == 'both':
self.args += ['--tls-ca-cert-file', self.cacert]
self.args += ['--tls-port', str(port),
'--tls-ca-cert-file', self.cacert,
'--tls-cert-file', self.cert,
'--tls-key-file', self.key,
'--tls-key-file-pass', 'redisraft']
Expand Down Expand Up @@ -499,7 +504,7 @@ def node_addresses(self):
return [n.address for n in self.nodes.values()]

def create(self, node_count, raft_args=None, cluster_id=None, password=None,
prepopulate_log=0):
prepopulate_log=0, tls_ca_cert_location=None):
if raft_args is None:
raft_args = {}
self.raft_args = raft_args.copy()
Expand All @@ -508,7 +513,8 @@ def create(self, node_count, raft_args=None, cluster_id=None, password=None,
config=self.config,
raft_args=raft_args,
cluster_id=self.cluster_id,
password=password)
password=password,
tls_ca_cert_location=tls_ca_cert_location)
for x in range(1, node_count + 1)}
self.next_id = node_count + 1
for _id, node in self.nodes.items():
Expand Down Expand Up @@ -537,7 +543,7 @@ def add_initialized_node(self, node):

def add_node(self, raft_args=None, port=None, cluster_setup=True,
node_id=None, use_cluster_args=False, single_run=False,
join_addr_list=None, redis_args=None, **kwargs):
join_addr_list=None, redis_args=None, tls_ca_cert_location=None,**kwargs):
_raft_args = raft_args
if use_cluster_args:
_raft_args = self.raft_args
Expand All @@ -548,7 +554,7 @@ def add_node(self, raft_args=None, port=None, cluster_setup=True,
node = None
try:
node = RedisRaft(_id, port, self.config, redis_args,
raft_args=_raft_args, **kwargs)
raft_args=_raft_args, tls_ca_cert_location=tls_ca_cert_location, **kwargs)
if cluster_setup:
if self.nodes:
if join_addr_list is None:
Expand Down
21 changes: 21 additions & 0 deletions tests/integration/test_sanity.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,3 +429,24 @@ def test_tls_reconfig(cluster):
commit_idx = cluster.leader_node().commit_index()
cluster.node(3).wait_for_commit_index(commit_idx, gt_ok=True)
cluster.node(3).wait_for_log_applied()


@pytest.mark.skipif("not config.getoption('tls')")
def test_tls_ca_cert_dir(cluster):
cluster.create(3, tls_ca_cert_location='dir')
assert cluster.execute('set', 'key', 'value')
assert cluster.execute('get', 'key') == b'value'


@pytest.mark.skipif("not config.getoption('tls')")
def test_tls_ca_cert_file(cluster):
cluster.create(3, tls_ca_cert_location='file')
assert cluster.execute('set', 'key', 'value')
assert cluster.execute('get', 'key') == b'value'


@pytest.mark.skipif("not config.getoption('tls')")
def test_tls_ca_cert_both(cluster):
cluster.create(3, tls_ca_cert_location='both')
assert cluster.execute('set', 'key', 'value')
assert cluster.execute('get', 'key') == b'value'
2 changes: 2 additions & 0 deletions utils/gen-test-certs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,5 @@ generate_cert "$DIR" server "Server-only" "-extfile ${DIR}/openssl.cnf -extensio
generate_cert "$DIR" redis "Generic-cert" "" true

[ -f $DIR/redis.dh ] || openssl dhparam -out $DIR/redis.dh 2048

c_rehash ${DIR}

0 comments on commit 3846e01

Please sign in to comment.