Add keylime to RHEL-9

Resolves: rhbz#2082989
This commit is contained in:
Sergio Correia 2022-06-21 07:21:18 -03:00
parent 9c72dfea97
commit b19c921a82
10 changed files with 1031 additions and 0 deletions

1
.gitignore vendored
View File

@ -0,0 +1 @@
/v6.4.1.tar.gz

View File

@ -0,0 +1,39 @@
From 90811cc0df4f32fbf9e5389cca15813e2f6395cb Mon Sep 17 00:00:00 2001
From: Sergio Correia <scorreia@redhat.com>
Date: Fri, 3 Jun 2022 22:01:15 -0300
Subject: [PATCH 1/5] Improve error handling when doing signature verification
This makes verify_signature_from_file() more consistent in that it will
always raise an exception informing the signature verification failed,
when this situation happens.
As it is, verify_signature() can raise a few different exceptions, and
those were not handled by verify_signature_from_file().
Signed-off-by: Sergio Correia <scorreia@redhat.com>
---
keylime/signing.py | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/keylime/signing.py b/keylime/signing.py
index 71f8be0..1353c1e 100644
--- a/keylime/signing.py
+++ b/keylime/signing.py
@@ -30,7 +30,13 @@ def verify_signature_from_file(key_file, filename, sig_file, file_description):
with open(filename, "rb") as file_f:
file = file_f.read()
- if verify_signature(key, sig, file):
+ verified = False
+ try:
+ verified = verify_signature(key, sig, file)
+ except Exception as e:
+ logger.warning("Unable to verify signature: %s", e)
+
+ if verified:
logger.debug("%s passed signature verification", file_description.capitalize())
else:
raise Exception(
--
2.35.1

View File

@ -0,0 +1,88 @@
From 09db3fe88b22c0e1522343c14f184ea610883fcf Mon Sep 17 00:00:00 2001
From: Daiki Ueno <dueno@redhat.com>
Date: Mon, 21 Mar 2022 11:06:45 +0100
Subject: [PATCH 2/5] revocation_notifier: Factor out revocation message
processing
This moves the revocation message processing logic out of
await_notifications, so it can be called directly from the POST
handler.
Signed-off-by: Daiki Ueno <dueno@redhat.com>
---
keylime/revocation_notifier.py | 45 ++++++++++++++++++----------------
1 file changed, 24 insertions(+), 21 deletions(-)
diff --git a/keylime/revocation_notifier.py b/keylime/revocation_notifier.py
index 4854a99..7cfe0e5 100644
--- a/keylime/revocation_notifier.py
+++ b/keylime/revocation_notifier.py
@@ -165,6 +165,29 @@ def notify_webhook(tosend):
cert_key = None
+def process_revocation(revocation, callback, cert_path):
+ global cert_key
+
+ if cert_key is None:
+ # load up the CV signing public key
+ if cert_path is not None and os.path.exists(cert_path):
+ logger.info("Lazy loading the revocation certificate from %s", cert_path)
+ with open(cert_path, "rb") as f:
+ certpem = f.read()
+ 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)
+ 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")):
+ logger.error("Invalid revocation message siganture %s", revocation)
+ else:
+ message = json.loads(revocation["msg"])
+ logger.debug("Revocation signature validated for revocation: %s", message)
+ callback(message)
+
+
def await_notifications(callback, revocation_cert_path):
# keep old typo "listen_notfications" around for a few versions
assert config.getboolean("cloud_agent", "listen_notifications", fallback=False) or config.getboolean(
@@ -175,8 +198,6 @@ def await_notifications(callback, revocation_cert_path):
except ImportError as error:
raise Exception("install PyZMQ for 'listen_notifications' option") from error
- global cert_key
-
if revocation_cert_path is None:
raise Exception("must specify revocation_cert_path")
@@ -197,25 +218,7 @@ def await_notifications(callback, revocation_cert_path):
while True:
rawbody = mysock.recv()
body = json.loads(rawbody)
-
- if cert_key is None:
- # load up the CV signing public key
- if revocation_cert_path is not None and os.path.exists(revocation_cert_path):
- logger.info("Lazy loading the revocation certificate from %s", revocation_cert_path)
- with open(revocation_cert_path, "rb") as f:
- certpem = f.read()
- cert_key = crypto.x509_import_pubkey(certpem)
-
- if cert_key is None:
- logger.warning("Unable to check signature of revocation message: %s not available", revocation_cert_path)
- elif "signature" not in body or body["signature"] == "none":
- logger.warning("No signature on revocation message from server")
- elif not crypto.rsa_verify(cert_key, body["msg"].encode("utf-8"), body["signature"].encode("utf-8")):
- logger.error("Invalid revocation message siganture %s", body)
- else:
- message = json.loads(body["msg"])
- logger.debug("Revocation signature validated for revocation: %s", message)
- callback(message)
+ process_revocation(body, callback, revocation_cert_path)
def main():
--
2.35.1

View File

@ -0,0 +1,175 @@
From 3defa4f6c399a4965dbea8b8188992bb470c2215 Mon Sep 17 00:00:00 2001
From: Daiki Ueno <dueno@redhat.com>
Date: Mon, 21 Mar 2022 11:46:08 +0100
Subject: [PATCH 3/5] keylime_agent: Support /notifications/revocation REST
method
Signed-off-by: Daiki Ueno <dueno@redhat.com>
---
keylime/keylime_agent.py | 109 ++++++++++++++++++++++++---------------
1 file changed, 68 insertions(+), 41 deletions(-)
diff --git a/keylime/keylime_agent.py b/keylime/keylime_agent.py
index f8ea0f5..9ee9797 100644
--- a/keylime/keylime_agent.py
+++ b/keylime/keylime_agent.py
@@ -210,17 +210,13 @@ class Handler(BaseHTTPRequestHandler):
rest_params = web_util.get_restful_params(self.path)
if rest_params is None:
- web_util.echo_json_response(self, 405, "Not Implemented: Use /keys/ interface")
+ web_util.echo_json_response(self, 405, "Not Implemented: Use /keys/ or /notifications/ interface")
return
if not rest_params["api_version"]:
web_util.echo_json_response(self, 400, "API Version not supported")
return
- if rest_params.get("keys", None) not in ["ukey", "vkey"]:
- web_util.echo_json_response(self, 400, "Only /keys/ukey or /keys/vkey are supported")
- return
-
content_length = int(self.headers.get("Content-Length", 0))
if content_length <= 0:
logger.warning("POST returning 400 response, expected content in message. url: %s", self.path)
@@ -230,6 +226,26 @@ class Handler(BaseHTTPRequestHandler):
post_body = self.rfile.read(content_length)
try:
json_body = json.loads(post_body)
+ except Exception as e:
+ logger.warning("POST returning 400 response, could not parse body data: %s", e)
+ web_util.echo_json_response(self, 400, "content is invalid")
+ return
+
+ if "notifications" in rest_params:
+ if rest_params["notifications"] == "revocation":
+ revocation_notifier.process_revocation(
+ json_body, perform_actions, cert_path=self.server.revocation_cert_path
+ )
+ web_util.echo_json_response(self, 200, "Success")
+ else:
+ web_util.echo_json_response(self, 400, "Only /notifications/revocation is supported")
+ return
+
+ if rest_params.get("keys", None) not in ["ukey", "vkey"]:
+ web_util.echo_json_response(self, 400, "Only /keys/ukey or /keys/vkey are supported")
+ return
+
+ try:
b64_encrypted_key = json_body["encrypted_key"]
decrypted_key = crypto.rsa_decrypt(self.server.rsaprivatekey, base64.b64decode(b64_encrypted_key))
except (ValueError, KeyError, TypeError) as e:
@@ -392,6 +408,7 @@ class CloudAgentHTTPServer(ThreadingMixIn, HTTPServer):
rsakey_path = None
mtls_cert_enabled = False
mtls_cert = None
+ revocation_cert_path = None
done = threading.Event()
auth_tag = None
payload = None
@@ -456,6 +473,13 @@ class CloudAgentHTTPServer(ThreadingMixIn, HTTPServer):
self.mtls_cert = None
logger.info("WARNING: mTLS disabled, Tenant and Verifier will reach out to agent via HTTP")
+ self.revocation_cert_path = config.get("cloud_agent", "revocation_cert")
+ if self.revocation_cert_path == "default":
+ self.revocation_cert_path = os.path.join(secdir, "unzipped/RevocationNotifier-cert.crt")
+ elif self.revocation_cert_path[0] != "/":
+ # if it is a relative, convert to absolute in work_dir
+ self.revocation_cert_path = os.path.abspath(os.path.join(config.WORK_DIR, self.revocation_cert_path))
+
# attempt to get a U value from the TPM NVRAM
nvram_u = tpm_instance.read_key_nvram()
if nvram_u is not None:
@@ -549,6 +573,45 @@ class CloudAgentHTTPServer(ThreadingMixIn, HTTPServer):
return False
+# Execute revocation action
+def perform_actions(revocation):
+ actionlist = []
+
+ # load the actions from inside the keylime module
+ actionlisttxt = config.get("cloud_agent", "revocation_actions")
+ if actionlisttxt.strip() != "":
+ actionlist = actionlisttxt.split(",")
+ actionlist = [f"revocation_actions.{i}" % i for i in actionlist]
+
+ # load actions from unzipped
+ secdir = secure_mount.mount()
+ action_list_path = os.path.join(secdir, "unzipped/action_list")
+ if os.path.exists(action_list_path):
+ with open(action_list_path, encoding="utf-8") as f:
+ actionlisttxt = f.read()
+ if actionlisttxt.strip() != "":
+ localactions = actionlisttxt.strip().split(",")
+ for action in localactions:
+ if not action.startswith("local_action_"):
+ logger.warning("Invalid local action: %s. Must start with local_action_", action)
+ else:
+ actionlist.append(action)
+
+ uzpath = os.path.join(secdir, "unzipped")
+ if uzpath not in sys.path:
+ sys.path.append(uzpath)
+
+ for action in actionlist:
+ logger.info("Executing revocation action %s", action)
+ try:
+ module = importlib.import_module(action)
+ execute = getattr(module, "execute")
+ loop = asyncio.new_event_loop()
+ loop.run_until_complete(execute(revocation))
+ except Exception as e:
+ logger.warning("Exception during execution of revocation action %s: %s", action, e)
+
+
def revocation_listener():
"""
This configures and starts the revocation listener. It is designed to be started in a separate process.
@@ -573,42 +636,6 @@ def revocation_listener():
# if it is a relative, convert to absolute in work_dir
cert_path = os.path.abspath(os.path.join(config.WORK_DIR, cert_path))
- # Callback function handling the revocations
- def perform_actions(revocation):
- actionlist = []
-
- # load the actions from inside the keylime module
- actionlisttxt = config.get("cloud_agent", "revocation_actions")
- if actionlisttxt.strip() != "":
- actionlist = actionlisttxt.split(",")
- actionlist = [f"revocation_actions.{i}" % i for i in actionlist]
-
- # load actions from unzipped
- action_list_path = os.path.join(secdir, "unzipped/action_list")
- if os.path.exists(action_list_path):
- with open(action_list_path, encoding="utf-8") as f:
- actionlisttxt = f.read()
- if actionlisttxt.strip() != "":
- localactions = actionlisttxt.strip().split(",")
- for action in localactions:
- if not action.startswith("local_action_"):
- logger.warning("Invalid local action: %s. Must start with local_action_", action)
- else:
- actionlist.append(action)
-
- uzpath = os.path.join(secdir, "unzipped")
- if uzpath not in sys.path:
- sys.path.append(uzpath)
-
- for action in actionlist:
- logger.info("Executing revocation action %s", action)
- try:
- module = importlib.import_module(action)
- execute = getattr(module, "execute")
- asyncio.get_event_loop().run_until_complete(execute(revocation))
- except Exception as e:
- logger.warning("Exception during execution of revocation action %s: %s", action, e)
-
try:
while True:
try:
--
2.35.1

View File

@ -0,0 +1,356 @@
From 16d3a31145e3d0001e6c6621adb6dbaf831cb03f Mon Sep 17 00:00:00 2001
From: Daiki Ueno <dueno@redhat.com>
Date: Mon, 21 Mar 2022 13:08:29 +0100
Subject: [PATCH 4/5] cloud_verifier: Support /notifications/revocation REST
API
Signed-off-by: Daiki Ueno <dueno@redhat.com>
---
keylime.conf | 22 ++++--
keylime/cloud_verifier_common.py | 16 +----
keylime/cloud_verifier_tornado.py | 108 +++++++++++++++++++++++-------
keylime/revocation_notifier.py | 18 ++++-
4 files changed, 119 insertions(+), 45 deletions(-)
diff --git a/keylime.conf b/keylime.conf
index fbd1119..c393e22 100644
--- a/keylime.conf
+++ b/keylime.conf
@@ -269,9 +269,21 @@ max_retries = 5
# will done as fast as possible. Floating point values accepted here.
quote_interval = 2
-# Whether to turn on the zero mq based revocation notifier system.
-# Currently this only works if you are using keylime-CA.
-revocation_notifier = True
+# Enable listed revocation notification methods.
+#
+# Available methods are:
+#
+# "zeromq": Enable the ZeroMQ based revocation notification method;
+# revocation_notifier_ip and revocation_notifier_port options must be
+# set. Currently this only works if you are using keylime-CA.
+#
+# "webhook": Send notification via webhook. The endpoint URL must be
+# configured with webhook_url option. This can be used to notify other
+# systems that do not have a Keylime agent running.
+#
+# "agent": Deliver notification directly to the agent via the REST
+# protocol.
+revocation_notifiers = zeromq
# The binding address and port of the revocation notifier service.
# If the 'revocation_notifier' option is set to "true", then the verifier
@@ -279,10 +291,6 @@ revocation_notifier = True
revocation_notifier_ip = 127.0.0.1
revocation_notifier_port = 8992
-# Enable revocation notifications via webhook. This can be used to notify other
-# systems that do not have a Keylime agent running.
-revocation_notifier_webhook = False
-
# Webhook url for revocation notifications.
webhook_url = ''
diff --git a/keylime/cloud_verifier_common.py b/keylime/cloud_verifier_common.py
index 52d6908..ab00768 100644
--- a/keylime/cloud_verifier_common.py
+++ b/keylime/cloud_verifier_common.py
@@ -7,7 +7,7 @@ import ast
import base64
import time
-from keylime import config, crypto, json, keylime_logging, revocation_notifier
+from keylime import config, crypto, json, keylime_logging
from keylime.agentstates import AgentAttestStates
from keylime.common import algorithms, validators
from keylime.failure import Component, Failure
@@ -268,14 +268,7 @@ def process_get_status(agent):
# sign a message with revocation key. telling of verification problem
-
-
-def notify_error(agent, msgtype="revocation", event=None):
- send_mq = config.getboolean("cloud_verifier", "revocation_notifier")
- send_webhook = config.getboolean("cloud_verifier", "revocation_notifier_webhook", fallback=False)
- if not (send_mq or send_webhook):
- return
-
+def prepare_error(agent, msgtype="revocation", event=None):
# prepare the revocation message:
revocation = {
"type": msgtype,
@@ -300,10 +293,7 @@ def notify_error(agent, msgtype="revocation", event=None):
else:
tosend["signature"] = "none"
- if send_mq:
- revocation_notifier.notify(tosend)
- if send_webhook:
- revocation_notifier.notify_webhook(tosend)
+ return tosend
def validate_agent_data(agent_data):
diff --git a/keylime/cloud_verifier_tornado.py b/keylime/cloud_verifier_tornado.py
index 60abf37..a8c08d2 100644
--- a/keylime/cloud_verifier_tornado.py
+++ b/keylime/cloud_verifier_tornado.py
@@ -9,6 +9,7 @@ import os
import signal
import sys
import traceback
+from concurrent.futures import ThreadPoolExecutor
from multiprocessing import Process
import tornado.ioloop
@@ -35,6 +36,9 @@ from keylime.failure import MAX_SEVERITY_LABEL, Component, Failure
logger = keylime_logging.init_logging("cloudverifier")
+# mTLS configuration to connect to the agent
+mtls_options = None
+
try:
engine = DBEngineManager().make_engine("cloud_verifier")
@@ -232,11 +236,6 @@ class VersionHandler(BaseHandler):
class AgentsHandler(BaseHandler):
- mtls_options = None # Stores the cert, key and password used by the verifier for mTLS connections
-
- def initialize(self, mtls_options):
- self.mtls_options = mtls_options
-
def head(self):
"""HEAD not supported"""
web_util.echo_json_response(self, 405, "HEAD not supported")
@@ -496,9 +495,7 @@ class AgentsHandler(BaseHandler):
mtls_cert = agent_data["mtls_cert"]
agent_data["ssl_context"] = None
if agent_mtls_cert_enabled and mtls_cert:
- agent_data["ssl_context"] = web_util.generate_agent_mtls_context(
- mtls_cert, self.mtls_options
- )
+ agent_data["ssl_context"] = web_util.generate_agent_mtls_context(mtls_cert, mtls_options)
if agent_data["ssl_context"] is None:
logger.warning("Connecting to agent without mTLS: %s", agent_id)
@@ -566,7 +563,7 @@ class AgentsHandler(BaseHandler):
if not isinstance(agent, dict):
agent = _from_db_obj(agent)
if agent["mtls_cert"]:
- agent["ssl_context"] = web_util.generate_agent_mtls_context(agent["mtls_cert"], self.mtls_options)
+ agent["ssl_context"] = web_util.generate_agent_mtls_context(agent["mtls_cert"], mtls_options)
agent["operational_state"] = states.START
asyncio.ensure_future(process_agent(agent, states.GET_QUOTE))
web_util.echo_json_response(self, 200, "Success")
@@ -860,6 +857,70 @@ async def invoke_provide_v(agent):
asyncio.ensure_future(process_agent(agent, states.GET_QUOTE))
+async def invoke_notify_error(agent, tosend):
+ if agent is None:
+ logger.warning("Agent deleted while being processed")
+ return
+ kwargs = {
+ "data": tosend,
+ }
+ if agent["ssl_context"]:
+ kwargs["context"] = agent["ssl_context"]
+ res = tornado_requests.request(
+ "POST",
+ f"http://{agent['ip']}:{agent['port']}/v{agent['supported_version']}/notifications/revocation",
+ **kwargs,
+ )
+ response = await res
+
+ if response is None:
+ logger.warning(
+ "Empty Notify Revocation response from cloud agent %s",
+ agent["agent_id"],
+ )
+ elif response.status_code != 200:
+ logger.warning(
+ "Unexpected Notify Revocation response error for cloud agent %s, Error: %s",
+ agent["agent_id"],
+ response.status_code,
+ )
+
+
+async def notify_error(agent, msgtype="revocation", event=None):
+ notifiers = revocation_notifier.get_notifiers()
+ if len(notifiers) == 0:
+ return
+
+ tosend = cloud_verifier_common.prepare_error(agent, msgtype, event)
+ if "webhook" in notifiers:
+ revocation_notifier.notify_webhook(tosend)
+ if "zeromq" in notifiers:
+ revocation_notifier.notify(tosend)
+ if "agent" in notifiers:
+ verifier_id = config.get(
+ "cloud_verifier", "cloudverifier_id", fallback=cloud_verifier_common.DEFAULT_VERIFIER_ID
+ )
+ session = get_session()
+ agents = session.query(VerfierMain).filter_by(verifier_id=verifier_id).all()
+ futures = []
+ loop = asyncio.get_event_loop()
+ # Notify all agents asynchronously through a thread pool
+ with ThreadPoolExecutor() as pool:
+ for agent_db_obj in agents:
+ if agent_db_obj.agent_id != agent["agent_id"]:
+ agent = _from_db_obj(agent_db_obj)
+ if agent["mtls_cert"]:
+ agent["ssl_context"] = web_util.generate_agent_mtls_context(agent["mtls_cert"], mtls_options)
+ func = functools.partial(invoke_notify_error, agent, tosend)
+ futures.append(await loop.run_in_executor(pool, func))
+ # Wait for all tasks complete in 60 seconds
+ try:
+ for f in asyncio.as_completed(futures, timeout=60):
+ await f
+ except asyncio.TimeoutError as e:
+ logger.error("Timeout during notifying error to agents: %s", e)
+
+
async def process_agent(agent, new_operational_state, failure=Failure(Component.INTERNAL, ["verifier"])):
# Convert to dict if the agent arg is a db object
if not isinstance(agent, dict):
@@ -900,7 +961,7 @@ async def process_agent(agent, new_operational_state, failure=Failure(Component.
# issue notification for invalid quotes
if new_operational_state == states.INVALID_QUOTE:
- cloud_verifier_common.notify_error(agent, event=failure.highest_severity_event)
+ await notify_error(agent, event=failure.highest_severity_event)
# When the failure is irrecoverable we stop polling the agent
if not failure.recoverable or failure.highest_severity == MAX_SEVERITY_LABEL:
@@ -975,9 +1036,7 @@ async def process_agent(agent, new_operational_state, failure=Failure(Component.
)
failure.add_event("not_reachable", "agent was not reachable from verifier", False)
if agent["first_verified"]: # only notify on previously good agents
- cloud_verifier_common.notify_error(
- agent, msgtype="comm_error", event=failure.highest_severity_event
- )
+ await notify_error(agent, msgtype="comm_error", event=failure.highest_severity_event)
else:
logger.debug("Communication error for new agent. No notification will be sent")
await process_agent(agent, states.FAILED, failure)
@@ -1004,7 +1063,7 @@ async def process_agent(agent, new_operational_state, failure=Failure(Component.
maxr,
)
failure.add_event("not_reachable_v", "agent was not reachable to provide V", False)
- cloud_verifier_common.notify_error(agent, msgtype="comm_error", event=failure.highest_severity_event)
+ await notify_error(agent, msgtype="comm_error", event=failure.highest_severity_event)
await process_agent(agent, states.FAILED, failure)
else:
agent["operational_state"] = states.PROVIDE_V
@@ -1031,7 +1090,7 @@ async def process_agent(agent, new_operational_state, failure=Failure(Component.
await process_agent(agent, states.FAILED, failure)
-async def activate_agents(verifier_id, verifier_ip, verifier_port, mtls_options):
+async def activate_agents(verifier_id, verifier_ip, verifier_port):
session = get_session()
aas = get_AgentAttestStates()
try:
@@ -1100,7 +1159,7 @@ def main():
# print out API versions we support
keylime_api_version.log_api_versions(logger)
- context, mtls_options = web_util.init_mtls(logger=logger)
+ context, server_mtls_options = web_util.init_mtls(logger=logger)
# Check for user defined CA to connect to agent
agent_mtls_cert = config.get("cloud_verifier", "agent_mtls_cert", fallback=None)
@@ -1108,12 +1167,15 @@ def main():
agent_mtls_private_key_pw = config.get("cloud_verifier", "agent_mtls_private_key_pw", fallback=None)
# Only set custom options if the cert should not be the same as used by the verifier
- if agent_mtls_cert != "CV":
+ global mtls_options
+ if agent_mtls_cert == "CV":
+ mtls_options = server_mtls_options
+ else:
mtls_options = (agent_mtls_cert, agent_mtls_private_key, agent_mtls_private_key_pw)
app = tornado.web.Application(
[
- (r"/v?[0-9]+(?:\.[0-9]+)?/agents/.*", AgentsHandler, {"mtls_options": mtls_options}),
+ (r"/v?[0-9]+(?:\.[0-9]+)?/agents/.*", AgentsHandler),
(r"/v?[0-9]+(?:\.[0-9]+)?/allowlists/.*", AllowlistHandler),
(r"/versions?", VersionHandler),
(r".*", MainHandler),
@@ -1149,17 +1211,17 @@ def main():
server.start()
if task_id == 0:
# Reactivate agents
- asyncio.ensure_future(
- activate_agents(cloudverifier_id, cloudverifier_host, cloudverifier_port, mtls_options)
- )
+ asyncio.ensure_future(activate_agents(cloudverifier_id, cloudverifier_host, cloudverifier_port))
tornado.ioloop.IOLoop.current().start()
logger.debug("Server %s stopped.", task_id)
sys.exit(0)
processes = []
+ run_revocation_notifier = "zeromq" in revocation_notifier.get_notifiers()
+
def sig_handler(*_):
- if config.getboolean("cloud_verifier", "revocation_notifier"):
+ if run_revocation_notifier:
revocation_notifier.stop_broker()
for p in processes:
p.join()
@@ -1167,7 +1229,7 @@ def main():
signal.signal(signal.SIGINT, sig_handler)
signal.signal(signal.SIGTERM, sig_handler)
- if config.getboolean("cloud_verifier", "revocation_notifier"):
+ if run_revocation_notifier:
logger.info(
"Starting service for revocation notifications on port %s",
config.getint("cloud_verifier", "revocation_notifier_port"),
diff --git a/keylime/revocation_notifier.py b/keylime/revocation_notifier.py
index 7cfe0e5..0628a64 100644
--- a/keylime/revocation_notifier.py
+++ b/keylime/revocation_notifier.py
@@ -22,8 +22,22 @@ broker_proc: Optional[Process] = None
_SOCKET_PATH = "/var/run/keylime/keylime.verifier.ipc"
+# return the revocation notification methods for cloud verifier
+def get_notifiers():
+ notifiers = set(config.get("cloud_verifier", "revocation_notifiers", fallback="").split(","))
+ if ("zeromq" not in notifiers) and config.getboolean("cloud_verifier", "revocation_notifier", fallback=False):
+ logger.warning("Warning: 'revocation_notifier' option is deprecated; use 'revocation_notifiers'")
+ notifiers.add("zeromq")
+ if ("webhook" not in notifiers) and config.getboolean(
+ "cloud_verifier", "revocation_notifier_webhook", fallback=False
+ ):
+ logger.warning("Warning: 'revocation_notifier_webhook' option is deprecated; use 'revocation_notifiers'")
+ notifiers.add("webhook")
+ return notifiers.intersection({"zeromq", "webhook", "agent"})
+
+
def start_broker():
- assert config.getboolean("cloud_verifier", "revocation_notifier")
+ assert "zeromq" in get_notifiers()
try:
import zmq # pylint: disable=import-outside-toplevel
except ImportError as error:
@@ -78,7 +92,7 @@ def stop_broker():
def notify(tosend):
- assert config.getboolean("cloud_verifier", "revocation_notifier")
+ assert "zeromq" in get_notifiers()
try:
import zmq # pylint: disable=import-outside-toplevel
except ImportError as error:
--
2.35.1

View File

@ -0,0 +1,117 @@
From 49bc0a3afbbe3740bb857b530440364b021a865f Mon Sep 17 00:00:00 2001
From: Sergio Correia <scorreia@redhat.com>
Date: Fri, 17 Jun 2022 19:57:17 -0300
Subject: [PATCH 5/5] Use python3-gpg instead of python3-gnupg
The former uses GPGME and is the recommended way of using GnuPG from
applications, by the GnuPG initiative, as it provides a better
documented API [1][2].
python-gpg is also already present in some distros, e.g. Fedora and
CentOS Stream, as it is a dependency of their package manager (dnf).
It is also available in other major distros such as Debian, Ubuntu,
OpenSUSE, so no disruptions are to be expected regarding packaging.
[1] https://gnupg.org/software/gpgme/index.html
[2] https://wiki.python.org/moin/GnuPrivacyGuard
---
installer.sh | 6 +++---
keylime/signing.py | 27 +++++++++++++--------------
requirements.txt | 3 +--
3 files changed, 17 insertions(+), 19 deletions(-)
diff --git a/installer.sh b/installer.sh
index 6618e1d..4355b33 100755
--- a/installer.sh
+++ b/installer.sh
@@ -58,7 +58,7 @@ case "$ID" in
echo "${ID} selected."
PACKAGE_MGR=$(command -v apt-get)
PYTHON_PREIN="git patch"
- PYTHON_DEPS="python3 python3-pip python3-dev python3-setuptools python3-zmq python3-tornado python3-cryptography python3-requests python3-psutil gcc g++ libssl-dev swig python3-yaml python3-gnupg python3-lark wget"
+ PYTHON_DEPS="python3 python3-pip python3-dev python3-setuptools python3-zmq python3-tornado python3-cryptography python3-requests python3-psutil gcc g++ libssl-dev swig python3-yaml python3-gpg python3-lark wget"
if [ "$(uname -m)" = "x86_64" ]; then
PYTHON_DEPS+=" libefivar-dev"
fi
@@ -96,7 +96,7 @@ case "$ID" in
PACKAGE_MGR=$(command -v dnf)
NEED_EPEL=1
PYTHON_PREIN="python3 python3-devel python3-setuptools python3-pip"
- PYTHON_DEPS="gcc gcc-c++ openssl-devel python3-yaml python3-requests swig python3-cryptography wget git python3-tornado python3-zmq python3-gnupg python3-psutil"
+ PYTHON_DEPS="gcc gcc-c++ openssl-devel python3-yaml python3-requests swig python3-cryptography wget git python3-tornado python3-zmq python3-gpg python3-psutil"
if [ "$(uname -m)" = "x86_64" ]; then
PYTHON_DEPS+=" efivar-libs"
fi
@@ -116,7 +116,7 @@ case "$ID" in
echo "${ID} selected."
PACKAGE_MGR=$(command -v dnf)
PYTHON_PREIN="python3 python3-devel python3-setuptools git wget patch"
- PYTHON_DEPS="python3-pip gcc gcc-c++ openssl-devel swig python3-pyyaml python3-zmq python3-cryptography python3-tornado python3-requests python3-gnupg yaml-cpp-devel procps-ng python3-psutil python3-lark-parser"
+ PYTHON_DEPS="python3-pip gcc gcc-c++ openssl-devel swig python3-pyyaml python3-zmq python3-cryptography python3-tornado python3-requests python3-gpg yaml-cpp-devel procps-ng python3-psutil python3-lark-parser"
if [ "$(uname -m)" = "x86_64" ]; then
PYTHON_DEPS+=" efivar-devel"
fi
diff --git a/keylime/signing.py b/keylime/signing.py
index 1353c1e..a1be9c7 100644
--- a/keylime/signing.py
+++ b/keylime/signing.py
@@ -5,7 +5,7 @@ Copyright 2017 Massachusetts Institute of Technology.
import tempfile
-import gnupg
+import gpg
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
@@ -55,19 +55,18 @@ def verify_signature(key, sig, file):
# PGP
if key_header == "-----BEGIN PGP PUBLIC KEY BLOCK-----":
- gpg = gnupg.GPG()
- logger.debug("Importing GPG key")
- gpg_imported = gpg.import_keys(key.decode("utf-8"))
- if gpg_imported.count == 1: # pylint: disable=E1101
- logger.debug("GPG key successfully imported")
- else:
- raise Exception("Unable to import GPG key")
-
- # The Python PGP library won't let you read a signature from memory, hence this hack.
- with tempfile.NamedTemporaryFile() as temp_sig:
- temp_sig.write(sig)
- temp_sig.flush()
- verified = gpg.verify_data(temp_sig.name, file)
+ verified = False
+ with tempfile.TemporaryDirectory() as gpg_homedir:
+ ctx = gpg.Context(home_dir=gpg_homedir)
+ try:
+ logger.debug("Importing GPG key")
+ result = ctx.key_import(key)
+ except Exception as e:
+ raise Exception("Unable to import GPG key") from e
+
+ if result is not None and hasattr(result, "considered") is True:
+ _, result = ctx.verify(file, sig)
+ verified = result.signatures[0].status == 0
# OpenSSL
elif key_header == "-----BEGIN PUBLIC KEY-----":
diff --git a/requirements.txt b/requirements.txt
index d31eabc..ca3fac3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,8 +10,7 @@ pyyaml>=3.11 # MIT
requests>=2.6 # Apache-2.0
sqlalchemy>=1.3 # MIT
alembic>=1.1.0 # MIT
-python-gnupg>=0.4.6 # BSD
packaging>=20.0 #BSD
psutil>=5.4.2 # BSD
# Note that lark was renamed from lark-parser with 1.0.0 release
-lark>=1.0.0 # MIT
\ No newline at end of file
+lark>=1.0.0 # MIT
--
2.35.1

8
gating.yaml Normal file
View File

@ -0,0 +1,8 @@
--- !Policy
product_versions:
- rhel-9
decision_context: osci_compose_gate
rules:
- !PassingTestCaseRule {test_case_name: baseos-ci.brew-build.openstack-swtmp.functional}
- !PassingTestCaseRule {test_case_name: baseos-ci.brew-build.beaker-tpm-ima.functional}
- !PassingTestCaseRule {test_case_name: baseos-ci.brew-build.beaker-beaker-swtpm-multihost.functional}

244
keylime.spec Normal file
View File

@ -0,0 +1,244 @@
%global srcname keylime
Name: keylime
Version: 6.4.1
Release: %{?autorelease}%{!?autorelease:1%{?dist}}
Summary: Open source TPM software for Bootstrapping and Maintaining Trust
BuildArch: noarch
URL: https://github.com/keylime/keylime
Source0: https://github.com/keylime/keylime/archive/refs/tags/v%{version}.tar.gz
Source1: %{srcname}.sysusers
Patch: 0001-Improve-error-handling-when-doing-signature-verifica.patch
# Agent-Local revocation.
Patch: 0002-revocation_notifier-Factor-out-revocation-message-pr.patch
Patch: 0003-keylime_agent-Support-notifications-revocation-REST-.patch
Patch: 0004-cloud_verifier-Support-notifications-revocation-REST.patch
# python-gpg.
Patch: 0005-Use-python3-gpg-instead-of-python3-gnupg.patch
License: ASL 2.0 and MIT
BuildRequires: git-core
BuildRequires: swig
BuildRequires: openssl-devel
BuildRequires: python3-devel
BuildRequires: python3-dbus
BuildRequires: python3-setuptools
BuildRequires: systemd-rpm-macros
Requires: python3-%{srcname} = %{version}-%{release}
Requires: %{srcname}-base = %{version}-%{release}
Requires: %{srcname}-verifier = %{version}-%{release}
Requires: %{srcname}-registrar = %{version}-%{release}
Requires: %{srcname}-tenant = %{version}-%{release}
# Agent.
Requires: keylime-agent
Suggests: keylime-agent-rust
%{?python_enable_dependency_generator}
%description
Keylime is a TPM based highly scalable remote boot attestation
and runtime integrity measurement solution.
%package base
Summary: The base package contains the default configuration
License: MIT
Requires(pre): shadow-utils
Requires: efivar-libs
Requires: procps-ng
Requires: tpm2-tss
Requires: tpm2-tools
%description base
The base package contains the Keylime default configuration
%package -n python3-%{srcname}
Summary: The Python Keylime module
License: MIT
Requires: %{srcname}-base = %{version}-%{release}
%{?python_provide:%python_provide python3-%{srcname}}
Requires: python3-tornado
Requires: python3-sqlalchemy
Requires: python3-alembic
Requires: python3-cryptography
Requires: python3-pyyaml
Requires: python3-packaging
Requires: python3-requests
Requires: python3-gpg
Requires: python3-lark-parser
%description -n python3-%{srcname}
The python3-keylime module implements the functionality used
by Keylime components.
%package verifier
Summary: The Python Keylime Verifier component
License: MIT
Requires: %{srcname}-base = %{version}-%{release}
Requires: python3-%{srcname} = %{version}-%{release}
%description verifier
The Keylime Verifier continuously verifies the integrity state
of the machine that the agent is running on.
%package registrar
Summary: The Keylime Registrar component
License: MIT
Requires: %{srcname}-base = %{version}-%{release}
Requires: python3-%{srcname} = %{version}-%{release}
%description registrar
The Keylime Registrar is a database of all agents registered
with Keylime and hosts the public keys of the TPM vendors.
%package tenant
Summary: The Python Keylime Tenant
License: MIT
Requires: %{srcname}-base = %{version}-%{release}
Requires: python3-%{srcname} = %{version}-%{release}
%description tenant
The Keylime Tenant can be used to provision a Keylime Agent.
%prep
%autosetup -S git -n %{srcname}-%{version}
%build
%py3_build
%install
%py3_install
mkdir -p %{buildroot}/%{_sharedstatedir}/%{srcname}
mkdir -p --mode=0700 %{buildroot}/%{_rundir}/%{srcname}
mkdir -p --mode=0700 %{buildroot}/%{_localstatedir}/log/%{srcname}
# Remove agent and webapp.
rm -f %{buildroot}/%{_bindir}/%{srcname}_agent
rm -f %{buildroot}%{python3_sitelib}/%{srcname}/__pycache__/%{srcname}_agent*
rm -f %{buildroot}%{python3_sitelib}/%{srcname}/cmd/__pycache__/agent.*
rm -f %{buildroot}%{python3_sitelib}/%{srcname}/cmd/agent.*
rm -f %{buildroot}%{python3_sitelib}/%{srcname}/%{srcname}_agent.*
rm -f %{buildroot}/%{_bindir}/%{srcname}_webapp
rm -f %{buildroot}%{python3_sitelib}/%{srcname}/__pycache__/tenant_webapp.*
rm -f %{buildroot}%{python3_sitelib}/%{srcname}/cmd/__pycache__/webapp.*
rm -f %{buildroot}%{python3_sitelib}/%{srcname}/cmd/webapp.*
rm -f %{buildroot}%{python3_sitelib}/%{srcname}/tenant_webapp.*
rm -rf %{buildroot}%{python3_sitelib}/%{srcname}/static/
# Remove misc progs.
rm -f %{buildroot}/%{_bindir}/%{srcname}_ima_emulator
rm -f %{buildroot}/%{_bindir}/%{srcname}_userdata_encrypt
# Disable zeromq revocation notifier, as there is no zeromq in RHEL.
# 6.4.1.
# Agent-Local revocation.
sed -e 's/^revocation_notifiers[[:space:]]*=.*/revocation_notifiers = agent/g' \
-i %{srcname}.conf
# Setting up the agent to use keylime:tss user/group after dropping privileges.
sed -e 's/^run_as[[:space:]]*=.*/run_as = keylime:tss/g' -i %{srcname}.conf
# Using sha256 for tpm_hash_alg.
sed -e 's/^tpm_hash_alg[[:space:]]*=.*/tpm_hash_alg = sha256/g' -i %{srcname}.conf
install -Dpm 600 %{srcname}.conf \
%{buildroot}%{_sysconfdir}/%{srcname}.conf
install -Dpm 644 ./services/%{srcname}_verifier.service \
%{buildroot}%{_unitdir}/%{srcname}_verifier.service
install -Dpm 644 ./services/%{srcname}_registrar.service \
%{buildroot}%{_unitdir}/%{srcname}_registrar.service
cp -r ./tpm_cert_store %{buildroot}%{_sharedstatedir}/keylime/
install -p -d %{buildroot}/%{_tmpfilesdir}
cat > %{buildroot}/%{_tmpfilesdir}/%{srcname}.conf << EOF
d %{_rundir}/%{srcname} 0700 %{srcname} %{srcname} -
EOF
install -p -D -m 0644 %{SOURCE1} %{buildroot}%{_sysusersdir}/%{srcname}.conf
%pre base
%sysusers_create_compat %{SOURCE1}
exit 0
%posttrans base
[ -f %{_sysconfdir}/%{srcname}.conf ] && \
chmod 600 %{_sysconfdir}/%{srcname}.conf && \
chown %{srcname} %{_sysconfdir}/%{srcname}.conf
[ -d %{_sharedstatedir}/%{srcname} ] && \
chown -R %{srcname} %{_sharedstatedir}/%{srcname}/
[ -d %{_localstatedir}/log/%{srcname} ] && \
chown -R %{srcname} %{_localstatedir}/log/%{srcname}/
exit 0
%post verifier
%systemd_post %{srcname}_verifier.service
%post registrar
%systemd_post %{srcname}_registrar.service
%preun verifier
%systemd_preun %{srcname}_verifier.service
%preun registrar
%systemd_preun %{srcname}_registrar.service
%postun verifier
%systemd_postun_with_restart %{srcname}_verifier.service
%postun registrar
%systemd_postun_with_restart %{srcname}_registrar.service
%files verifier
%license LICENSE
%{_bindir}/%{srcname}_verifier
%{_bindir}/%{srcname}_ca
%{_bindir}/%{srcname}_migrations_apply
%{_unitdir}/keylime_verifier.service
%files registrar
%license LICENSE
%{_bindir}/%{srcname}_registrar
%{_unitdir}/keylime_registrar.service
%files tenant
%license LICENSE
%{_bindir}/%{srcname}_tenant
%files -n python3-%{srcname}
%license LICENSE
%{python3_sitelib}/%{srcname}-*.egg-info/
%{python3_sitelib}/%{srcname}
%files base
%license LICENSE keylime/static/icons/ICON-LICENSE
%doc README.md
%config(noreplace) %attr(600,%{srcname},%{srcname}) %{_sysconfdir}/%{srcname}.conf
%attr(700,%{srcname},%{srcname}) %dir %{_rundir}/%{srcname}
%attr(700,%{srcname},%{srcname}) %dir %{_localstatedir}/log/%{srcname}
%attr(700,%{srcname},%{srcname}) %{_sharedstatedir}/%{srcname}
%{_tmpfilesdir}/%{srcname}.conf
%{_sysusersdir}/%{srcname}.conf
%files
%license LICENSE
%changelog
%autochangelog

2
keylime.sysusers Normal file
View File

@ -0,0 +1,2 @@
u keylime - "Keylime unprivileged user" /var/lib/keylime /usr/sbin/nologin
m keylime tss

1
sources Normal file
View File

@ -0,0 +1 @@
SHA512 (v6.4.1.tar.gz) = 1075eacb45f27df36e16e68b6486cfb32060c86ddbf0f40b28ab59ce4a76db183c65a8d76896fe49451b5b2ba84be1b39e758d42b943fd9ec66e659be2f1d89f