349 lines
14 KiB
Diff
349 lines
14 KiB
Diff
From a50c7e50171d8f5999bdd927b6306f6d14974c57 Mon Sep 17 00:00:00 2001
|
|
From: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
|
|
Date: Thu, 16 Apr 2026 14:14:06 +0200
|
|
Subject: [PATCH 1/2] shared_data: Move SyncManager socket to /var/run/keylime/
|
|
|
|
The SyncManager's server process creates a Unix domain socket for IPC
|
|
with worker processes. By default, this socket was placed in /tmp with
|
|
a random name (listener-*).
|
|
|
|
Move the socket to /var/run/keylime/, following standard daemon
|
|
practice. Keylime already uses this directory for its ZeroMQ revocation
|
|
notification socket.
|
|
|
|
Changes:
|
|
- Pass explicit address to SyncManager so the socket is created at
|
|
/var/run/keylime/shared_data.<pid>.sock instead of /tmp/listener-*
|
|
- Add _ensure_runtime_dir() to create or validate the directory
|
|
- Add test conftest.py to redirect sockets to a temp directory
|
|
- Add pytest to test-requirements.txt for pylint to resolve imports
|
|
|
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
|
|
---
|
|
keylime/shared_data.py | 54 +++++++++++++++++++++++++++++++++++++-----
|
|
test-requirements.txt | 1 +
|
|
test/conftest.py | 30 +++++++++++++++++++++++
|
|
3 files changed, 79 insertions(+), 6 deletions(-)
|
|
create mode 100644 test/conftest.py
|
|
|
|
diff --git a/keylime/shared_data.py b/keylime/shared_data.py
|
|
index 494f2f53b..aef39bcc4 100644
|
|
--- a/keylime/shared_data.py
|
|
+++ b/keylime/shared_data.py
|
|
@@ -18,6 +18,23 @@
|
|
|
|
logger = keylime_logging.init_logging("shared_data")
|
|
|
|
+_RUNTIME_DIR = "/var/run/keylime"
|
|
+
|
|
+
|
|
+def _ensure_runtime_dir() -> None:
|
|
+ """Ensure the runtime directory exists with correct permissions.
|
|
+
|
|
+ Under systemd, ``tmpfiles.d`` creates ``/var/run/keylime/`` at boot.
|
|
+ This function provides a fallback for non-systemd execution and
|
|
+ validates permissions in either case.
|
|
+ """
|
|
+ os.makedirs(_RUNTIME_DIR, mode=0o700, exist_ok=True)
|
|
+ perms = os.stat(_RUNTIME_DIR).st_mode & 0o777
|
|
+ if perms != 0o700 or not os.access(_RUNTIME_DIR, os.W_OK | os.X_OK):
|
|
+ msg = f"{_RUNTIME_DIR} is not usable by the current process"
|
|
+ logger.error(msg)
|
|
+ raise PermissionError(msg)
|
|
+
|
|
|
|
def _manager_ignore_signals() -> None:
|
|
"""Ignore SIGTERM and SIGINT in the Manager's server process.
|
|
@@ -137,8 +154,20 @@ def __init__(self) -> None:
|
|
"""
|
|
logger.debug("Initializing SharedDataManager")
|
|
|
|
- # Use explicit context to ensure fork compatibility
|
|
- # The Manager must be started BEFORE any fork() calls
|
|
+ # Ensure /var/run/keylime/ exists with correct permissions
|
|
+ # before forking the Manager server process.
|
|
+ _ensure_runtime_dir()
|
|
+ self._socket_path = os.path.join(_RUNTIME_DIR, f"shared_data.{os.getpid()}.sock")
|
|
+
|
|
+ # Remove stale socket from a previous run (e.g. after a crash).
|
|
+ # CPython's SocketListener does not pre-unlink before bind().
|
|
+ try:
|
|
+ os.unlink(self._socket_path)
|
|
+ except (FileNotFoundError, PermissionError):
|
|
+ pass
|
|
+
|
|
+ # Use explicit context to ensure fork compatibility.
|
|
+ # The Manager must be started BEFORE any fork() calls.
|
|
ctx = mp.get_context("fork")
|
|
# Use SyncManager directly (instead of the ctx.Manager() shortcut)
|
|
# so we can pass an initializer that makes the Manager's server
|
|
@@ -150,7 +179,7 @@ def __init__(self) -> None:
|
|
# SIGKILL escalation.
|
|
# Cannot use 'with' context manager here: the Manager must outlive
|
|
# __init__ and persist for the lifetime of SharedDataManager.
|
|
- self._manager = SyncManager(ctx=ctx)
|
|
+ self._manager = SyncManager(address=self._socket_path, ctx=ctx)
|
|
self._manager.start( # pylint: disable=consider-using-with
|
|
initializer=_manager_ignore_signals,
|
|
)
|
|
@@ -162,8 +191,6 @@ def __init__(self) -> None:
|
|
self._lock = self._manager.Lock()
|
|
self._initialized_at = time.time()
|
|
|
|
- # Register handler to reinitialize manager connection after fork
|
|
- # This is needed because Manager uses network connections that don't survive fork
|
|
try:
|
|
self._parent_pid = os.getpid()
|
|
logger.debug("SharedDataManager initialized in process %d", self._parent_pid)
|
|
@@ -173,7 +200,10 @@ def __init__(self) -> None:
|
|
# Ensure cleanup on exit
|
|
atexit.register(self.cleanup)
|
|
|
|
- logger.info("SharedDataManager initialized successfully")
|
|
+ logger.info(
|
|
+ "SharedDataManager initialized successfully (socket: %s)",
|
|
+ self._socket_path,
|
|
+ )
|
|
|
|
def set_data(self, key: str, value: Any) -> None:
|
|
"""Store arbitrary pickleable data by key.
|
|
@@ -333,6 +363,18 @@ def cleanup(self) -> None:
|
|
except Exception:
|
|
logger.exception("Error during SharedDataManager shutdown")
|
|
|
|
+ # Remove socket file if it still exists. The Manager server
|
|
+ # process normally unlinks it on exit, but if it was killed
|
|
+ # (SIGKILL) the file may be left behind.
|
|
+ socket_path = getattr(self, "_socket_path", None)
|
|
+ if socket_path:
|
|
+ try:
|
|
+ os.unlink(socket_path)
|
|
+ except FileNotFoundError:
|
|
+ pass
|
|
+ except OSError as e:
|
|
+ logger.debug("Could not remove socket file %s: %s", socket_path, e)
|
|
+
|
|
def deregister_child(self) -> None:
|
|
"""Remove the Manager's server process from multiprocessing's child tracking.
|
|
|
|
diff --git a/test-requirements.txt b/test-requirements.txt
|
|
index bdd44e3e9..bf74580a9 100644
|
|
--- a/test-requirements.txt
|
|
+++ b/test-requirements.txt
|
|
@@ -1,6 +1,7 @@
|
|
dbus-python
|
|
# modules required for pylint
|
|
setuptools
|
|
+pytest
|
|
# packages required for mypy
|
|
sqlalchemy-stubs
|
|
types-python-dateutil
|
|
diff --git a/test/conftest.py b/test/conftest.py
|
|
new file mode 100644
|
|
index 000000000..da2843922
|
|
--- /dev/null
|
|
+++ b/test/conftest.py
|
|
@@ -0,0 +1,30 @@
|
|
+"""Shared pytest fixtures for keylime tests."""
|
|
+
|
|
+import shutil
|
|
+import tempfile
|
|
+from unittest.mock import patch
|
|
+
|
|
+import pytest
|
|
+
|
|
+from keylime.shared_data import cleanup_global_shared_memory
|
|
+
|
|
+
|
|
+@pytest.fixture(autouse=True)
|
|
+def _shared_data_runtime_dir():
|
|
+ """Redirect SharedDataManager sockets to a temporary directory.
|
|
+
|
|
+ The SyncManager creates Unix domain sockets in /var/run/keylime/,
|
|
+ which may not be writable by the test user. This fixture patches
|
|
+ the runtime directory to a per-test temp directory so that tests
|
|
+ work in any environment.
|
|
+
|
|
+ After each test, any global SharedDataManager is shut down to
|
|
+ prevent stale managers from referencing deleted temp directories.
|
|
+ """
|
|
+ tmpdir = tempfile.mkdtemp()
|
|
+ with patch("keylime.shared_data._RUNTIME_DIR", tmpdir):
|
|
+ yield
|
|
+ # Shut down any global SharedDataManager left alive by the test
|
|
+ # so the next test starts fresh with a new temp directory.
|
|
+ cleanup_global_shared_memory()
|
|
+ shutil.rmtree(tmpdir, ignore_errors=True)
|
|
|
|
From 712ab6c841e258e463f858904bfc0991f704a3b9 Mon Sep 17 00:00:00 2001
|
|
From: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
|
|
Date: Thu, 16 Apr 2026 14:14:45 +0200
|
|
Subject: [PATCH 2/2] installer: Add tmpfiles.d config for all keylime
|
|
directories
|
|
|
|
Add keylime-tmpfiles.conf to manage all keylime directories.
|
|
|
|
This includes:
|
|
|
|
- /var/run/keylime (runtime IPC sockets)
|
|
- /var/lib/keylime (persistent state)
|
|
- /etc/keylime and config snippet directories (configuration)
|
|
- TPM certificate store copy from /usr/share to /var/lib
|
|
|
|
Simplify installer.sh to avoid redundant directory creation and
|
|
ownership setting. The installer only needs to install the tmpfiles.d
|
|
config to /usr/lib/tmpfiles.d/keylime.conf and apply it immediately with
|
|
systemd-tmpfiles --create so the directories exist before the services
|
|
start.
|
|
|
|
The installer validates the TPM cert store source exists before copying
|
|
and includes a non-systemd fallback for manual directory creation.
|
|
|
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
|
|
---
|
|
services/installer.sh | 61 ++++++++++++++++++++++++++--------
|
|
services/keylime-tmpfiles.conf | 40 ++++++++++++++++++++++
|
|
2 files changed, 87 insertions(+), 14 deletions(-)
|
|
create mode 100644 services/keylime-tmpfiles.conf
|
|
|
|
diff --git a/services/installer.sh b/services/installer.sh
|
|
index f34027c61..f462f136b 100755
|
|
--- a/services/installer.sh
|
|
+++ b/services/installer.sh
|
|
@@ -11,7 +11,7 @@ fi
|
|
BASEDIR=$(dirname "$0")
|
|
|
|
# check keylime scripts directory (same for verifier, agent, registrar)
|
|
-KEYLIMEDIR=$(dirname $(whereis keylime_verifier | cut -d " " -f 2))
|
|
+KEYLIMEDIR=$(dirname "$(whereis keylime_verifier | cut -d " " -f 2)")
|
|
if [[ $KEYLIMEDIR == "." ]]; then
|
|
echo "Unable to find keylime scripts" 1>&2
|
|
exit 1
|
|
@@ -20,8 +20,8 @@ fi
|
|
echo "Using keylime scripts directory: ${KEYLIMEDIR}"
|
|
|
|
# prepare keylime service files and store them in systemd path
|
|
-sed "s|KEYLIMEDIR|$KEYLIMEDIR|g" $BASEDIR/keylime_registrar.service.template > /etc/systemd/system/keylime_registrar.service
|
|
-sed "s|KEYLIMEDIR|$KEYLIMEDIR|g" $BASEDIR/keylime_verifier.service.template > /etc/systemd/system/keylime_verifier.service
|
|
+sed "s|KEYLIMEDIR|$KEYLIMEDIR|g" "$BASEDIR/keylime_registrar.service.template" > /etc/systemd/system/keylime_registrar.service
|
|
+sed "s|KEYLIMEDIR|$KEYLIMEDIR|g" "$BASEDIR/keylime_verifier.service.template" > /etc/systemd/system/keylime_verifier.service
|
|
|
|
echo "Creating keylime user if it not exists"
|
|
if ! getent passwd keylime >/dev/null; then
|
|
@@ -30,23 +30,56 @@ if ! getent passwd keylime >/dev/null; then
|
|
keylime
|
|
fi
|
|
|
|
-echo "Changing files to be owned by the keylime user"
|
|
-# Create all directories required if not there
|
|
-mkdir -p /var/lib/keylime
|
|
-mkdir -p /var/log/keylime
|
|
-mkdir -p /var/run/keylime
|
|
+# install TPM certificate store to /usr/share/keylime/
|
|
+# tmpfiles.d will copy this to /var/lib/keylime/tpm_cert_store
|
|
+TPM_CERT_STORE_SRC="$BASEDIR/../tpm_cert_store"
|
|
+if [[ ! -d "$TPM_CERT_STORE_SRC" ]]; then
|
|
+ echo "Missing TPM certificate store: $TPM_CERT_STORE_SRC" 1>&2
|
|
+ exit 1
|
|
+fi
|
|
+
|
|
+mkdir -p /usr/share/keylime
|
|
+cp -a "$TPM_CERT_STORE_SRC" /usr/share/keylime/ || exit 1
|
|
|
|
-chown keylime:keylime -R /etc/keylime
|
|
-chown keylime:keylime -R /var/lib/keylime
|
|
-chown keylime:keylime -R /var/log/keylime
|
|
-chown keylime:keylime -R /var/run/keylime
|
|
+# install tmpfiles.d config for keylime directories
|
|
+mkdir -p /usr/lib/tmpfiles.d
|
|
+cp "$BASEDIR/keylime-tmpfiles.conf" /usr/lib/tmpfiles.d/keylime.conf
|
|
+
|
|
+# apply the tmpfiles.d config immediately to create directories with correct ownership
|
|
+if command -v systemd-tmpfiles >/dev/null 2>&1; then
|
|
+ systemd-tmpfiles --create keylime.conf
|
|
+else
|
|
+ echo "Warning: systemd-tmpfiles not found, creating directories manually"
|
|
+ # Create essential directories as fallback for non-systemd systems
|
|
+ mkdir -p /var/run/keylime /var/lib/keylime \
|
|
+ /etc/keylime/ca.conf.d \
|
|
+ /etc/keylime/logging.conf.d \
|
|
+ /etc/keylime/verifier.conf.d \
|
|
+ /etc/keylime/registrar.conf.d \
|
|
+ /etc/keylime/tenant.conf.d \
|
|
+ /etc/keylime/agent.conf.d
|
|
+ chown keylime:keylime /var/run/keylime /var/lib/keylime
|
|
+ chmod 700 /var/run/keylime /var/lib/keylime
|
|
+ # Mirror tmpfiles.d Z/z semantics: recursively set ownership and
|
|
+ # file permissions under /etc/keylime, then fix directories to 0500.
|
|
+ chown -R keylime:keylime /etc/keylime
|
|
+ find /etc/keylime -type f -exec chmod 400 {} \;
|
|
+ find /etc/keylime -type d -exec chmod 500 {} \;
|
|
+ # Copy TPM cert store from /usr/share to /var/lib only if the
|
|
+ # target does not exist yet (mirrors the tmpfiles.d C directive).
|
|
+ # This preserves operator-added EK certificates.
|
|
+ if [ -d /usr/share/keylime/tpm_cert_store ] && [ ! -d /var/lib/keylime/tpm_cert_store ]; then
|
|
+ cp -r /usr/share/keylime/tpm_cert_store /var/lib/keylime/
|
|
+ chown -R keylime:keylime /var/lib/keylime/tpm_cert_store
|
|
+ find /var/lib/keylime/tpm_cert_store -type f -exec chmod 400 {} \;
|
|
+ chmod 500 /var/lib/keylime/tpm_cert_store
|
|
+ fi
|
|
+fi
|
|
|
|
# set permissions
|
|
chmod 664 /etc/systemd/system/keylime_registrar.service
|
|
chmod 664 /etc/systemd/system/keylime_verifier.service
|
|
|
|
-chmod 700 /var/run/keylime
|
|
-
|
|
# enable at startup
|
|
systemctl enable keylime_registrar.service
|
|
systemctl enable keylime_verifier.service
|
|
diff --git a/services/keylime-tmpfiles.conf b/services/keylime-tmpfiles.conf
|
|
new file mode 100644
|
|
index 000000000..f3c0b43d6
|
|
--- /dev/null
|
|
+++ b/services/keylime-tmpfiles.conf
|
|
@@ -0,0 +1,40 @@
|
|
+d /run/keylime 0700 keylime keylime -
|
|
+
|
|
+d /var/lib/keylime 0700 keylime keylime -
|
|
+
|
|
+d /etc/keylime 0500 keylime keylime -
|
|
+d /etc/keylime/ca.conf.d 0500 keylime keylime -
|
|
+d /etc/keylime/logging.conf.d 0500 keylime keylime -
|
|
+d /etc/keylime/verifier.conf.d 0500 keylime keylime -
|
|
+d /etc/keylime/registrar.conf.d 0500 keylime keylime -
|
|
+d /etc/keylime/tenant.conf.d 0500 keylime keylime -
|
|
+d /etc/keylime/agent.conf.d 0500 keylime keylime -
|
|
+
|
|
+# TPM certificate store.
|
|
+# Copy the cert store from /usr/share/keylime/tpm_cert_store
|
|
+# to /var/lib/keylime/tpm_cert_store.
|
|
+# Files inside /var/lib/keylime/tpm_cert_store/ have
|
|
+# 0400 permission and are owned by keylime/keylime,
|
|
+# while /var/lib/keylime/tpm_cert_store/ itself has
|
|
+# permission 0500, also owned by keylime/keylime.
|
|
+C /var/lib/keylime/tpm_cert_store 0500 keylime keylime - /usr/share/keylime/tpm_cert_store
|
|
+Z /var/lib/keylime/tpm_cert_store 0400 keylime keylime -
|
|
+z /var/lib/keylime/tpm_cert_store 0500 keylime keylime -
|
|
+# Finally, /var/lib/keylime itself has 0700 permission,
|
|
+# and is owned by keylime/keylime.
|
|
+z /var/lib/keylime 0700 keylime keylime -
|
|
+
|
|
+# Keylime configuration in /etc/keylime has permission 0400
|
|
+# owned by keylime/keylime, while snippet directories and
|
|
+# the actual /etc/keylime directory have permission 0500,
|
|
+# also owned by keylime/keylime.
|
|
+Z /etc/keylime 0400 keylime keylime -
|
|
+# Now fix the directories:
|
|
+z /etc/keylime/ca.conf.d 0500 keylime keylime -
|
|
+z /etc/keylime/logging.conf.d 0500 keylime keylime -
|
|
+z /etc/keylime/verifier.conf.d 0500 keylime keylime -
|
|
+z /etc/keylime/registrar.conf.d 0500 keylime keylime -
|
|
+z /etc/keylime/tenant.conf.d 0500 keylime keylime -
|
|
+z /etc/keylime/agent.conf.d 0500 keylime keylime -
|
|
+# And finally, /etc/keylime itself.
|
|
+z /etc/keylime 0500 keylime keylime -
|