From b380ab9024ecccfc31ee9448c21d6fd3d826a0aa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20H=C3=A1k?= <jan.hak@nic.cz>
Date: Wed, 5 Mar 2025 13:21:40 +0100
Subject: [PATCH] redis: access redis using GnuTLS

---
 Knot.files                |   7 ++-
 src/knot/Makefile.inc     |   6 ++
 src/knot/common/hiredis.c | 127 ++++++++++++++++++++++++++++++++++++++
 src/knot/common/hiredis.h |  27 ++++++++
 src/knot/conf/schema.c    |   2 +
 src/knot/conf/schema.h    |   2 +
 src/knot/zone/zonefile.c  |  27 ++++++++
 7 files changed, 196 insertions(+), 2 deletions(-)
 create mode 100644 src/knot/common/hiredis.c
 create mode 100644 src/knot/common/hiredis.h

diff --git a/Knot.files b/Knot.files
index 2a8ccacb22..73f709de9f 100644
--- a/Knot.files
+++ b/Knot.files
@@ -32,8 +32,6 @@ src/contrib/libngtcp2/ngtcp2/crypto/shared.h
 src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_acktr.c
 src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_acktr.h
 src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_addr.c
-src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_dcidtr.c
-src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_dcidtr.h
 src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_addr.h
 src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_balloc.c
 src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_balloc.h
@@ -52,6 +50,8 @@ src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conv.c
 src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_conv.h
 src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_crypto.c
 src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_crypto.h
+src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_dcidtr.c
+src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_dcidtr.h
 src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_err.c
 src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_err.h
 src/contrib/libngtcp2/ngtcp2/lib/ngtcp2_frame_chain.c
@@ -180,6 +180,8 @@ src/knot/common/evsched.c
 src/knot/common/evsched.h
 src/knot/common/fdset.c
 src/knot/common/fdset.h
+src/knot/common/hiredis.c
+src/knot/common/hiredis.h
 src/knot/common/log.c
 src/knot/common/log.h
 src/knot/common/process.c
@@ -566,6 +568,7 @@ src/libzscanner/scanner.h
 src/libzscanner/scanner.rl
 src/libzscanner/scanner_body.rl
 src/redis/knot.c
+src/redis/misc/events.c
 src/utils/common/exec.c
 src/utils/common/exec.h
 src/utils/common/hex.c
diff --git a/src/knot/Makefile.inc b/src/knot/Makefile.inc
index 678b0754fa..f6c0a9c09e 100644
--- a/src/knot/Makefile.inc
+++ b/src/knot/Makefile.inc
@@ -228,6 +228,12 @@ libknotd_la_SOURCES = \
 	knot/zone/zonefile.c			\
 	knot/zone/zonefile.h
 
+if ENABLE_REDIS
+libknotd_la_SOURCES += \
+	knot/common/hiredis.c	\
+	knot/common/hiredis.h
+endif ENABLE_REDIS
+
 if ENABLE_QUIC
 libknotd_la_SOURCES += \
 	knot/query/quic-requestor.c		\
diff --git a/src/knot/common/hiredis.c b/src/knot/common/hiredis.c
new file mode 100644
index 0000000000..4c3918b3f3
--- /dev/null
+++ b/src/knot/common/hiredis.c
@@ -0,0 +1,127 @@
+/*  Copyright (C) 2025 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <unistd.h>
+#include <hiredis/alloc.h>
+#include <hiredis/sds.h>
+
+#include "knot/common/hiredis.h"
+#include "libknot/errcode.h"
+#include "libknot/quic/tls.h"
+
+typedef struct {
+	struct knot_tls_ctx *tls;
+	struct knot_tls_conn *conn;
+} redis_tls_ctx_t;
+
+static void knot_redis_tls_close(redisContext *ctx);
+static void knot_redis_tls_free(void *privctx);
+static ssize_t knot_redis_tls_read(struct redisContext *ctx, char *buff, size_t size);
+static ssize_t knot_redis_tls_write(struct redisContext *ctx);
+
+redisContextFuncs redisContextGnuTLSFuncs = {
+	.close = knot_redis_tls_close,
+	.free_privctx = knot_redis_tls_free,
+	.read = knot_redis_tls_read,
+	.write = knot_redis_tls_write
+};
+
+static void ctx_deinit(redis_tls_ctx_t *ctx)
+{
+	if (ctx != NULL) {
+		if (ctx->tls != NULL) {
+			knot_creds_free(ctx->tls->creds);
+		}
+		knot_tls_conn_del(ctx->conn);
+		knot_tls_ctx_free(ctx->tls);
+		hi_free(ctx);
+	}
+}
+
+static void knot_redis_tls_close(redisContext *ctx)
+{
+	redis_tls_ctx_t *tls_ctx = ctx->privctx;
+	if (ctx && ctx->fd != REDIS_INVALID_FD) {
+		knot_tls_conn_del(tls_ctx->conn);
+		close(ctx->fd);
+		ctx->fd = REDIS_INVALID_FD;
+	}
+}
+
+static void knot_redis_tls_free(void *privctx)
+{
+	redis_tls_ctx_t *tls_ctx = privctx;
+	ctx_deinit(tls_ctx);
+}
+
+static ssize_t knot_redis_tls_read(struct redisContext *ctx, char *buff, size_t size)
+{
+	redis_tls_ctx_t *tls_ctx = ctx->privctx;
+
+	int ret = knot_tls_recv(tls_ctx->conn, buff, size);
+	if (ret >= 0) {
+		return ret;
+	} else if (ret == KNOT_NET_ERECV ||
+	           ret == KNOT_NET_ECONNECT ||
+	           ret == KNOT_NET_EHSHAKE ||
+	           ret == KNOT_ETIMEOUT
+	) {
+		return -1;
+	}
+	return 0;
+}
+
+static ssize_t knot_redis_tls_write(struct redisContext *ctx)
+{
+	redis_tls_ctx_t *tls_ctx = ctx->privctx;
+
+	int ret = knot_tls_send(tls_ctx->conn, ctx->obuf, sdslen(ctx->obuf));
+	if (ret >= 0) {
+		return ret;
+	} else if (ret == KNOT_NET_ESEND ||
+	           ret == KNOT_NET_ECONNECT ||
+	           ret == KNOT_NET_EHSHAKE ||
+	           ret == KNOT_ETIMEOUT
+	) {
+		return -1;
+	}
+	return 0;
+}
+
+int hiredis_attach_gnutls(redisContext *ctx, struct knot_creds *creds)
+{
+	redis_tls_ctx_t *privctx = hi_calloc(1, sizeof(redis_tls_ctx_t));
+	if (ctx == NULL) {
+		return KNOT_ENOMEM;
+	}
+
+	privctx->tls = knot_tls_ctx_new(creds, 10000, 10000, KNOT_TLS_CLIENT);
+	if (privctx->tls == NULL) {
+		ctx_deinit(privctx);
+		return KNOT_EINVAL;
+	}
+
+	privctx->conn = knot_tls_conn_new(privctx->tls, ctx->fd);
+	if (privctx->conn == NULL) {
+		ctx_deinit(privctx);
+		return KNOT_ECONN;
+	}
+
+	ctx->funcs = &redisContextGnuTLSFuncs;
+	ctx->privctx = privctx;
+
+	return KNOT_EOK;
+}
diff --git a/src/knot/common/hiredis.h b/src/knot/common/hiredis.h
new file mode 100644
index 0000000000..864d7cb0fc
--- /dev/null
+++ b/src/knot/common/hiredis.h
@@ -0,0 +1,27 @@
+/*  Copyright (C) 2025 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \brief Extension of hiredis to support GnuTLS backend.
+ */
+
+#pragma once
+
+#include <hiredis/hiredis.h>
+
+#include "libknot/quic/tls_common.h"
+
+int hiredis_attach_gnutls(redisContext *ctx, struct knot_creds *creds);
diff --git a/src/knot/conf/schema.c b/src/knot/conf/schema.c
index 4765813667..0388c53fee 100644
--- a/src/knot/conf/schema.c
+++ b/src/knot/conf/schema.c
@@ -331,6 +331,8 @@ static const yp_item_t desc_database[] = {
 	{ C_CATALOG_DB_MAX_SIZE, YP_TINT,  YP_VINT = { MEGA(5), VIRT_MEM_LIMIT(GIGA(100)),
 	                                               VIRT_MEM_LIMIT(GIGA(20)), YP_SSIZE } },
 	{ C_ZONE_DB_LISTEN,      YP_TADDR, YP_VADDR = { 6379 }, YP_FNONE, { check_listen } },
+	{ C_ZONE_DB_TLS,         YP_TBOOL, YP_VNONE },
+	{ C_ZONE_DB_CERT_KEY,    YP_TB64,  YP_VNONE, YP_FMULTI, { check_cert_pin } },
 	{ C_COMMENT,             YP_TSTR,  YP_VNONE },
 	{ NULL }
 };
diff --git a/src/knot/conf/schema.h b/src/knot/conf/schema.h
index 922aea33e2..431da39db1 100644
--- a/src/knot/conf/schema.h
+++ b/src/knot/conf/schema.h
@@ -194,6 +194,8 @@
 #define C_ZONEMD_VERIFY		"\x0D""zonemd-verify"
 #define C_ZONE_BACKEND		"\x0C""zone-backend"
 #define C_ZONE_DB_LISTEN	"\x0E""zone-db-listen"
+#define C_ZONE_DB_TLS		"\x0B""zone-db-tls"
+#define C_ZONE_DB_CERT_KEY	"\x10""zone-db-cert-key"
 #define C_ZONE_MAX_SIZE		"\x0D""zone-max-size"
 #define C_ZONE_MAX_TTL		"\x0C""zone-max-ttl"
 #define C_ZSK_LIFETIME		"\x0C""zsk-lifetime"
diff --git a/src/knot/zone/zonefile.c b/src/knot/zone/zonefile.c
index 58aa897f62..5b64461d9e 100644
--- a/src/knot/zone/zonefile.c
+++ b/src/knot/zone/zonefile.c
@@ -197,6 +197,9 @@ int zonefile_open(zloader_t *loader, const char *source, const knot_dname_t *ori
 }
 
 #ifdef ENABLE_REDIS
+
+#include "knot/common/hiredis.h"
+
 redisContext *zone_rdb_connect(conf_t *conf)
 {
 	conf_val_t db_listen = conf_db_param(conf, C_ZONE_DB_LISTEN);
@@ -225,6 +228,30 @@ redisContext *zone_rdb_connect(conf_t *conf)
 		return NULL;
 	}
 
+	if (conf_get_bool(conf, C_DB, C_ZONE_DB_TLS)) {
+		char *cert_file = conf_tls(conf, C_CERT_FILE);
+		char *key_file = conf_tls(conf, C_KEY_FILE);
+		//
+		free(key_file);
+		free(cert_file);
+
+		conf_val_t val = conf_db_param(conf, C_ZONE_DB_CERT_KEY);
+		size_t pin_len;
+		const uint8_t *pin = conf_bin(&val, &pin_len);
+		struct knot_creds *creds = knot_creds_init_peer(NULL, pin, pin_len);
+		if (creds == NULL) {
+			redisFree(rdb);
+			return NULL;
+		}
+
+		int ret = hiredis_attach_gnutls(rdb, creds);
+		if (ret != KNOT_EOK) {
+			knot_creds_free(creds);
+			redisFree(rdb);
+			return NULL;
+		}
+	}
+
 	return rdb;
 }
 
-- 
GitLab