async def get_agent_state(self, agent_id): try: get_agent_state = RequestsClient(verifier_base_url, tls_enabled) response = get_agent_state.get( (f'/agents/{agent_id}'), cert=cert, verify=False ) except Exception as e: logger.error("Status command response: %s:%s Unexpected response from Cloud Verifier.", tenant_templ.cloudverifier_ip, tenant_templ.cloudverifier_port) logger.exception(e) config.echo_json_response( self, 500, "Unexpected response from Cloud Verifier", str(e)) logger.error("Unexpected response from Cloud Verifier: %s", e) return inst_response_body = response.json() if response.status_code != 200 and response.status_code != 404: logger.error("Status command response: %d Unexpected response from Cloud Verifier.", response.status_code) keylime_logging.log_http_response( logger, logging.ERROR, inst_response_body) return None if "results" not in inst_response_body: logger.critical("Error: unexpected http response body from Cloud Verifier: %s", response.status_code) return None # Agent not added to CV (but still registered) if response.status_code == 404: return {"operational_state": states.REGISTERED} return inst_response_body["results"]
def get(self): """Get an allowlist GET /(?:v[0-9]/)?allowlists/{name} """ rest_params = config.get_restful_params(self.request.uri) if rest_params is None or 'allowlists' not in rest_params: config.echo_json_response(self, 400, "Invalid URL") return allowlist_name = rest_params['allowlists'] if allowlist_name is None: config.echo_json_response(self, 400, "Invalid URL") logger.warning( 'GET returning 400 response: ' + self.request.path) return session = get_session() try: allowlist = session.query(VerifierAllowlist).filter_by( name=allowlist_name).one() except NoResultFound: config.echo_json_response(self, 404, "Allowlist %s not found" % allowlist_name) return except SQLAlchemyError as e: logger.error(f'SQLAlchemy Error: {e}') config.echo_json_response(self, 500, "Failed to get allowlist") raise response = {} for field in ('name', 'tpm_policy', 'vtpm_policy', 'ima_policy'): response[field] = getattr(allowlist, field, None) config.echo_json_response(self, 200, 'Success', response)
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"]): lines.append(line) config.echo_json_response(self, status_code, self._reason, lines) else: config.echo_json_response(self, status_code, self._reason)
def delete(self): """Delete an allowlist DELETE /(?:v[0-9]/)?allowlists/{name} """ rest_params = config.get_restful_params(self.request.uri) if rest_params is None or 'allowlists' not in rest_params: config.echo_json_response(self, 400, "Invalid URL") return allowlist_name = rest_params['allowlists'] if allowlist_name is None: config.echo_json_response(self, 400, "Invalid URL") logger.warning( 'DELETE returning 400 response: ' + self.request.path) return session = get_session() try: session.query(VerifierAllowlist).filter_by( name=allowlist_name).one() except NoResultFound: config.echo_json_response(self, 404, "Allowlist %s not found" % allowlist_name) return except SQLAlchemyError as e: logger.error(f'SQLAlchemy Error: {e}') config.echo_json_response(self, 500, "Failed to get allowlist") raise try: session.query(VerifierAllowlist).filter_by( name=allowlist_name).delete() session.commit() except SQLAlchemyError as e: logger.error(f'SQLAlchemy Error: {e}') config.echo_json_response(self, 500, "Failed to get allowlist") raise # NOTE(kaifeng) 204 Can not have response body, but current helper # doesn't support this case. self.set_status(204) self.set_header('Content-Type', 'application/json') self.finish() logger.info( 'DELETE returning 204 response for allowlist: ' + allowlist_name)
def get(self): """This method handles the GET requests to retrieve status on agents from the Cloud Verifier. Currently, only agents resources are available for GETing, i.e. /agents. All other GET uri's will return errors. Agents requests require a single agent_id parameter which identifies the agent to be returned. If the agent_id is not found, a 404 response is returned. If the agent_id was not found, it either completed successfully, or failed. If found, the agent_id is still polling to contact the Cloud Agent. """ session = self.make_session(engine) rest_params = config.get_restful_params(self.request.uri) if rest_params is None: config.echo_json_response( self, 405, "Not Implemented: Use /agents/ interface") return if "agents" not in rest_params: config.echo_json_response(self, 400, "uri not supported") logger.warning('GET returning 400 response. uri not supported: ' + self.request.path) return agent_id = rest_params["agents"] if agent_id is not None: try: agent = session.query(VerfierMain).filter_by( agent_id=agent_id).one_or_none() except SQLAlchemyError as e: logger.error(f'SQLAlchemy Error: {e}') if agent is not None: response = cloud_verifier_common.process_get_status(agent) config.echo_json_response(self, 200, "Success", response) else: config.echo_json_response(self, 404, "agent id not found") else: json_response = session.query(VerfierMain.agent_id).all() config.echo_json_response(self, 200, "Success", {'uuids': json_response}) logger.info('GET returning 200 response for agent_id list')
def delete(self): """This method handles the DELETE requests to remove agents from the Cloud Verifier. Currently, only agents resources are available for DELETEing, i.e. /agents. All other DELETE uri's will return errors. agents requests require a single agent_id parameter which identifies the agent to be deleted. """ rest_params = config.get_restful_params(self.request.uri) if rest_params is None: config.echo_json_response( self, 405, "Not Implemented: Use /agents/ interface") return if "agents" not in rest_params: config.echo_json_response(self, 400, "uri not supported") logger.warning( 'DELETE returning 400 response. uri not supported: %s', self.request.path) return agent_id = rest_params["agents"] # let Tenant do dirty work of deleting agent mytenant = tenant.Tenant() mytenant.agent_uuid = agent_id mytenant.do_cvdelete() config.echo_json_response(self, 200, "Success")
def put(self): """This method handles the PUT requests to add agents to the Cloud Verifier. Currently, only agents resources are available for PUTing, i.e. /agents. All other PUT uri's will return errors. """ rest_params = config.get_restful_params(self.request.uri) if rest_params is None: config.echo_json_response( self, 405, "Not Implemented: Use /agents/ interface") return if "agents" not in rest_params: config.echo_json_response(self, 400, "uri not supported") logger.warning('PUT returning 400 response. uri not supported: %s', self.request.path) return agent_id = rest_params["agents"] # let Tenant do dirty work of reactivating agent mytenant = tenant.Tenant() mytenant.agent_uuid = agent_id mytenant.do_cvreactivate() config.echo_json_response(self, 200, "Success")
def do_DELETE(self): """This method handles the DELETE requests to remove agents from the Registrar Server. Currently, only agents resources are available for DELETEing, i.e. /agents. All other DELETE uri's will return errors. agents requests require a single agent_id parameter which identifies the agent to be deleted. """ session = SessionManager().make_session(engine) rest_params = config.get_restful_params(self.path) if rest_params is None: config.echo_json_response( self, 405, "Not Implemented: Use /agents/ interface") return if "agents" not in rest_params: config.echo_json_response(self, 400, "uri not supported") logger.warning( 'DELETE agent returning 400 response. uri not supported: %s', self.path) return agent_id = rest_params["agents"] if agent_id is not None: if session.query(RegistrarMain).filter_by( agent_id=agent_id).delete(): # send response try: session.commit() except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e) config.echo_json_response(self, 200, "Success") return # send response config.echo_json_response(self, 404) return config.echo_json_response(self, 404)
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 post(self): """This method handles the POST requests to add agents to the Cloud Verifier. Currently, only agents resources are available for POSTing, i.e. /agents. All other POST uri's will return errors. agents requests require a json block sent in the body """ session = get_session() try: rest_params = config.get_restful_params(self.request.uri) if rest_params is None: config.echo_json_response( self, 405, "Not Implemented: Use /agents/ interface") return if "agents" not in rest_params: config.echo_json_response(self, 400, "uri not supported") logger.warning('POST returning 400 response. uri not supported: %s', self.request.path) return agent_id = rest_params["agents"] if agent_id is not None: content_length = len(self.request.body) if content_length == 0: config.echo_json_response( self, 400, "Expected non zero content length") logger.warning('POST returning 400 response. Expected non zero content length.') else: json_body = json.loads(self.request.body) agent_data = {} agent_data['v'] = json_body['v'] agent_data['ip'] = json_body['cloudagent_ip'] agent_data['port'] = int(json_body['cloudagent_port']) agent_data['operational_state'] = states.START agent_data['public_key'] = "" agent_data['tpm_policy'] = json_body['tpm_policy'] agent_data['vtpm_policy'] = json_body['vtpm_policy'] agent_data['meta_data'] = json_body['metadata'] agent_data['allowlist'] = json_body['allowlist'] agent_data['mb_refstate'] = json_body['mb_refstate'] agent_data['ima_sign_verification_keys'] = json_body['ima_sign_verification_keys'] agent_data['revocation_key'] = json_body['revocation_key'] agent_data['accept_tpm_hash_algs'] = json_body['accept_tpm_hash_algs'] agent_data['accept_tpm_encryption_algs'] = json_body['accept_tpm_encryption_algs'] agent_data['accept_tpm_signing_algs'] = json_body['accept_tpm_signing_algs'] agent_data['hash_alg'] = "" agent_data['enc_alg'] = "" agent_data['sign_alg'] = "" agent_data['agent_id'] = agent_id is_valid, err_msg = cloud_verifier_common.validate_agent_data(agent_data) if not is_valid: config.echo_json_response(self, 400, err_msg) logger.warning(err_msg) return try: new_agent_count = session.query( VerfierMain).filter_by(agent_id=agent_id).count() except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e) # don't allow overwriting if new_agent_count > 0: config.echo_json_response( self, 409, "Agent of uuid %s already exists" % (agent_id)) logger.warning("Agent of uuid %s already exists", agent_id) else: try: # Add the agent and data session.add(VerfierMain(**agent_data)) session.commit() except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e) for key in list(exclude_db.keys()): agent_data[key] = exclude_db[key] asyncio.ensure_future( process_agent(agent_data, states.GET_QUOTE)) config.echo_json_response(self, 200, "Success") logger.info('POST returning 200 response for adding agent id: %s', agent_id) else: config.echo_json_response(self, 400, "uri not supported") logger.warning("POST returning 400 response. uri not supported") except Exception as e: config.echo_json_response(self, 400, "Exception error: %s" % e) logger.warning("POST returning 400 response. Exception error: %s", e) logger.exception(e) self.finish()
def delete(self): """This method handles the DELETE requests to remove agents from the Cloud Verifier. Currently, only agents resources are available for DELETEing, i.e. /agents. All other DELETE uri's will return errors. agents requests require a single agent_id parameter which identifies the agent to be deleted. """ session = get_session() rest_params = config.get_restful_params(self.request.uri) if rest_params is None: config.echo_json_response( self, 405, "Not Implemented: Use /agents/ interface") return if "agents" not in rest_params: config.echo_json_response(self, 400, "uri not supported") return agent_id = rest_params["agents"] if agent_id is None: config.echo_json_response(self, 400, "uri not supported") logger.warning('DELETE returning 400 response. uri not supported: %s', self.request.path) return try: agent = session.query(VerfierMain).filter_by( agent_id=agent_id).first() except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e) if agent is None: config.echo_json_response(self, 404, "agent id not found") logger.info('DELETE returning 404 response. agent id: %s not found.', agent_id) return op_state = agent.operational_state if op_state in (states.SAVED, states.FAILED, states.TERMINATED, states.TENANT_FAILED, states.INVALID_QUOTE): try: session.query(VerfierMain).filter_by( agent_id=agent_id).delete() session.commit() except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e) config.echo_json_response(self, 200, "Success") logger.info('DELETE returning 200 response for agent id: %s', agent_id) else: try: update_agent = session.query(VerfierMain).get(agent_id) update_agent.operational_state = states.TERMINATED try: session.add(update_agent) except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e) session.commit() config.echo_json_response(self, 202, "Accepted") logger.info('DELETE returning 202 response for agent id: %s', agent_id) except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e)
def head(self): """HEAD not supported""" config.echo_json_response(self, 405, "HEAD not supported")
def put(self): config.echo_json_response( self, 405, "Not Implemented: Use /agents/ interface instead")
def do_GET(self): """This method handles the GET requests to retrieve status on agents from the Registrar Server. Currently, only agents resources are available for GETing, i.e. /agents. All other GET uri's will return errors. agents requests require a single agent_id parameter which identifies the agent to be returned. If the agent_id is not found, a 404 response is returned. """ session = SessionManager().make_session(engine) rest_params = config.get_restful_params(self.path) if rest_params is None: config.echo_json_response( self, 405, "Not Implemented: Use /agents/ interface") return if "agents" not in rest_params: config.echo_json_response(self, 400, "uri not supported") logger.warning('GET returning 400 response. uri not supported: %s', self.path) return agent_id = rest_params["agents"] if agent_id is not None: try: agent = session.query(RegistrarMain).filter_by( agent_id=agent_id).first() except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e) if agent is None: config.echo_json_response(self, 404, "agent_id not found") logger.warning( 'GET returning 404 response. agent_id %s not found.', agent_id) return if not agent.active: config.echo_json_response(self, 404, "agent_id not yet active") logger.warning( 'GET returning 404 response. agent_id %s not yet active.', agent_id) return response = { 'aik_tpm': agent.aik_tpm, 'ek_tpm': agent.ek_tpm, 'ekcert': agent.ekcert, 'regcount': agent.regcount, } if agent.virtual: response['provider_keys'] = agent.provider_keys config.echo_json_response(self, 200, "Success", response) logger.info('GET returning 200 response for agent_id: %s', agent_id) else: # return the available registered uuids from the DB json_response = session.query(RegistrarMain.agent_id).all() return_response = [item[0] for item in json_response] config.echo_json_response(self, 200, "Success", {'uuids': return_response}) logger.info('GET returning 200 response for agent_id list') return
async def get(self): """This method handles the GET requests to retrieve status on agents from the WebApp. Currently, only the web app is available for GETing, i.e. /agents. All other GET uri's will return errors. """ rest_params = config.get_restful_params(self.request.uri) if rest_params is None: config.echo_json_response( self, 405, "Not Implemented: Use /agents/ or /logs/ interface") return if "logs" in rest_params and rest_params["logs"] == "tenant": offset = 0 if "pos" in rest_params and rest_params[ "pos"] is not None and rest_params["pos"].isdigit(): offset = int(rest_params["pos"]) # intercept requests for logs with open(keylime_logging.LOGSTREAM, 'r') as f: logValue = f.readlines() config.echo_json_response(self, 200, "Success", {'log': logValue[offset:]}) return if "agents" not in rest_params: # otherwise they must be looking for agent info config.echo_json_response(self, 400, "uri not supported") logger.warning('GET returning 400 response. uri not supported: %s', self.request.path) return agent_id = rest_params["agents"] if agent_id is not None: # Handle request for specific agent data separately agents = await self.get_agent_state(agent_id) agents["id"] = agent_id config.echo_json_response(self, 200, "Success", agents) return # If no agent ID, get list of all agents from Registrar try: get_agents = RequestsClient(registrar_base_tls_url, tls_enabled) response = get_agents.get(('/agents/'), cert=cert, verify=False) except Exception as e: logger.error( "Status command response: %s:%s Unexpected response from Registrar.", tenant_templ.registrar_ip, tenant_templ.registrar_port) logger.exception(e) config.echo_json_response(self, 500, "Unexpected response from Registrar", str(e)) return response_body = response.json() if response.status_code != 200: logger.error( "Status command response: %d Unexpected response from Registrar.", response.status_code) keylime_logging.log_http_response(logger, logging.ERROR, response_body) return None if ("results" not in response_body) or ("uuids" not in response_body["results"]): logger.critical( "Error: unexpected http response body from Registrar: %s", response.status_code) return None agent_list = response_body["results"]["uuids"] # Loop through each agent and ask for status agents = {} for agent in agent_list: agents[agent] = await self.get_agent_state(agent_id) # Pre-create sorted agents list sorted_by_state = {} for state in states.VALID_STATES: sorted_by_state[state] = {} # Build sorted agents list for agent_id in agents: state = agents[agent_id]["operational_state"] sorted_by_state[state][agent_id] = agents[agent_id] print_order = [ states.TENANT_FAILED, states.INVALID_QUOTE, states.FAILED, states.GET_QUOTE, states.GET_QUOTE_RETRY, states.PROVIDE_V, states.PROVIDE_V_RETRY, states.SAVED, states.START, states.TERMINATED, states.REGISTERED ] sorted_agents = [] for state in print_order: for agent_id in sorted_by_state[state]: sorted_agents.append(agent_id) config.echo_json_response(self, 200, "Success", {'uuids': sorted_agents})
def do_PUT(self): """This method handles the PUT requests to add agents to the Registrar Server. Currently, only agents resources are available for PUTing, i.e. /agents. All other PUT uri's will return errors. """ session = SessionManager().make_session(engine) rest_params = config.get_restful_params(self.path) if rest_params is None: config.echo_json_response( self, 405, "Not Implemented: Use /agents/ interface") return if "agents" not in rest_params: config.echo_json_response(self, 400, "uri not supported") logger.warning( 'PUT agent returning 400 response. uri not supported: %s', self.path) return agent_id = rest_params["agents"] if agent_id is None: config.echo_json_response(self, 400, "agent id not found in uri") logger.warning( 'PUT agent returning 400 response. agent id not found in uri %s', self.path) return try: content_length = int(self.headers.get('Content-Length', 0)) if content_length == 0: config.echo_json_response(self, 400, "Expected non zero content length") logger.warning( 'PUT for %s returning 400 response. Expected non zero content length.', agent_id) return post_body = self.rfile.read(content_length) json_body = json.loads(post_body) auth_tag = json_body['auth_tag'] try: agent = session.query(RegistrarMain).filter_by( agent_id=agent_id).first() except NoResultFound as e: raise Exception( "attempting to activate agent before requesting " "registrar for %s" % agent_id) from e except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e) raise if config.STUB_TPM: try: session.query(RegistrarMain).filter( RegistrarMain.agent_id == agent_id).update( {'active': True}) session.commit() except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e) raise else: # TODO(kaifeng) Special handling should be removed if engine.dialect.name == "mysql": agent.key = agent.key.encode('utf-8') ex_mac = crypto.do_hmac(agent.key, agent_id) if ex_mac == auth_tag: try: session.query(RegistrarMain).filter( RegistrarMain.agent_id == agent_id).update( {'active': True}) session.commit() except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e) raise else: raise Exception( "Auth tag %s does not match expected value %s" % (auth_tag, ex_mac)) config.echo_json_response(self, 200, "Success") logger.info('PUT activated: %s', agent_id) except Exception as e: config.echo_json_response(self, 400, "Error: %s" % e) logger.warning("PUT for %s returning 400 response. Error: %s", agent_id, e) logger.exception(e) return
def post(self): """Create an allowlist POST /(?:v[0-9]/)?allowlists/{name} body: {"tpm_policy": {..}, "vtpm_policy": {..} """ rest_params = config.get_restful_params(self.request.uri) if rest_params is None or 'allowlists' not in rest_params: config.echo_json_response(self, 400, "Invalid URL") return allowlist_name = rest_params['allowlists'] if allowlist_name is None: config.echo_json_response(self, 400, "Invalid URL") return content_length = len(self.request.body) if content_length == 0: config.echo_json_response( self, 400, "Expected non zero content length") logger.warning( 'POST returning 400 response. Expected non zero content length.') return allowlist = {} json_body = json.loads(self.request.body) allowlist['name'] = allowlist_name tpm_policy = json_body.get('tpm_policy') if tpm_policy: allowlist['tpm_policy'] = tpm_policy vtpm_policy = json_body.get('vtpm_policy') if vtpm_policy: allowlist['vtpm_policy'] = vtpm_policy ima_policy = json_body.get('ima_policy') if ima_policy: allowlist['ima_policy'] = ima_policy session = get_session() # don't allow overwritting try: al_count = session.query( VerifierAllowlist).filter_by(name=allowlist_name).count() if al_count > 0: config.echo_json_response( self, 409, "Allowlist with name %s already exists" % allowlist_name) logger.warning( "Allowlist with name %s already exists" % allowlist_name) return except SQLAlchemyError as e: logger.error(f'SQLAlchemy Error: {e}') raise try: # Add the agent and data session.add(VerifierAllowlist(**allowlist)) session.commit() except SQLAlchemyError as e: logger.error(f'SQLAlchemy Error: {e}') raise config.echo_json_response(self, 201) logger.info('POST returning 201')
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 = config.get_restful_params(self.path) if rest_params is None: config.echo_json_response( self, 405, "Not Implemented: Use /keys/ or /quotes/ interfaces") 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' ) config.echo_json_response(self, 400, "Bootstrap key not yet available.") return challenge = rest_params['challenge'] response = {} response['hmac'] = crypto.do_hmac(self.server.K, challenge) config.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 config.echo_json_response(self, 200, "Success", response) logger.info('GET pubkey returning 200 response.') return elif "quotes" in rest_params: nonce = rest_params['nonce'] pcrmask = rest_params['mask'] if 'mask' in rest_params else None vpcrmask = rest_params['vmask'] if 'vmask' in rest_params else None # 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' ) config.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 pcrmask.isalnum()) and (vpcrmask is None or vpcrmask.isalnum())): logger.warning( 'GET quote returning 400 response. parameters should be strictly alphanumeric' ) config.echo_json_response( self, 400, "parameters should be strictly alphanumeric") return # identity quotes are always shallow hash_alg = tpm_instance.defaults['hash'] if not tpm_instance.is_vtpm( ) or rest_params["quotes"] == 'identity': 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 int(rest_params["partial"], 0) == 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, } # return a measurement list if available if TPM_Utilities.check_mask(imaMask, config.IMA_PCR): if not os.path.exists(config.IMA_ML): logger.warning("IMA measurement list not available: %s", config.IMA_ML) else: with open(config.IMA_ML, 'r') as f: ml = f.read() response['ima_measurement_list'] = ml # 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 os.path.exists(config.MEASUREDBOOT_ML): logger.warning("TPM2 event log not available: %s", config.MEASUREDBOOT_ML) else: with open(config.MEASUREDBOOT_ML, 'rb') as f: el = base64.b64encode(f.read()) response['mb_measurement_list'] = el config.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) config.echo_json_response(self, 400, "uri not supported") return
async def get(self): """This method handles the GET requests to retrieve status on agents from the WebApp. Currently, only the web app is available for GETing, i.e. /agents. All other GET uri's will return errors. """ rest_params = config.get_restful_params(self.request.uri) if rest_params is None: config.echo_json_response( self, 405, "Not Implemented: Use /agents/ or /logs/ interface") return if "logs" in rest_params and rest_params["logs"] == "tenant": offset = 0 if "pos" in rest_params and rest_params["pos"] is not None and rest_params["pos"].isdigit(): offset = int(rest_params["pos"]) # intercept requests for logs with open(keylime_logging.LOGSTREAM, 'r') as f: logValue = f.readlines() config.echo_json_response(self, 200, "Success", { 'log': logValue[offset:]}) return if "agents" not in rest_params: # otherwise they must be looking for agent info config.echo_json_response(self, 400, "uri not supported") logger.warning('GET returning 400 response. uri not supported: %s', self.request.path) return agent_id = rest_params["agents"] if agent_id is not None: # Handle request for specific agent data separately agents = await self.get_agent_state(agent_id) agents["id"] = agent_id config.echo_json_response(self, 200, "Success", agents) return # If no agent ID, get list of all agents from Registrar try: get_agents = RequestsClient(registrar_base_tls_url, tls_enabled) response = get_agents.get( ('/agents/'), cert=cert, verify=False ) except Exception as e: logger.error("Status command response: %s:%s Unexpected response from Registrar.", tenant_templ.registrar_ip, tenant_templ.registrar_port) logger.exception(e) config.echo_json_response( self, 500, "Unexpected response from Registrar", str(e)) return response_body = response.json() if response.status_code != 200: logger.error("Status command response: %d Unexpected response from Registrar.", response.status_code) keylime_logging.log_http_response( logger, logging.ERROR, response_body) return None if ("results" not in response_body) or ("uuids" not in response_body["results"]): logger.critical("Error: unexpected http response body from Registrar: %s", response.status_code) return None agent_list = response_body["results"]["uuids"] config.echo_json_response(self, 200, "Success", { 'uuids': agent_list})
def do_PUT(self): """PUT not supported""" config.echo_json_response(self, 405, "PUT not supported via TLS interface")
def delete(self): config.echo_json_response( self, 405, "Not Implemented: Use /webapp/, /agents/ or /logs/ interface instead" )
def put(self): """This method handles the PUT requests to add agents to the Cloud Verifier. Currently, only agents resources are available for PUTing, i.e. /agents. All other PUT uri's will return errors. agents requests require a json block sent in the body """ session = get_session() try: rest_params = config.get_restful_params(self.request.uri) if rest_params is None: config.echo_json_response( self, 405, "Not Implemented: Use /agents/ interface") return if "agents" not in rest_params: config.echo_json_response(self, 400, "uri not supported") logger.warning('PUT returning 400 response. uri not supported: %s', self.request.path) return agent_id = rest_params["agents"] if agent_id is None: config.echo_json_response(self, 400, "uri not supported") logger.warning("PUT returning 400 response. uri not supported") try: agent = session.query(VerfierMain).filter_by( agent_id=agent_id).one() except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e) if agent is None: config.echo_json_response(self, 404, "agent id not found") logger.info('PUT returning 404 response. agent id: %s not found.', agent_id) return if "reactivate" in rest_params: agent.operational_state = states.START asyncio.ensure_future( process_agent(agent, states.GET_QUOTE)) config.echo_json_response(self, 200, "Success") logger.info('PUT returning 200 response for agent id: %s', agent_id) elif "stop" in rest_params: # do stuff for terminate logger.debug("Stopping polling on %s", agent_id) try: session.query(VerfierMain).filter(VerfierMain.agent_id == agent_id).update( {'operational_state': states.TENANT_FAILED}) session.commit() except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e) config.echo_json_response(self, 200, "Success") logger.info('PUT returning 200 response for agent id: %s', agent_id) else: config.echo_json_response(self, 400, "uri not supported") logger.warning("PUT returning 400 response. uri not supported") except Exception as e: config.echo_json_response(self, 400, "Exception error: %s" % e) logger.warning("PUT returning 400 response. Exception error: %s", e) logger.exception(e) self.finish()
def do_DELETE(self): """DELETE not supported""" config.echo_json_response(self, 405, "DELETE not supported")
def head(self): config.echo_json_response( self, 400, "Allowlist handler: HEAD Not Implemented")
def do_PATCH(self): """PATCH not supported""" config.echo_json_response(self, 405, "PATCH not supported")
def do_GET(self): """GET not supported""" config.echo_json_response(self, 405, "GET not supported")
def post(self): """This method handles the POST requests to add agents to the Cloud Verifier. Currently, only agents resources are available for POSTing, i.e. /agents. All other POST uri's will return errors. agents requests require a yaml block sent in the body """ rest_params = config.get_restful_params(self.request.uri) if rest_params is None: config.echo_json_response( self, 405, "Not Implemented: Use /agents/ interface") return if "agents" not in rest_params: config.echo_json_response(self, 400, "uri not supported") logger.warning( 'POST returning 400 response. uri not supported: %s', self.request.path) return agent_id = rest_params["agents"] # Parse payload files (base64 data-uri) if self.get_argument("ptype", Agent_Init_Types.FILE, True) == Agent_Init_Types.FILE: keyfile = None payload = None data = { 'data': parse_data_uri(self.get_argument("file_data", None, True)) } ca_dir = None incl_dir = None ca_dir_pw = None elif self.get_argument("ptype", Agent_Init_Types.FILE, True) == Agent_Init_Types.KEYFILE: keyfile = { 'data': parse_data_uri(self.get_argument("keyfile_data", None, True)), } payload = { 'data': parse_data_uri(self.get_argument("file_data", None, True)) } data = None ca_dir = None incl_dir = None ca_dir_pw = None elif self.get_argument("ptype", Agent_Init_Types.FILE, True) == Agent_Init_Types.CA_DIR: keyfile = None payload = None data = None incl_dir = { 'data': parse_data_uri( self.get_argument("include_dir_data", None, True)), 'name': self.get_argument("include_dir_name", "", True).splitlines() } ca_dir = self.get_argument("ca_dir", 'default', True) if ca_dir == "": ca_dir = 'default' ca_dir_pw = self.get_argument("ca_dir_pw", 'default', True) if ca_dir_pw == "": ca_dir_pw = 'default' else: config.echo_json_response(self, 400, "invalid payload type chosen") logger.warning('POST returning 400 response. malformed query') return # Pull in user-defined v/TPM policies tpm_policy = self.get_argument("tpm_policy", "", True) if tpm_policy == "": tpm_policy = None vtpm_policy = self.get_argument("vtpm_policy", "", True) if vtpm_policy == "": vtpm_policy = None # Pull in allowlist allowlist = None a_list_data = self.get_argument("a_list_data", None, True) if a_list_data != "": allowlist_str = parse_data_uri(a_list_data) if allowlist_str is not None: allowlist = allowlist_str[0].splitlines() # Pull in IMA exclude list ima_exclude = None e_list_data = self.get_argument("e_list_data", None, True) if e_list_data != "": ima_exclude_str = parse_data_uri(e_list_data) if ima_exclude_str is not None: ima_exclude = ima_exclude_str[0].splitlines() # Build args to give to Tenant's init_add method args = { 'agent_ip': self.get_argument("agent_ip", None, True), 'file': data, 'keyfile': keyfile, 'payload': payload, 'ca_dir': ca_dir, 'incl_dir': incl_dir, 'ca_dir_pw': ca_dir_pw, 'tpm_policy': tpm_policy, 'vtpm_policy': vtpm_policy, 'allowlist': allowlist, 'ima_exclude': ima_exclude, } # let Tenant do dirty work of adding agent try: mytenant = tenant.Tenant() mytenant.agent_uuid = agent_id mytenant.init_add(args) mytenant.preloop() mytenant.do_cv() mytenant.do_quote() except Exception as e: logger.exception(e) logger.warning('POST returning 500 response. Tenant error: %s', e) config.echo_json_response(self, 500, "Request failure", str(e)) return config.echo_json_response(self, 200, "Success")
def put(self): config.echo_json_response( self, 400, "Allowlist handler: PUT Not Implemented")
def do_POST(self): """This method handles the POST requests to add agents to the Registrar Server. Currently, only agents resources are available for POSTing, i.e. /agents. All other POST uri's will return errors. POST requests require an an agent_id identifying the agent to add, and json block sent in the body with 2 entries: ek and aik. """ session = SessionManager().make_session(engine) rest_params = config.get_restful_params(self.path) if rest_params is None: config.echo_json_response( self, 405, "Not Implemented: Use /agents/ interface") return if "agents" not in rest_params: config.echo_json_response(self, 400, "uri not supported") logger.warning( 'POST agent returning 400 response. uri not supported: %s', self.path) return agent_id = rest_params["agents"] if agent_id is None: config.echo_json_response(self, 400, "agent id not found in uri") logger.warning( 'POST agent returning 400 response. agent id not found in uri %s', self.path) return try: content_length = int(self.headers.get('Content-Length', 0)) if content_length == 0: config.echo_json_response(self, 400, "Expected non zero content length") logger.warning( 'POST for %s returning 400 response. Expected non zero content length.', agent_id) return post_body = self.rfile.read(content_length) json_body = json.loads(post_body) ekcert = json_body['ekcert'] aik_tpm = json_body['aik_tpm'] initialize_tpm = tpm() if ekcert is None or ekcert == 'emulator': logger.warning('Agent %s did not submit an ekcert' % agent_id) ek_tpm = json_body['ek_tpm'] else: if 'ek_tpm' in json_body: # This would mean the agent submitted both a non-None ekcert, *and* # an ek_tpm... We can deal with it by just ignoring the ek_tpm they sent logger.warning( 'Overriding ek_tpm for agent %s from ekcert' % agent_id) # If there's an EKCert, we just overwrite their ek_tpm # Note, we don't validate the EKCert here, other than the implicit # "is it a valid x509 cert" check. So it's still untrusted. # This will be validated by the tenant. ek509 = load_der_x509_certificate( base64.b64decode(ekcert), backend=default_backend(), ) ek_tpm = base64.b64encode( tpm2_objects.ek_low_tpm2b_public_from_pubkey( ek509.public_key(), )) aik_attrs = tpm2_objects.get_tpm2b_public_object_attributes( base64.b64decode(aik_tpm), ) if aik_attrs != tpm2_objects.AK_EXPECTED_ATTRS: config.echo_json_response(self, 400, "Invalid AK attributes") logger.warning( "Agent %s submitted AIK with invalid attributes! %s (provided) != %s (expected)", agent_id, tpm2_objects.object_attributes_description(aik_attrs), tpm2_objects.object_attributes_description( tpm2_objects.AK_EXPECTED_ATTRS), ) return # try to encrypt the AIK (blob, key) = initialize_tpm.encryptAIK( agent_id, base64.b64decode(ek_tpm), base64.b64decode(aik_tpm), ) # special behavior if we've registered this uuid before regcount = 1 try: agent = session.query(RegistrarMain).filter_by( agent_id=agent_id).first() except NoResultFound: agent = None except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e) raise if agent is not None: # keep track of how many ek-ekcerts have registered on this uuid regcount = agent.regcount if agent.ek_tpm != ek_tpm or agent.ekcert != ekcert: logger.warning( 'WARNING: Overwriting previous registration for this UUID with new ek-ekcert pair!' ) regcount += 1 # force overwrite logger.info('Overwriting previous registration for this UUID.') try: session.query(RegistrarMain).filter_by( agent_id=agent_id).delete() session.commit() except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e) raise # Add values to database d = {} d['agent_id'] = agent_id d['ek_tpm'] = ek_tpm d['aik_tpm'] = aik_tpm d['ekcert'] = ekcert d['virtual'] = int(ekcert == 'virtual') d['active'] = int(False) d['key'] = key d['provider_keys'] = {} d['regcount'] = regcount try: session.add(RegistrarMain(**d)) session.commit() except SQLAlchemyError as e: logger.error('SQLAlchemy Error: %s', e) raise response = { 'blob': blob, } config.echo_json_response(self, 200, "Success", response) logger.info('POST returning key blob for agent_id: %s', agent_id) except Exception as e: config.echo_json_response(self, 400, "Error: %s" % e) logger.warning("POST for %s returning 400 response. Error: %s", agent_id, e) logger.exception(e)