Use TLS on revocation notification webhook

- Include system installed CA certificates when verifying webhook
   server certificate
- Include the CA certificates added via configuration file option
  'trusted_server_ca'

Resolves: RHEL-78057
Resolves: RHEL-78313
Resolves: RHEL-78316

Signed-off-by: Sergio Correia <scorreia@redhat.com>
This commit is contained in:
Sergio Correia 2025-02-05 16:22:30 +00:00
parent 8d907c5ddf
commit 5d41ae0699
No known key found for this signature in database
GPG Key ID: D0D219ED1F7E762C
2 changed files with 179 additions and 1 deletions

View File

@ -0,0 +1,167 @@
From 4bd644b74719fdbb6c521d3d5eb2430d8dc18b36 Mon Sep 17 00:00:00 2001
From: Sergio Correia <scorreia@redhat.com>
Date: Wed, 5 Feb 2025 16:16:25 +0000
Subject: [PATCH 16/16] Use TLS on revocation notification webhook
---
keylime/requests_client.py | 5 ++
keylime/revocation_notifier.py | 91 +++++++++++++++++++++++-----------
2 files changed, 68 insertions(+), 28 deletions(-)
diff --git a/keylime/requests_client.py b/keylime/requests_client.py
index 85a175c..e993fbc 100644
--- a/keylime/requests_client.py
+++ b/keylime/requests_client.py
@@ -1,3 +1,4 @@
+import re
import ssl
from typing import Any, Dict, Optional
@@ -15,6 +16,10 @@ class RequestsClient:
ignore_hostname: bool = True,
**kwargs: Any,
) -> None:
+ # Remove eventual "http?://" from the base url
+ if base_url.startswith("http"):
+ base_url = re.sub(r"https?://", "", base_url)
+
if tls_enabled:
self.base_url = f"https://{base_url}"
else:
diff --git a/keylime/revocation_notifier.py b/keylime/revocation_notifier.py
index 5cc8b1a..434bf64 100644
--- a/keylime/revocation_notifier.py
+++ b/keylime/revocation_notifier.py
@@ -9,8 +9,9 @@ from typing import Any, Callable, Dict, Optional, Set
import requests
-from keylime import config, crypto, json, keylime_logging
+from keylime import config, crypto, json, keylime_logging, web_util
from keylime.common import retry
+from keylime.requests_client import RequestsClient
logger = keylime_logging.init_logging("revocation_notifier")
broker_proc: Optional[Process] = None
@@ -109,7 +110,10 @@ def notify(tosend: Dict[str, Any]) -> None:
exponential_backoff = config.getboolean("verifier", "exponential_backoff")
next_retry = retry.retry_time(exponential_backoff, interval, i, logger)
logger.debug(
- "Unable to publish revocation message %d times, trying again in %f seconds: %s", i, next_retry, e
+ "Unable to publish revocation message %d times, trying again in %f seconds: %s",
+ i,
+ next_retry,
+ e,
)
time.sleep(next_retry)
mysock.close()
@@ -132,30 +136,50 @@ def notify_webhook(tosend: Dict[str, Any]) -> None:
def worker_webhook(tosend: Dict[str, Any], url: str) -> None:
interval = config.getfloat("verifier", "retry_interval")
exponential_backoff = config.getboolean("verifier", "exponential_backoff")
- with requests.Session() as session:
- logger.info("Sending revocation event via webhook...")
- for i in range(config.getint("verifier", "max_retries")):
- next_retry = retry.retry_time(exponential_backoff, interval, i, logger)
+
+ max_retries = config.getint("verifier", "max_retries")
+ if max_retries <= 0:
+ logger.info("Invalid value found in 'max_retries' option for verifier, using default value")
+ max_retries = 5
+
+ # Get TLS options from the configuration
+ (cert, key, trusted_ca, key_password), verify_server_cert = web_util.get_tls_options(
+ "verifier", is_client=True, logger=logger
+ )
+
+ # Generate the TLS context using the obtained options
+ tls_context = web_util.generate_tls_context(cert, key, trusted_ca, key_password, is_client=True, logger=logger)
+
+ logger.info("Sending revocation event via webhook to %s ...", url)
+ for i in range(max_retries):
+ next_retry = retry.retry_time(exponential_backoff, interval, i, logger)
+
+ with RequestsClient(
+ url,
+ verify_server_cert,
+ tls_context,
+ ) as client:
try:
- response = session.post(url, json=tosend, timeout=5)
- if response.status_code in [200, 202]:
- break
-
- logger.debug(
- "Unable to publish revocation message %d times via webhook, "
- "trying again in %d seconds. "
- "Server returned status code: %s",
- i,
- next_retry,
- response.status_code,
- )
- except requests.exceptions.RequestException as e:
- logger.debug(
- "Unable to publish revocation message %d times via webhook, trying again in %d seconds: %s",
- i,
- next_retry,
- e,
- )
+ res = client.post("", json=tosend, timeout=5)
+ except requests.exceptions.SSLError as ssl_error:
+ if "TLSV1_ALERT_UNKNOWN_CA" in str(ssl_error):
+ logger.warning(
+ "Keylime does not recognize certificate from peer. Check if verifier 'trusted_server_ca' is configured correctly"
+ )
+
+ raise ssl_error from ssl_error
+
+ if res and res.status_code in [200, 202]:
+ break
+
+ logger.debug(
+ "Unable to publish revocation message %d times via webhook, "
+ "trying again in %d seconds. "
+ "Server returned status code: %s",
+ i + 1,
+ next_retry,
+ res.status_code,
+ )
time.sleep(next_retry)
@@ -167,7 +191,11 @@ def notify_webhook(tosend: Dict[str, Any]) -> None:
cert_key = None
-def process_revocation(revocation: Dict[str, Any], callback: Callable[[Dict[str, Any]], None], cert_path: str) -> None:
+def process_revocation(
+ revocation: Dict[str, Any],
+ callback: Callable[[Dict[str, Any]], None],
+ cert_path: str,
+) -> None:
global cert_key
if cert_key is None:
@@ -179,10 +207,17 @@ def process_revocation(revocation: Dict[str, Any], callback: Callable[[Dict[str,
cert_key = crypto.x509_import_pubkey(certpem)
if cert_key is None:
- logger.warning("Unable to check signature of revocation message: %s not available", cert_path)
+ logger.warning(
+ "Unable to check signature of revocation message: %s not available",
+ cert_path,
+ )
elif "signature" not in revocation or revocation["signature"] == "none":
logger.warning("No signature on revocation message from server")
- elif not crypto.rsa_verify(cert_key, revocation["msg"].encode("utf-8"), revocation["signature"].encode("utf-8")):
+ elif not crypto.rsa_verify(
+ cert_key,
+ revocation["msg"].encode("utf-8"),
+ revocation["signature"].encode("utf-8"),
+ ):
logger.error("Invalid revocation message siganture %s", revocation)
else:
message = json.loads(revocation["msg"])
--
2.47.1

View File

@ -9,7 +9,7 @@
Name: keylime
Version: 7.3.0
Release: 14%{?dist}
Release: 15%{?dist}
Summary: Open source TPM software for Bootstrapping and Maintaining Trust
URL: https://github.com/keylime/keylime
@ -32,6 +32,7 @@ Patch: 0012-Restore-create-allowlist.patch
Patch: 0013-Set-generator-and-timestamp-in-create-policy.patch
Patch: 0014-tpm_util-Replace-a-logger.error-with-an-Exception-in.patch
Patch: 0015-Backport-keylime-policy-tool.patch
Patch: 0016-Use-TLS-on-revocation-notification-webhook.patch
License: ASL 2.0 and MIT
@ -374,6 +375,16 @@ fi
%license LICENSE
%changelog
* Wed Feb 05 2025 Sergio Correia <scorreia@redhat.com> - 7.3.0-15
- Use TLS on revocation notification webhook
- Include system installed CA certificates when verifying webhook
server certificate
- Include the CA certificates added via configuration file option
'trusted_server_ca'
Resolves: RHEL-78057
Resolves: RHEL-78313
Resolves: RHEL-78316
* Fri Jan 10 2025 Sergio Correia <scorreia@redhat.com> - 7.3.0-14
- Backport keylime-policy tool
Resolves: RHEL-75797