Esempio n. 1
0
    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)
Esempio n. 3
0
    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)
Esempio n. 5
0
    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')
Esempio n. 6
0
    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")
Esempio n. 7
0
    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")
Esempio n. 8
0
    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)
Esempio n. 9
0
    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)
Esempio n. 11
0
    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()
Esempio n. 12
0
    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)
Esempio n. 13
0
 def head(self):
     """HEAD not supported"""
     config.echo_json_response(self, 405, "HEAD not supported")
Esempio n. 14
0
 def put(self):
     config.echo_json_response(
         self, 405, "Not Implemented: Use /agents/ interface instead")
Esempio n. 15
0
    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
Esempio n. 16
0
    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})
Esempio n. 17
0
    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
Esempio n. 18
0
    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')
Esempio n. 19
0
    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
Esempio n. 20
0
    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})
Esempio n. 21
0
 def do_PUT(self):
     """PUT not supported"""
     config.echo_json_response(self, 405,
                               "PUT not supported via TLS interface")
Esempio n. 22
0
 def delete(self):
     config.echo_json_response(
         self, 405,
         "Not Implemented: Use /webapp/, /agents/ or /logs/  interface instead"
     )
Esempio n. 23
0
    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()
Esempio n. 24
0
 def do_DELETE(self):
     """DELETE not supported"""
     config.echo_json_response(self, 405, "DELETE not supported")
Esempio n. 25
0
 def head(self):
     config.echo_json_response(
         self, 400, "Allowlist handler: HEAD Not Implemented")
Esempio n. 26
0
 def do_PATCH(self):
     """PATCH not supported"""
     config.echo_json_response(self, 405, "PATCH not supported")
Esempio n. 27
0
 def do_GET(self):
     """GET not supported"""
     config.echo_json_response(self, 405, "GET not supported")
Esempio n. 28
0
    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")
Esempio n. 29
0
 def put(self):
     config.echo_json_response(
         self, 400, "Allowlist handler: PUT Not Implemented")
Esempio n. 30
0
    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)