async def invoke_provide_v(agent): if agent is None: raise Exception("Agent deleted while being processed") try: if agent['pending_event'] is not None: agent['pending_event'] = None except KeyError: pass v_json_message = cloud_verifier_common.prepare_v(agent) version = keylime_api_version.current_version() res = tornado_requests.request("POST", "http://%s:%d/%s/keys/vkey" % (agent['ip'], agent['port'], version), data=v_json_message) response = await res if response.status_code != 200: if response.status_code == 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.error) asyncio.ensure_future(process_agent(agent, states.FAILED)) else: asyncio.ensure_future(process_agent(agent, states.GET_QUOTE))
async def invoke_get_quote(agent, need_pubkey): if agent is None: raise Exception("agent deleted while being processed") params = cloud_verifier_common.prepare_get_quote(agent) partial_req = "1" if need_pubkey: partial_req = "0" version = keylime_api_version.current_version() res = tornado_requests.request( "GET", "http://%s:%d/v%s/quotes/integrity?nonce=%s&mask=%s&vmask=%s&partial=%s&ima_ml_entry=%d" % (agent['ip'], agent['port'], version, params["nonce"], params["mask"], params['vmask'], partial_req, params['ima_ml_entry']), context=None) response = await res if response.status_code != 200: # this is a connection error, retry get quote if response.status_code == 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) asyncio.ensure_future(process_agent(agent, states.FAILED)) else: try: json_response = json.loads(response.body) # validate the cloud agent response if 'provide_V' not in agent: agent['provide_V'] = True agentAttestState = get_AgentAttestStates().get_by_agent_id( agent['agent_id']) if cloud_verifier_common.process_quote_response( agent, json_response['results'], agentAttestState): if agent['provide_V']: asyncio.ensure_future( process_agent(agent, states.PROVIDE_V)) else: asyncio.ensure_future( process_agent(agent, states.GET_QUOTE)) else: asyncio.ensure_future( process_agent(agent, states.INVALID_QUOTE)) # store the attestation state store_attestation_state(agentAttestState) except Exception as e: logger.exception(e)
def test_060_cv_version_get(self): """Test CV's GET /version Interface""" cv_client = RequestsClient(tenant_templ.verifier_base_url, tls_enabled) response = cv_client.get("/version", cert=tenant_templ.cert, verify=False) self.assertEqual(response.status_code, 200, "Non-successful CV allowlist Post return code!") json_response = response.json() # Ensure response is well-formed self.assertIn("results", json_response, "Malformed response body!") results = json_response["results"] self.assertEqual(results["current_version"], api_version.current_version()) self.assertEqual(results["supported_versions"], api_version.all_versions())
def get(self): rest_params = config.get_restful_params(self.request.uri) if rest_params is None: config.echo_json_response(self, 405, "Not Implemented") return if "version" not in rest_params: config.echo_json_response(self, 400, "URI not supported") logger.warning('GET returning 400 response. URI not supported: %s', self.request.path) return version_info = { "current_version": keylime_api_version.current_version(), "supported_versions": keylime_api_version.all_versions(), } config.echo_json_response(self, 200, "Success", version_info)
def do_GET(self): """This method handles the GET requests to the unprotected side of the Registrar Server Currently the only supported path is /versions which shows the supported API versions """ rest_params = config.get_restful_params(self.path) if rest_params is None: config.echo_json_response( self, 405, "Not Implemented: Use /version/ interface") return if "version" not in rest_params: config.echo_json_response(self, 400, "URI not supported") logger.warning( 'GET agent returning 400 response. URI not supported: %s', self.path) return version_info = { "current_version": keylime_api_version.current_version(), "supported_versions": keylime_api_version.all_versions(), } config.echo_json_response(self, 200, "Success", version_info)
def test_current_version(self): self.assertEqual(api_version.current_version(), "1.0", "Current version is 1.0")
def main(): for ML in [config.MEASUREDBOOT_ML, config.IMA_ML]: if not os.access(ML, os.F_OK): logger.warning( 'Measurement list path %s not accessible by agent. Any attempt to instruct it to access this path - via "keylime_tenant" CLI - will result in agent process dying', ML, ) ima_log_file = None if os.path.exists(config.IMA_ML): ima_log_file = open(config.IMA_ML, "r", encoding="utf-8") # pylint: disable=consider-using-with tpm_log_file_data = None if os.path.exists(config.MEASUREDBOOT_ML): with open(config.MEASUREDBOOT_ML, "rb") as tpm_log_file: tpm_log_file_data = base64.b64encode(tpm_log_file.read()) if config.get("cloud_agent", "agent_uuid") == "dmidecode": if os.getuid() != 0: raise RuntimeError( "agent_uuid is configured to use dmidecode, but current process is not running as root." ) cmd = ["which", "dmidecode"] ret = cmd_exec.run(cmd, raiseOnError=False) if ret["code"] != 0: raise RuntimeError( "agent_uuid is configured to use dmidecode, but it's is not found on the system." ) # initialize the tmpfs partition to store keys if it isn't already available secdir = secure_mount.mount() # Now that operations requiring root privileges are done, drop privileges # if 'run_as' is available in the configuration. if os.getuid() == 0: run_as = config.get("cloud_agent", "run_as", fallback="") if run_as != "": user_utils.chown(secdir, run_as) user_utils.change_uidgid(run_as) logger.info("Dropped privileges to %s", run_as) else: logger.warning( "Cannot drop privileges since 'run_as' is empty or missing in keylime.conf agent section." ) # Instanitate TPM class instance_tpm = tpm() # get params for initialization registrar_ip = config.get("cloud_agent", "registrar_ip") registrar_port = config.get("cloud_agent", "registrar_port") # get params for the verifier to contact the agent contact_ip = os.getenv("KEYLIME_AGENT_CONTACT_IP", None) if contact_ip is None and config.has_option("cloud_agent", "agent_contact_ip"): contact_ip = config.get("cloud_agent", "agent_contact_ip") contact_port = os.getenv("KEYLIME_AGENT_CONTACT_PORT", None) if contact_port is None and config.has_option("cloud_agent", "agent_contact_port"): contact_port = config.get("cloud_agent", "agent_contact_port", fallback="invalid") # change dir to working dir fs_util.ch_dir(config.WORK_DIR) # set a conservative general umask os.umask(0o077) # initialize tpm (ekcert, ek_tpm, aik_tpm) = instance_tpm.tpm_init( self_activate=False, config_pw=config.get("cloud_agent", "tpm_ownerpassword") ) # this tells initialize not to self activate the AIK # Warn if kernel version is <5.10 and another algorithm than SHA1 is used, # because otherwise IMA will not work kernel_version = tuple(platform.release().split("-")[0].split(".")) if tuple(map(int, kernel_version)) < ( 5, 10, 0) and instance_tpm.defaults["hash"] != algorithms.Hash.SHA1: logger.warning( "IMA attestation only works on kernel versions <5.10 with SHA1 as hash algorithm. " 'Even if ascii_runtime_measurements shows "%s" as the ' "algorithm, it might be just padding zeros", (instance_tpm.defaults["hash"]), ) if ekcert is None and instance_tpm.is_emulator(): ekcert = "emulator" # now we need the UUID try: agent_uuid = config.get("cloud_agent", "agent_uuid") except configparser.NoOptionError: agent_uuid = None if agent_uuid == "hash_ek": ek_pubkey = pubkey_from_tpm2b_public(base64.b64decode(ek_tpm)) ek_pubkey_pem = ek_pubkey.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) agent_uuid = hashlib.sha256(ek_pubkey_pem).hexdigest() elif agent_uuid == "generate" or agent_uuid is None: agent_uuid = str(uuid.uuid4()) elif agent_uuid == "dmidecode": cmd = ["dmidecode", "-s", "system-uuid"] ret = cmd_exec.run(cmd) sys_uuid = ret["retout"][0].decode("utf-8") agent_uuid = sys_uuid.strip() try: uuid.UUID(agent_uuid) except ValueError as e: raise RuntimeError( # pylint: disable=raise-missing-from f"The UUID returned from dmidecode is invalid: {str(e)}") elif agent_uuid == "hostname": agent_uuid = socket.getfqdn() elif agent_uuid == "environment": agent_uuid = os.getenv("KEYLIME_AGENT_UUID", None) if agent_uuid is None: raise RuntimeError( "Env variable KEYLIME_AGENT_UUID is empty, but agent_uuid is set to 'environment'" ) elif not validators.valid_uuid(agent_uuid): raise RuntimeError("The UUID is not valid") if not validators.valid_agent_id(agent_uuid): raise RuntimeError( "The agent ID set via agent uuid parameter use invalid characters") logger.info("Agent UUID: %s", agent_uuid) serveraddr = (config.get("cloud_agent", "cloudagent_ip"), config.getint("cloud_agent", "cloudagent_port")) keylime_ca = config.get("cloud_agent", "keylime_ca") if keylime_ca == "default": keylime_ca = os.path.join(config.WORK_DIR, "cv_ca", "cacert.crt") server = CloudAgentHTTPServer(serveraddr, Handler, agent_uuid, contact_ip, ima_log_file, tpm_log_file_data) if server.mtls_cert_enabled: context = web_util.generate_mtls_context(server.mtls_cert_path, server.rsakey_path, keylime_ca, logger=logger) server.socket = context.wrap_socket(server.socket, server_side=True) else: if (not config.getboolean( "cloud_agent", "enable_insecure_payload", fallback=False) and config.get("cloud_agent", "payload_script") != ""): raise RuntimeError( "agent mTLS is disabled, while a tenant can instruct the agent to execute code on the node. " 'In order to allow the running of the agent, "enable_insecure_payload" has to be set to "True"' ) serverthread = threading.Thread(target=server.serve_forever, daemon=True) # register it and get back a blob mtls_cert = "disabled" if server.mtls_cert: mtls_cert = server.mtls_cert.public_bytes(serialization.Encoding.PEM) keyblob = registrar_client.doRegisterAgent(registrar_ip, registrar_port, agent_uuid, ek_tpm, ekcert, aik_tpm, mtls_cert, contact_ip, contact_port) if keyblob is None: instance_tpm.flush_keys() raise Exception("Registration failed") # get the ephemeral registrar key key = instance_tpm.activate_identity(keyblob) if key is None: instance_tpm.flush_keys() raise Exception("Activation failed") # tell the registrar server we know the key retval = registrar_client.doActivateAgent(registrar_ip, registrar_port, agent_uuid, key) if not retval: instance_tpm.flush_keys() raise Exception("Registration failed on activate") # Start revocation listener in a new process to not interfere with tornado revocation_process = multiprocessing.Process(target=revocation_listener, daemon=True) revocation_process.start() logger.info( "Starting Cloud Agent on %s:%s with API version %s. Use <Ctrl-C> to stop", serveraddr[0], serveraddr[1], keylime_api_version.current_version(), ) serverthread.start() def shutdown_handler(*_): logger.info("TERM Signal received, shutting down...") logger.debug("Stopping revocation notifier...") revocation_process.terminate() logger.debug("Shutting down HTTP server...") server.shutdown() server.server_close() serverthread.join() logger.debug("HTTP server stopped...") revocation_process.join() logger.debug("Revocation notifier stopped...") secure_mount.umount() logger.debug("Umounting directories...") instance_tpm.flush_keys() logger.debug("Flushed keys successfully") sys.exit(0) signal.signal(signal.SIGTERM, shutdown_handler) signal.signal(signal.SIGQUIT, shutdown_handler) signal.signal(signal.SIGINT, shutdown_handler) # Keep the main thread alive by waiting for the server thread serverthread.join()
def do_GET(self): """This method services the GET request typically from either the Tenant or the Cloud Verifier. Only tenant and cloudverifier uri's are supported. Both requests require a nonce parameter. The Cloud verifier requires an additional mask paramter. If the uri or parameters are incorrect, a 400 response is returned. """ logger.info("GET invoked from %s with uri: %s", self.client_address, self.path) rest_params = web_util.get_restful_params(self.path) if rest_params is None: web_util.echo_json_response( self, 405, "Not Implemented: Use /version, /keys/ or /quotes/ interfaces") return if "version" in rest_params: version_info = { "supported_version": keylime_api_version.current_version() } web_util.echo_json_response(self, 200, "Success", version_info) return if not rest_params["api_version"]: web_util.echo_json_response(self, 400, "API Version not supported") return if "keys" in rest_params and rest_params["keys"] == "verify": if self.server.K is None: logger.info( "GET key challenge returning 400 response. bootstrap key not available" ) web_util.echo_json_response( self, 400, "Bootstrap key not yet available.") return if "challenge" not in rest_params: logger.info( "GET key challenge returning 400 response. No challenge provided" ) web_util.echo_json_response(self, 400, "No challenge provided.") return challenge = rest_params["challenge"] response = {} response["hmac"] = crypto.do_hmac(self.server.K, challenge) web_util.echo_json_response(self, 200, "Success", response) logger.info("GET key challenge returning 200 response.") # If agent pubkey requested elif "keys" in rest_params and rest_params["keys"] == "pubkey": response = {} response["pubkey"] = self.server.rsapublickey_exportable web_util.echo_json_response(self, 200, "Success", response) logger.info("GET pubkey returning 200 response.") return elif "quotes" in rest_params: nonce = rest_params.get("nonce", None) pcrmask = rest_params.get("mask", None) ima_ml_entry = rest_params.get("ima_ml_entry", "0") # if the query is not messed up if nonce is None: logger.warning( "GET quote returning 400 response. nonce not provided as an HTTP parameter in request" ) web_util.echo_json_response( self, 400, "nonce not provided as an HTTP parameter in request") return # Sanitization assurance (for tpm.run() tasks below) if not (nonce.isalnum() and (pcrmask is None or validators.valid_hex(pcrmask)) and ima_ml_entry.isalnum()): logger.warning( "GET quote returning 400 response. parameters should be strictly alphanumeric" ) web_util.echo_json_response( self, 400, "parameters should be strictly alphanumeric") return if len(nonce) > tpm_instance.MAX_NONCE_SIZE: logger.warning( "GET quote returning 400 response. Nonce is too long (max size %i): %i", tpm_instance.MAX_NONCE_SIZE, len(nonce), ) web_util.echo_json_response( self, 400, f"Nonce is too long (max size {tpm_instance.MAX_NONCE_SIZE}): {len(nonce)}" ) return hash_alg = tpm_instance.defaults["hash"] quote = tpm_instance.create_quote( nonce, self.server.rsapublickey_exportable, pcrmask, hash_alg) imaMask = pcrmask # Allow for a partial quote response (without pubkey) enc_alg = tpm_instance.defaults["encrypt"] sign_alg = tpm_instance.defaults["sign"] if "partial" in rest_params and (rest_params["partial"] is None or rest_params["partial"] == "1"): response = { "quote": quote, "hash_alg": hash_alg, "enc_alg": enc_alg, "sign_alg": sign_alg, } else: response = { "quote": quote, "hash_alg": hash_alg, "enc_alg": enc_alg, "sign_alg": sign_alg, "pubkey": self.server.rsapublickey_exportable, } response["boottime"] = self.server.boottime # return a measurement list if available if TPM_Utilities.check_mask(imaMask, config.IMA_PCR): ima_ml_entry = int(ima_ml_entry) if ima_ml_entry > self.server.next_ima_ml_entry: ima_ml_entry = 0 ml, nth_entry, num_entries = ima.read_measurement_list( self.server.ima_log_file, ima_ml_entry) if num_entries > 0: response["ima_measurement_list"] = ml response["ima_measurement_list_entry"] = nth_entry self.server.next_ima_ml_entry = num_entries # similar to how IMA log retrievals are triggered by IMA_PCR, we trigger boot logs with MEASUREDBOOT_PCRs # other possibilities would include adding additional data to rest_params to trigger boot log retrievals # generally speaking, retrieving the 15Kbytes of a boot log does not seem significant compared to the # potential Mbytes of an IMA measurement list. if TPM_Utilities.check_mask(imaMask, config.MEASUREDBOOT_PCRS[0]): if not self.server.tpm_log_file_data: logger.warning("TPM2 event log not available: %s", config.MEASUREDBOOT_ML) else: response[ "mb_measurement_list"] = self.server.tpm_log_file_data web_util.echo_json_response(self, 200, "Success", response) logger.info("GET %s quote returning 200 response.", rest_params["quotes"]) return else: logger.warning("GET returning 400 response. uri not supported: %s", self.path) web_util.echo_json_response(self, 400, "uri not supported") return
else: tls_enabled = False cert = "" logger.warning( "Warning: TLS is currently disabled, keys will be sent in the clear! This should only be used for testing." ) verifier_ip = config.get('cloud_verifier', 'cloudverifier_ip') verifier_port = config.get('cloud_verifier', 'cloudverifier_port') verifier_base_url = f'{verifier_ip}:{verifier_port}' registrar_ip = config.get('registrar', 'registrar_ip') registrar_tls_port = config.get('registrar', 'registrar_tls_port') registrar_base_tls_url = f'{registrar_ip}:{registrar_tls_port}' api_version = keylime_api_version.current_version() class Agent_Init_Types: FILE = '0' KEYFILE = '1' CA_DIR = '2' class BaseHandler(tornado.web.RequestHandler): def write_error(self, status_code, **kwargs): if self.settings.get("serve_traceback") and "exc_info" in kwargs: # in debug mode, try to send a traceback lines = [] for line in traceback.format_exception(*kwargs["exc_info"]):
def main(): for ML in [config.MEASUREDBOOT_ML, config.IMA_ML]: if not os.access(ML, os.F_OK): logger.warning( "Measurement list path %s not accessible by agent. Any attempt to instruct it to access this path - via \"keylime_tenant\" CLI - will result in agent process dying", ML) ima_log_file = None if os.path.exists(config.IMA_ML): ima_log_file = open(config.IMA_ML, 'r', encoding="utf-8") tpm_log_file_data = None if os.path.exists(config.MEASUREDBOOT_ML): with open(config.MEASUREDBOOT_ML, 'rb') as tpm_log_file: tpm_log_file_data = base64.b64encode(tpm_log_file.read()) if config.get('cloud_agent', 'agent_uuid') == 'dmidecode': if os.getuid() != 0: raise RuntimeError('agent_uuid is configured to use dmidecode, ' 'but current process is not running as root.') cmd = ['which', 'dmidecode'] ret = cmd_exec.run(cmd, raiseOnError=False) if ret['code'] != 0: raise RuntimeError('agent_uuid is configured to use dmidecode, ' 'but it\'s is not found on the system.') # initialize the tmpfs partition to store keys if it isn't already available secdir = secure_mount.mount() # Now that operations requiring root privileges are done, drop privileges # if 'run_as' is available in the configuration. if os.getuid() == 0: run_as = config.get('cloud_agent', 'run_as', fallback='') if run_as != '': user_utils.chown(secdir, run_as) user_utils.change_uidgid(run_as) logger.info(f"Dropped privileges to {run_as}") else: logger.warning( "Cannot drop privileges since 'run_as' is empty or missing in keylime.conf agent section." ) # Instanitate TPM class instance_tpm = tpm() # get params for initialization registrar_ip = config.get('cloud_agent', 'registrar_ip') registrar_port = config.get('cloud_agent', 'registrar_port') # get params for the verifier to contact the agent contact_ip = os.getenv("KEYLIME_AGENT_CONTACT_IP", None) if contact_ip is None and config.has_option('cloud_agent', 'agent_contact_ip'): contact_ip = config.get('cloud_agent', 'agent_contact_ip') contact_port = os.getenv("KEYLIME_AGENT_CONTACT_PORT", None) if contact_port is None and config.has_option('cloud_agent', 'agent_contact_port'): contact_port = config.get('cloud_agent', 'agent_contact_port', fallback="invalid") # change dir to working dir fs_util.ch_dir(config.WORK_DIR) # set a conservative general umask os.umask(0o077) # initialize tpm (ekcert, ek_tpm, aik_tpm) = instance_tpm.tpm_init( self_activate=False, config_pw=config.get('cloud_agent', 'tpm_ownerpassword') ) # this tells initialize not to self activate the AIK virtual_agent = instance_tpm.is_vtpm() # Warn if kernel version is <5.10 and another algorithm than SHA1 is used, # because otherwise IMA will not work kernel_version = tuple(platform.release().split("-")[0].split(".")) if tuple(map(int, kernel_version)) < ( 5, 10, 0) and instance_tpm.defaults["hash"] != algorithms.Hash.SHA1: logger.warning( "IMA attestation only works on kernel versions <5.10 with SHA1 as hash algorithm. " "Even if ascii_runtime_measurements shows \"%s\" as the " "algorithm, it might be just padding zeros", (instance_tpm.defaults["hash"])) if ekcert is None: if virtual_agent: ekcert = 'virtual' elif instance_tpm.is_emulator(): ekcert = 'emulator' # now we need the UUID try: agent_uuid = config.get('cloud_agent', 'agent_uuid') except configparser.NoOptionError: agent_uuid = None if agent_uuid == 'hash_ek': ek_pubkey = pubkey_from_tpm2b_public(base64.b64decode(ek_tpm)) ek_pubkey_pem = ek_pubkey.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) agent_uuid = hashlib.sha256(ek_pubkey_pem).hexdigest() elif agent_uuid == 'generate' or agent_uuid is None: agent_uuid = str(uuid.uuid4()) elif agent_uuid == 'dmidecode': cmd = ['dmidecode', '-s', 'system-uuid'] ret = cmd_exec.run(cmd) sys_uuid = ret['retout'][0].decode('utf-8') agent_uuid = sys_uuid.strip() try: uuid.UUID(agent_uuid) except ValueError as e: raise RuntimeError( "The UUID returned from dmidecode is invalid: %s" % e) # pylint: disable=raise-missing-from elif agent_uuid == 'hostname': agent_uuid = socket.getfqdn() elif agent_uuid == 'environment': agent_uuid = os.getenv("KEYLIME_AGENT_UUID", None) if agent_uuid is None: raise RuntimeError( "Env variable KEYLIME_AGENT_UUID is empty, but agent_uuid is set to 'environment'" ) elif not validators.valid_uuid(agent_uuid): raise RuntimeError("The UUID is not valid") if not validators.valid_agent_id(agent_uuid): raise RuntimeError( "The agent ID set via agent uuid parameter use invalid characters") if config.STUB_VTPM and config.TPM_CANNED_VALUES is not None: # Use canned values for stubbing jsonIn = config.TPM_CANNED_VALUES if "add_vtpm_to_group" in jsonIn: # The value we're looking for has been canned! agent_uuid = jsonIn['add_vtpm_to_group']['retout'] else: # Our command hasn't been canned! raise Exception("Command %s not found in canned json!" % ("add_vtpm_to_group")) logger.info("Agent UUID: %s", agent_uuid) serveraddr = (config.get('cloud_agent', 'cloudagent_ip'), config.getint('cloud_agent', 'cloudagent_port')) keylime_ca = config.get('cloud_agent', 'keylime_ca') if keylime_ca == "default": keylime_ca = os.path.join(config.WORK_DIR, 'cv_ca', 'cacert.crt') server = CloudAgentHTTPServer(serveraddr, Handler, agent_uuid, contact_ip, ima_log_file, tpm_log_file_data) context = web_util.generate_mtls_context(server.mtls_cert_path, server.rsakey_path, keylime_ca, logger=logger) server.socket = context.wrap_socket(server.socket, server_side=True) serverthread = threading.Thread(target=server.serve_forever, daemon=True) # register it and get back a blob mtls_cert = server.mtls_cert.public_bytes(serialization.Encoding.PEM) keyblob = registrar_client.doRegisterAgent(registrar_ip, registrar_port, agent_uuid, ek_tpm, ekcert, aik_tpm, mtls_cert, contact_ip, contact_port) if keyblob is None: instance_tpm.flush_keys() raise Exception("Registration failed") # get the ephemeral registrar key key = instance_tpm.activate_identity(keyblob) if key is None: instance_tpm.flush_keys() raise Exception("Activation failed") # tell the registrar server we know the key retval = registrar_client.doActivateAgent(registrar_ip, registrar_port, agent_uuid, key) if not retval: instance_tpm.flush_keys() raise Exception("Registration failed on activate") # Start revocation listener in a new process to not interfere with tornado revocation_process = multiprocessing.Process(target=revocation_listener, daemon=True) revocation_process.start() logger.info( "Starting Cloud Agent on %s:%s with API version %s. Use <Ctrl-C> to stop", serveraddr[0], serveraddr[1], keylime_api_version.current_version()) serverthread.start() def shutdown_handler(*_): logger.info("TERM Signal received, shutting down...") logger.debug("Stopping revocation notifier...") revocation_process.terminate() logger.debug("Shutting down HTTP server...") server.shutdown() server.server_close() serverthread.join() logger.debug("HTTP server stopped...") revocation_process.join() logger.debug("Revocation notifier stopped...") secure_mount.umount() logger.debug("Umounting directories...") instance_tpm.flush_keys() logger.debug("Flushed keys successfully") sys.exit(0) signal.signal(signal.SIGTERM, shutdown_handler) signal.signal(signal.SIGQUIT, shutdown_handler) signal.signal(signal.SIGINT, shutdown_handler) # Keep the main thread alive by waiting for the server thread serverthread.join()
def test_current_version(self): self.assertEqual(api_version.current_version(), "2.1", "Current version is 2.1")
def main(): for ML in [ config.MEASUREDBOOT_ML, config.IMA_ML ] : if not os.access(ML, os.F_OK) : logger.warning("Measurement list path %s not accessible by agent. Any attempt to instruct it to access this path - via \"keylime_tenant\" CLI - will result in agent process dying", ML) if config.get('cloud_agent', 'agent_uuid') == 'dmidecode': if os.getuid() != 0: raise RuntimeError('agent_uuid is configured to use dmidecode, ' 'but current process is not running as root.') cmd = ['which', 'dmidecode'] ret = cmd_exec.run(cmd, raiseOnError=False) if ret['code'] != 0: raise RuntimeError('agent_uuid is configured to use dmidecode, ' 'but it\'s is not found on the system.') # Instanitate TPM class instance_tpm = tpm() # get params for initialization registrar_ip = config.get('cloud_agent', 'registrar_ip') registrar_port = config.get('cloud_agent', 'registrar_port') # get params for the verifier to contact the agent contact_ip = os.getenv("KEYLIME_AGENT_CONTACT_IP", None) if contact_ip is None and config.has_option('cloud_agent', 'agent_contact_ip'): contact_ip = config.get('cloud_agent', 'agent_contact_ip') contact_port = os.getenv("KEYLIME_AGENT_CONTACT_PORT", None) if contact_port is None and config.has_option('cloud_agent', 'agent_contact_port'): contact_port = config.get('cloud_agent', 'agent_contact_port', fallback="invalid") # initialize the tmpfs partition to store keys if it isn't already available secdir = secure_mount.mount() # change dir to working dir config.ch_dir(config.WORK_DIR, logger) # initialize tpm (ekcert, ek_tpm, aik_tpm) = instance_tpm.tpm_init(self_activate=False, config_pw=config.get( 'cloud_agent', 'tpm_ownerpassword')) # this tells initialize not to self activate the AIK virtual_agent = instance_tpm.is_vtpm() if ekcert is None: if virtual_agent: ekcert = 'virtual' elif instance_tpm.is_emulator(): ekcert = 'emulator' # now we need the UUID try: agent_uuid = config.get('cloud_agent', 'agent_uuid') except configparser.NoOptionError: agent_uuid = None if agent_uuid == 'openstack': agent_uuid = openstack.get_openstack_uuid() elif agent_uuid == 'hash_ek': agent_uuid = hashlib.sha256(ek_tpm).hexdigest() elif agent_uuid == 'generate' or agent_uuid is None: agent_uuid = str(uuid.uuid4()) elif agent_uuid == 'dmidecode': cmd = ['dmidecode', '-s', 'system-uuid'] ret = cmd_exec.run(cmd) sys_uuid = ret['retout'][0].decode('utf-8') agent_uuid = sys_uuid.strip() try: uuid.UUID(agent_uuid) except ValueError as e: raise RuntimeError("The UUID returned from dmidecode is invalid: %s" % e) # pylint: disable=raise-missing-from elif agent_uuid == 'hostname': agent_uuid = socket.getfqdn() if config.STUB_VTPM and config.TPM_CANNED_VALUES is not None: # Use canned values for stubbing jsonIn = config.TPM_CANNED_VALUES if "add_vtpm_to_group" in jsonIn: # The value we're looking for has been canned! agent_uuid = jsonIn['add_vtpm_to_group']['retout'] else: # Our command hasn't been canned! raise Exception("Command %s not found in canned json!" % ("add_vtpm_to_group")) logger.info("Agent UUID: %s", agent_uuid) # register it and get back a blob keyblob = registrar_client.doRegisterAgent( registrar_ip, registrar_port, agent_uuid, ek_tpm, ekcert, aik_tpm, contact_ip, contact_port) if keyblob is None: instance_tpm.flush_keys() raise Exception("Registration failed") # get the ephemeral registrar key key = instance_tpm.activate_identity(keyblob) if key is None: instance_tpm.flush_keys() raise Exception("Activation failed") # tell the registrar server we know the key retval = False retval = registrar_client.doActivateAgent( registrar_ip, registrar_port, agent_uuid, key) if not retval: instance_tpm.flush_keys() raise Exception("Registration failed on activate") serveraddr = (config.get('cloud_agent', 'cloudagent_ip'), config.getint('cloud_agent', 'cloudagent_port')) server = CloudAgentHTTPServer(serveraddr, Handler, agent_uuid) serverthread = threading.Thread(target=server.serve_forever) logger.info("Starting Cloud Agent on %s:%s with API version %s. Use <Ctrl-C> to stop", serveraddr[0], serveraddr[1], keylime_api_version.current_version()) serverthread.start() # want to listen for revocations? if config.getboolean('cloud_agent', 'listen_notfications'): cert_path = config.get('cloud_agent', 'revocation_cert') if cert_path == "default": cert_path = os.path.join(secdir, "unzipped/RevocationNotifier-cert.crt") elif cert_path[0] != '/': # if it is a relative, convert to absolute in work_dir cert_path = os.path.abspath( os.path.join(config.WORK_DIR, cert_path)) 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 = ["revocation_actions.%s" % 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 = "%s/unzipped" % secdir 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: revocation_notifier.await_notifications( perform_actions, revocation_cert_path=cert_path) except Exception as e: logger.exception(e) logger.warning("No connection to revocation server, retrying in 10s...") time.sleep(10) except KeyboardInterrupt: logger.info("TERM Signal received, shutting down...") instance_tpm.flush_keys() server.shutdown() else: try: while True: time.sleep(1) except KeyboardInterrupt: logger.info("TERM Signal received, shutting down...") instance_tpm.flush_keys() server.shutdown()