From 86a18d1bb4b19e31a858e4a409f8b6c33bf346d2 Mon Sep 17 00:00:00 2001 From: Anderson Toshiyuki Sasaki Date: Mon, 28 Aug 2023 22:55:42 +0200 Subject: [PATCH] Automatically update agent API version Resolves: RHEL-1518 Signed-off-by: Anderson Toshiyuki Sasaki --- ...tomatically-update-agent-API-version.patch | 244 ++++++++++++++++++ keylime.spec | 7 +- 2 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 0011-Automatically-update-agent-API-version.patch diff --git a/0011-Automatically-update-agent-API-version.patch b/0011-Automatically-update-agent-API-version.patch new file mode 100644 index 0000000..c87b309 --- /dev/null +++ b/0011-Automatically-update-agent-API-version.patch @@ -0,0 +1,244 @@ +From b0cf69c9db20eb319ea2e90c22f500e09b704224 Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Wed, 23 Aug 2023 16:24:15 +0200 +Subject: [PATCH] Implement automatic agent API version bump + +Automatically update the agent supported API version in the database if +the agent is updated and its API version is bumped. + +Previously, if an agent was added to a verifier while it used an old API +version, and then it is updated with an API version bump, the +attestation would fail as the verifier would try to reach the agent +using the old API version. + +Fixes #1457 + +Signed-off-by: Anderson Toshiyuki Sasaki +--- + keylime/cloud_verifier_tornado.py | 185 +++++++++++++++++++++++++++--- + 1 file changed, 167 insertions(+), 18 deletions(-) + +diff --git a/keylime/cloud_verifier_tornado.py b/keylime/cloud_verifier_tornado.py +index 261022ac6..31e6f7159 100644 +--- a/keylime/cloud_verifier_tornado.py ++++ b/keylime/cloud_verifier_tornado.py +@@ -32,6 +32,7 @@ + ) + from keylime.agentstates import AgentAttestState, AgentAttestStates + from keylime.common import retry, states, validators ++from keylime.common.version import str_to_version + from keylime.da import record + from keylime.db.keylime_db import DBEngineManager, SessionManager + from keylime.db.verifier_db import VerfierMain, VerifierAllowlist +@@ -998,6 +999,80 @@ def data_received(self, chunk: Any) -> None: + raise NotImplementedError() + + ++async def update_agent_api_version(agent: Dict[str, Any], timeout: float = 60.0) -> Union[Dict[str, Any], None]: ++ agent_id = agent["agent_id"] ++ ++ logger.info("Agent %s API version bump detected, trying to update stored API version", agent_id) ++ kwargs = {} ++ if agent["ssl_context"]: ++ kwargs["context"] = agent["ssl_context"] ++ ++ res = tornado_requests.request( ++ "GET", ++ f"http://{agent['ip']}:{agent['port']}/version", ++ **kwargs, ++ timeout=timeout, ++ ) ++ response = await res ++ ++ if response.status_code != 200: ++ logger.warning( ++ "Could not get agent %s supported API version, Error: %s", ++ agent["agent_id"], ++ response.status_code, ++ ) ++ return None ++ ++ try: ++ json_response = json.loads(response.body) ++ new_version = json_response["results"]["supported_version"] ++ old_version = agent["supported_version"] ++ ++ # Only update the API version to use if it is supported by the verifier ++ if new_version in keylime_api_version.all_versions(): ++ new_version_tuple = str_to_version(new_version) ++ old_version_tuple = str_to_version(old_version) ++ ++ assert new_version_tuple, f"Agent {agent_id} version {new_version} is invalid" ++ assert old_version_tuple, f"Agent {agent_id} version {old_version} is invalid" ++ ++ # Check that the new version is greater than current version ++ if new_version_tuple <= old_version_tuple: ++ logger.warning( ++ "Agent %s API version %s is lower or equal to previous version %s", ++ agent_id, ++ new_version, ++ old_version, ++ ) ++ return None ++ ++ logger.info("Agent %s new API version %s is supported", agent_id, new_version) ++ session = get_session() ++ agent["supported_version"] = new_version ++ ++ # Remove keys that should not go to the DB ++ agent_db = dict(agent) ++ for key in exclude_db: ++ if key in agent_db: ++ del agent_db[key] ++ ++ session.query(VerfierMain).filter_by(agent_id=agent_id).update(agent_db) # pyright: ignore ++ session.commit() ++ else: ++ logger.warning("Agent %s new API version %s is not supported", agent_id, new_version) ++ return None ++ ++ except SQLAlchemyError as e: ++ logger.error("SQLAlchemy Error updating API version for agent %s: %s", agent_id, e) ++ return None ++ except Exception as e: ++ logger.exception(e) ++ return None ++ ++ logger.info("Agent %s API version updated to %s", agent["agent_id"], agent["supported_version"]) ++ return agent ++ ++ + async def invoke_get_quote( + agent: Dict[str, Any], runtime_policy: str, need_pubkey: bool, timeout: float = 60.0 + ) -> None: +@@ -1028,15 +1103,43 @@ async def invoke_get_quote( + # this is a connection error, retry get quote + if response.status_code in [408, 500, 599]: + asyncio.ensure_future(process_agent(agent, states.GET_QUOTE_RETRY)) +- else: +- # catastrophic error, do not continue +- logger.critical( +- "Unexpected Get Quote response error for cloud agent %s, Error: %s", +- agent["agent_id"], +- response.status_code, +- ) +- failure.add_event("no_quote", "Unexpected Get Quote reponse from agent", False) +- asyncio.ensure_future(process_agent(agent, states.FAILED, failure)) ++ return ++ ++ if response.status_code == 400: ++ try: ++ json_response = json.loads(response.body) ++ if "API version not supported" in json_response["status"]: ++ update = update_agent_api_version(agent) ++ updated = await update ++ ++ if updated: ++ asyncio.ensure_future(process_agent(updated, states.GET_QUOTE_RETRY)) ++ else: ++ logger.warning("Could not update stored agent %s API version", agent["agent_id"]) ++ failure.add_event( ++ "version_not_supported", ++ {"context": "Agent API version not supported", "data": json_response}, ++ False, ++ ) ++ asyncio.ensure_future(process_agent(agent, states.FAILED, failure)) ++ return ++ ++ except Exception as e: ++ logger.exception(e) ++ failure.add_event( ++ "exception", {"context": "Agent caused the verifier to throw an exception", "data": str(e)}, False ++ ) ++ asyncio.ensure_future(process_agent(agent, states.FAILED, failure)) ++ return ++ ++ # catastrophic error, do not continue ++ logger.critical( ++ "Unexpected Get Quote response error for cloud agent %s, Error: %s", ++ agent["agent_id"], ++ response.status_code, ++ ) ++ failure.add_event("no_quote", "Unexpected Get Quote reponse from agent", False) ++ asyncio.ensure_future(process_agent(agent, states.FAILED, failure)) + else: + try: + json_response = json.loads(response.body) +@@ -1100,15 +1203,43 @@ async def invoke_provide_v(agent: Dict[str, Any], timeout: float = 60.0) -> None + if response.status_code != 200: + if response.status_code in [408, 500, 599]: + asyncio.ensure_future(process_agent(agent, states.PROVIDE_V_RETRY)) +- else: +- # catastrophic error, do not continue +- logger.critical( +- "Unexpected Provide V response error for cloud agent %s, Error: %s", +- agent["agent_id"], +- response.status_code, +- ) +- failure.add_event("no_v", {"message": "Unexpected provide V response", "data": response.status_code}, False) +- asyncio.ensure_future(process_agent(agent, states.FAILED, failure)) ++ return ++ ++ if response.status_code == 400: ++ try: ++ json_response = json.loads(response.body) ++ if "API version not supported" in json_response["status"]: ++ update = update_agent_api_version(agent) ++ updated = await update ++ ++ if updated: ++ asyncio.ensure_future(process_agent(updated, states.PROVIDE_V_RETRY)) ++ else: ++ logger.warning("Could not update stored agent %s API version", agent["agent_id"]) ++ failure.add_event( ++ "version_not_supported", ++ {"context": "Agent API version not supported", "data": json_response}, ++ False, ++ ) ++ asyncio.ensure_future(process_agent(agent, states.FAILED, failure)) ++ return ++ ++ except Exception as e: ++ logger.exception(e) ++ failure.add_event( ++ "exception", {"context": "Agent caused the verifier to throw an exception", "data": str(e)}, False ++ ) ++ asyncio.ensure_future(process_agent(agent, states.FAILED, failure)) ++ return ++ ++ # catastrophic error, do not continue ++ logger.critical( ++ "Unexpected Provide V response error for cloud agent %s, Error: %s", ++ agent["agent_id"], ++ response.status_code, ++ ) ++ failure.add_event("no_v", {"message": "Unexpected provide V response", "data": response.status_code}, False) ++ asyncio.ensure_future(process_agent(agent, states.FAILED, failure)) + else: + asyncio.ensure_future(process_agent(agent, states.GET_QUOTE)) + +@@ -1134,6 +1265,24 @@ async def invoke_notify_error(agent: Dict[str, Any], tosend: Dict[str, Any], tim + agent["agent_id"], + ) + elif response.status_code != 200: ++ if response.status_code == 400: ++ try: ++ json_response = json.loads(response.body) ++ if "API version not supported" in json_response["status"]: ++ update = update_agent_api_version(agent) ++ updated = await update ++ ++ if updated: ++ asyncio.ensure_future(invoke_notify_error(updated, tosend)) ++ else: ++ logger.warning("Could not update stored agent %s API version", agent["agent_id"]) ++ ++ return ++ ++ except Exception as e: ++ logger.exception(e) ++ return ++ + logger.warning( + "Unexpected Notify Revocation response error for cloud agent %s, Error: %s", + agent["agent_id"], diff --git a/keylime.spec b/keylime.spec index 1d9553d..8f73414 100644 --- a/keylime.spec +++ b/keylime.spec @@ -9,7 +9,7 @@ Name: keylime Version: 7.3.0 -Release: 6%{?dist} +Release: 7%{?dist} Summary: Open source TPM software for Bootstrapping and Maintaining Trust URL: https://github.com/keylime/keylime @@ -27,6 +27,7 @@ Patch: 0007-Handle-session-close-using-a-session-manager.patch Patch: 0008-verifier-should-read-parameters-from-verifier.conf-o.patch Patch: 0009-CVE-2023-38201.patch Patch: 0010-CVE-2023-38200.patch +Patch: 0011-Automatically-update-agent-API-version.patch License: ASL 2.0 and MIT @@ -359,6 +360,10 @@ fi %license LICENSE %changelog +* Mon Aug 28 2023 Anderson Toshiyuki Sasaki - 7.3.0-7 +- Automatically update agent API version + Resolves: RHEL-1518 + * Mon Aug 28 2023 Sergio Correia - 7.3.0-6 - Fix registrar is subject to a DoS against SSL (CVE-2023-38200) Resolves: rhbz#2222694