From 71d3e9a2424eb0dc63b78f3684a93dbe49629f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20V=C3=A1rady?= Date: Tue, 30 Apr 2024 20:09:50 +0200 Subject: [PATCH] webhook: implement TLS client authentication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: László Várady --- .../python-modules/webhook/scl/webhook.conf | 23 ++++++++++- syslog-ng/python-modules/webhook/source.py | 38 +++++++++++++++---- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/syslog-ng/python-modules/webhook/scl/webhook.conf b/syslog-ng/python-modules/webhook/scl/webhook.conf index a788878bbe..ba94383680 100644 --- a/syslog-ng/python-modules/webhook/scl/webhook.conf +++ b/syslog-ng/python-modules/webhook/scl/webhook.conf @@ -25,6 +25,12 @@ block source webhook( auth_token("") tls_key_file("") tls_cert_file("") + + # for client authentication + tls_peer_verify(no) + tls_use_system_cert_store(no) + tls_ca_file("") + tls_ca_dir("") ... ) { @@ -35,6 +41,11 @@ block source webhook( "auth_token" => "`auth_token`" "tls_key_file" => "`tls_key_file`" "tls_cert_file" => "`tls_cert_file`" + + "tls_peer_verify" => `tls_peer_verify` + "tls_use_system_cert_store" => `tls_use_system_cert_store` + "tls_ca_file" => "`tls_ca_file`" + "tls_ca_dir" => "`tls_ca_dir`" ) `__VARARGS__` ); @@ -43,9 +54,15 @@ block source webhook( block source webhook-json( port("") auth_token("") + prefix("") tls_key_file("") tls_cert_file("") - prefix("") + + # for client authentication + tls_peer_verify(no) + tls_use_system_cert_store(no) + tls_ca_file("") + tls_ca_dir("") ... ) { @@ -54,6 +71,10 @@ block source webhook-json( webhook( port("`port`") auth_token("`auth_token`") tls_key_file("`tls_key_file`") tls_cert_file("`tls_cert_file`") + + tls_peer_verify(`tls_peer_verify`) + tls_use_system_cert_store(`tls_use_system_cert_store`) + tls_ca_file("`tls_ca_file`") tls_ca_dir("`tls_ca_dir`") `__VARARGS__` ); }; diff --git a/syslog-ng/python-modules/webhook/source.py b/syslog-ng/python-modules/webhook/source.py index 66836db101..0e8999cce4 100644 --- a/syslog-ng/python-modules/webhook/source.py +++ b/syslog-ng/python-modules/webhook/source.py @@ -57,6 +57,7 @@ def get_current_user(self): token = self.request.headers.get("Authorization", "").split(" ") if len(token) != 2: + self.source.logger.debug("Auth failed, missing Authorization header or auth-scheme") return False token = token[1] @@ -72,16 +73,14 @@ def init(self, options: dict[str, Any]) -> bool: if not self.init_options(options): return False - self.ssl_ctx = None - if self.tls_key_file: - self.ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) - self.ssl_ctx.load_cert_chain(self.tls_cert_file, self.tls_key_file) - if not self.port: self.port = 443 if self.tls_key_file else 80 - self.port = int(self.port) + self.ssl_ctx = None + if self.tls_key_file: + self.setup_tls() + self.suspended = threading.Event() self.event_loop = asyncio.new_event_loop() self.request_exit = asyncio.Event() @@ -124,15 +123,40 @@ def request_exit(self) -> None : asyncio.run_coroutine_threadsafe(self.stopServer(), self.event_loop) pass - def log_access(self, req: tornado.web.RequestHandler): + def log_access(self, req: tornado.web.RequestHandler) -> None: self.logger.debug(f"{req.get_status()} {req._request_summary()}") + def setup_tls(self) -> None: + self.ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + self.ssl_ctx.load_cert_chain(certfile=self.tls_cert_file, keyfile=self.tls_key_file) + + if self.tls_peer_verify: + self.logger.debug("Enabling client cert verification") + self.ssl_ctx.verify_mode = ssl.CERT_REQUIRED + else: + self.ssl_ctx.verify_mode = ssl.CERT_NONE + + if self.tls_use_system_cert_store: + self.logger.debug("Using system cert store for client auth") + self.ssl_ctx.load_default_certs(ssl.Purpose.CLIENT_AUTH) + + self.tls_ca_dir = self.tls_ca_dir if self.tls_ca_dir else None + self.tls_ca_file = self.tls_ca_file if self.tls_ca_file else None + + if self.tls_ca_dir or self.tls_ca_file: + self.ssl_ctx.load_verify_locations(cafile=self.tls_ca_file, capath=self.tls_ca_dir) + def init_options(self, options: dict[str, Any]) -> bool: try: self.port = options.get("port") self.auth_token = options.get("auth_token") self.tls_key_file = options.get("tls_key_file") self.tls_cert_file = options.get("tls_cert_file") + + self.tls_peer_verify = bool(options.get("tls_peer_verify", False)) + self.tls_use_system_cert_store = bool(options.get("tls_use_system_cert_store", False)) + self.tls_ca_file = options.get("tls_ca_file") + self.tls_ca_dir = options.get("tls_ca_dir") return True except KeyError as e: self.logger.error(f"Missing option '{e.args[0]}'")