Exemplo n.º 1
0
def main():
    # if we are configured to auto-migrate the DB, check if there are any migrations to perform
    if config.has_option("cloud_verifier",
                         "auto_migrate_db") and config.getboolean(
                             "cloud_verifier", "auto_migrate_db"):
        keylime.cmd.migrations_apply.apply("cloud_verifier")

    cloud_verifier_tornado.main()
Exemplo n.º 2
0
def main(argv=sys.argv):
    # if we are configured to auto-migrate the DB, check if there are any migrations to perform
    if config.has_option('registrar', 'auto_migrate_db') and config.getboolean(
            'registrar', 'auto_migrate_db'):
        keylime.cmd.migrations_apply.apply('registrar')

    registrar_common.start(config.get('registrar', 'registrar_ip'),
                           config.getint('registrar', 'registrar_tls_port'),
                           config.getint('registrar', 'registrar_port'))
Exemplo n.º 3
0
def main():
    # if we are configured to auto-migrate the DB, check if there are any migrations to perform
    if config.has_option("registrar", "auto_migrate_db") and config.getboolean(
            "registrar", "auto_migrate_db"):
        keylime.cmd.migrations_apply.apply("registrar")

    registrar_common.start(
        config.get("registrar", "registrar_ip"),
        config.getint("registrar", "registrar_tls_port"),
        config.getint("registrar", "registrar_port"),
    )
Exemplo n.º 4
0
def revocation_listener():
    """
    This configures and starts the revocation listener. It is designed to be started in a separate process.
    """

    if config.has_option("cloud_agent", "listen_notifications"):
        if not config.getboolean("cloud_agent", "listen_notifications"):
            return

    # keep old typo "listen_notfications" around for a few versions
    if config.has_option("cloud_agent", "listen_notfications"):
        logger.warning(
            'Option typo "listen_notfications" is deprecated. Please use "listen_notifications" instead.'
        )
        if not config.getboolean("cloud_agent", "listen_notfications"):
            return

    secdir = secure_mount.mount()

    cert_path = config.get("cloud_agent", "revocation_cert")
    if cert_path == "default":
        cert_path = os.path.join(secdir,
                                 "unzipped/RevocationNotifier-cert.crt")
    elif cert_path[0] != "/":
        # if it is a relative, convert to absolute in work_dir
        cert_path = os.path.abspath(os.path.join(config.WORK_DIR, cert_path))

    try:
        while True:
            try:
                revocation_notifier.await_notifications(
                    perform_actions, revocation_cert_path=cert_path)
            except Exception as e:
                logger.exception(e)
                logger.warning(
                    "No connection to revocation server, retrying in 10s...")
                time.sleep(10)
    except (KeyboardInterrupt, SystemExit):
        logger.info("Stopping revocation listener...")
Exemplo n.º 5
0
def init_mtls(section='cloud_verifier', generatedir='cv_ca'):
    if not config.getboolean('general', "enable_tls"):
        logger.warning(
            "Warning: TLS is currently disabled, keys will be sent in the clear! This should only be used for testing."
        )
        return None

    logger.info("Setting up TLS...")
    my_cert = config.get(section, 'my_cert')
    ca_cert = config.get(section, 'ca_cert')
    my_priv_key = config.get(section, 'private_key')
    my_key_pw = config.get(section, 'private_key_pw')
    tls_dir = config.get(section, 'tls_dir')

    if tls_dir == 'generate':
        if my_cert != 'default' or my_priv_key != 'default' or ca_cert != 'default':
            raise Exception(
                "To use tls_dir=generate, options ca_cert, my_cert, and private_key must all be set to 'default'"
            )

        if generatedir[0] != '/':
            generatedir = os.path.abspath('%s/%s' %
                                          (config.WORK_DIR, generatedir))
        tls_dir = generatedir
        ca_path = "%s/cacert.crt" % (tls_dir)
        if os.path.exists(ca_path):
            logger.info(
                "Existing CA certificate found in %s, not generating a new one"
                % (tls_dir))
        else:
            logger.info(
                "Generating a new CA in %s and a client certificate for connecting"
                % tls_dir)
            logger.info("use keylime_ca -d %s to manage this CA" % tls_dir)
            if not os.path.exists(tls_dir):
                os.makedirs(tls_dir, 0o700)
            if my_key_pw == 'default':
                logger.warning(
                    "CAUTION: using default password for CA, please set private_key_pw to a strong password"
                )
            ca_util.setpassword(my_key_pw)
            ca_util.cmd_init(tls_dir)
            ca_util.cmd_mkcert(tls_dir, socket.gethostname())
            ca_util.cmd_mkcert(tls_dir, 'client')

    if tls_dir == 'CV':
        if section != 'registrar':
            raise Exception(
                "You only use the CV option to tls_dir for the registrar not %s"
                % section)
        tls_dir = os.path.abspath('%s/%s' % (config.WORK_DIR, 'cv_ca'))
        if not os.path.exists("%s/cacert.crt" % (tls_dir)):
            raise Exception(
                "It appears that the verifier has not yet created a CA and certificates, please run the verifier first"
            )

    # if it is relative path, convert to absolute in WORK_DIR
    if tls_dir[0] != '/':
        tls_dir = os.path.abspath('%s/%s' % (config.WORK_DIR, tls_dir))

    if ca_cert == 'default':
        ca_path = "%s/cacert.crt" % (tls_dir)
    elif not os.path.isabs(ca_cert):
        ca_path = "%s/%s" % (tls_dir, ca_cert)
    else:
        ca_path = ca_cert

    if my_cert == 'default':
        my_cert = "%s/%s-cert.crt" % (tls_dir, socket.gethostname())
    elif not os.path.isabs(my_cert):
        my_cert = "%s/%s" % (tls_dir, my_cert)
    else:
        pass

    if my_priv_key == 'default':
        my_priv_key = "%s/%s-private.pem" % (tls_dir, socket.gethostname())
    elif not os.path.isabs(my_priv_key):
        my_priv_key = "%s/%s" % (tls_dir, my_priv_key)

    try:
        context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        context.load_verify_locations(cafile=ca_path)
        context.load_cert_chain(certfile=my_cert,
                                keyfile=my_priv_key,
                                password=my_key_pw)
        if (config.has_option(section, 'check_client_cert')
                and config.getboolean(section, 'check_client_cert')):
            context.verify_mode = ssl.CERT_REQUIRED
    except ssl.SSLError as exc:
        if exc.reason == 'EE_KEY_TOO_SMALL':
            logger.error('Higher key strength is required for keylime '
                         'running on this system. If keylime is responsible '
                         'to generate the certificate, please raise the value '
                         'of configuration option [ca]cert_bits, remove '
                         'generated certificate and re-run keylime service')
        raise exc

    return context
Exemplo n.º 6
0
def main():
    """Main method of the Cloud Verifier Server.  This method is encapsulated in a function for packaging to allow it to be
    called as a function by an external program."""

    cloudverifier_port = config.get('cloud_verifier', 'cloudverifier_port')
    cloudverifier_host = config.get('cloud_verifier', 'cloudverifier_ip')

    # allow tornado's max upload size to be configurable
    max_upload_size = None
    if config.has_option('cloud_verifier', 'max_upload_size'):
        max_upload_size = int(config.get('cloud_verifier', 'max_upload_size'))

    VerfierMain.metadata.create_all(engine, checkfirst=True)
    session = get_session()
    try:
        query_all = session.query(VerfierMain).all()
        for row in query_all:
            if row.operational_state in states.APPROVED_REACTIVATE_STATES:
                row.operational_state = states.START
        session.commit()
    except SQLAlchemyError as e:
        logger.error('SQLAlchemy Error: %s', e)

    num = session.query(VerfierMain.agent_id).count()
    if num > 0:
        agent_ids = session.query(VerfierMain.agent_id).all()
        logger.info("Agent ids in db loaded from file: %s", agent_ids)

    logger.info('Starting Cloud Verifier (tornado) on port %s, use <Ctrl-C> to stop', cloudverifier_port)

    app = tornado.web.Application([
        (r"/(?:v[0-9]/)?agents/.*", AgentsHandler),
        (r"/(?:v[0-9]/)?allowlists/.*", AllowlistHandler),
        (r".*", MainHandler),
    ])

    context = cloud_verifier_common.init_mtls()

    # after TLS is up, start revocation notifier
    if config.getboolean('cloud_verifier', 'revocation_notifier'):
        logger.info("Starting service for revocation notifications on port %s", config.getint('cloud_verifier', 'revocation_notifier_port'))
        revocation_notifier.start_broker()

    sockets = tornado.netutil.bind_sockets(
        int(cloudverifier_port), address=cloudverifier_host)
    task_id = tornado.process.fork_processes(config.getint(
        'cloud_verifier', 'multiprocessing_pool_num_workers'))
    asyncio.set_event_loop(asyncio.new_event_loop())
    # Auto reactivate agent
    if task_id == 0:
        asyncio.ensure_future(activate_agents())

    server = tornado.httpserver.HTTPServer(app, ssl_options=context, max_buffer_size=max_upload_size)
    server.add_sockets(sockets)

    try:
        tornado.ioloop.IOLoop.instance().start()
    except KeyboardInterrupt:
        tornado.ioloop.IOLoop.instance().stop()
        if config.getboolean('cloud_verifier', 'revocation_notifier'):
            revocation_notifier.stop_broker()
Exemplo n.º 7
0
def main():
    for ML in [config.MEASUREDBOOT_ML, config.IMA_ML]:
        if not os.access(ML, os.F_OK):
            logger.warning(
                'Measurement list path %s not accessible by agent. Any attempt to instruct it to access this path - via "keylime_tenant" CLI - will result in agent process dying',
                ML,
            )

    ima_log_file = None
    if os.path.exists(config.IMA_ML):
        ima_log_file = open(config.IMA_ML, "r", encoding="utf-8")  # pylint: disable=consider-using-with

    tpm_log_file_data = None
    if os.path.exists(config.MEASUREDBOOT_ML):
        with open(config.MEASUREDBOOT_ML, "rb") as tpm_log_file:
            tpm_log_file_data = base64.b64encode(tpm_log_file.read())

    if config.get("cloud_agent", "agent_uuid") == "dmidecode":
        if os.getuid() != 0:
            raise RuntimeError(
                "agent_uuid is configured to use dmidecode, but current process is not running as root."
            )
        cmd = ["which", "dmidecode"]
        ret = cmd_exec.run(cmd, raiseOnError=False)
        if ret["code"] != 0:
            raise RuntimeError(
                "agent_uuid is configured to use dmidecode, but it's is not found on the system."
            )

    # initialize the tmpfs partition to store keys if it isn't already available
    secdir = secure_mount.mount()

    # Now that operations requiring root privileges are done, drop privileges
    # if 'run_as' is available in the configuration.
    if os.getuid() == 0:
        run_as = config.get("cloud_agent", "run_as", fallback="")
        if run_as != "":
            user_utils.chown(secdir, run_as)
            user_utils.change_uidgid(run_as)
            logger.info("Dropped privileges to %s", run_as)
        else:
            logger.warning(
                "Cannot drop privileges since 'run_as' is empty or missing in keylime.conf agent section."
            )

    # Instanitate TPM class

    instance_tpm = tpm()
    # get params for initialization
    registrar_ip = config.get("cloud_agent", "registrar_ip")
    registrar_port = config.get("cloud_agent", "registrar_port")

    # get params for the verifier to contact the agent
    contact_ip = os.getenv("KEYLIME_AGENT_CONTACT_IP", None)
    if contact_ip is None and config.has_option("cloud_agent",
                                                "agent_contact_ip"):
        contact_ip = config.get("cloud_agent", "agent_contact_ip")
    contact_port = os.getenv("KEYLIME_AGENT_CONTACT_PORT", None)
    if contact_port is None and config.has_option("cloud_agent",
                                                  "agent_contact_port"):
        contact_port = config.get("cloud_agent",
                                  "agent_contact_port",
                                  fallback="invalid")

    # change dir to working dir
    fs_util.ch_dir(config.WORK_DIR)

    # set a conservative general umask
    os.umask(0o077)

    # initialize tpm
    (ekcert, ek_tpm, aik_tpm) = instance_tpm.tpm_init(
        self_activate=False,
        config_pw=config.get("cloud_agent", "tpm_ownerpassword")
    )  # this tells initialize not to self activate the AIK

    # Warn if kernel version is <5.10 and another algorithm than SHA1 is used,
    # because otherwise IMA will not work
    kernel_version = tuple(platform.release().split("-")[0].split("."))
    if tuple(map(int, kernel_version)) < (
            5, 10,
            0) and instance_tpm.defaults["hash"] != algorithms.Hash.SHA1:
        logger.warning(
            "IMA attestation only works on kernel versions <5.10 with SHA1 as hash algorithm. "
            'Even if ascii_runtime_measurements shows "%s" as the '
            "algorithm, it might be just padding zeros",
            (instance_tpm.defaults["hash"]),
        )

    if ekcert is None and instance_tpm.is_emulator():
        ekcert = "emulator"

    # now we need the UUID
    try:
        agent_uuid = config.get("cloud_agent", "agent_uuid")
    except configparser.NoOptionError:
        agent_uuid = None
    if agent_uuid == "hash_ek":
        ek_pubkey = pubkey_from_tpm2b_public(base64.b64decode(ek_tpm))
        ek_pubkey_pem = ek_pubkey.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo)
        agent_uuid = hashlib.sha256(ek_pubkey_pem).hexdigest()
    elif agent_uuid == "generate" or agent_uuid is None:
        agent_uuid = str(uuid.uuid4())
    elif agent_uuid == "dmidecode":
        cmd = ["dmidecode", "-s", "system-uuid"]
        ret = cmd_exec.run(cmd)
        sys_uuid = ret["retout"][0].decode("utf-8")
        agent_uuid = sys_uuid.strip()
        try:
            uuid.UUID(agent_uuid)
        except ValueError as e:
            raise RuntimeError(  # pylint: disable=raise-missing-from
                f"The UUID returned from dmidecode is invalid: {str(e)}")
    elif agent_uuid == "hostname":
        agent_uuid = socket.getfqdn()
    elif agent_uuid == "environment":
        agent_uuid = os.getenv("KEYLIME_AGENT_UUID", None)
        if agent_uuid is None:
            raise RuntimeError(
                "Env variable KEYLIME_AGENT_UUID is empty, but agent_uuid is set to 'environment'"
            )
    elif not validators.valid_uuid(agent_uuid):
        raise RuntimeError("The UUID is not valid")

    if not validators.valid_agent_id(agent_uuid):
        raise RuntimeError(
            "The agent ID set via agent uuid parameter use invalid characters")

    logger.info("Agent UUID: %s", agent_uuid)

    serveraddr = (config.get("cloud_agent", "cloudagent_ip"),
                  config.getint("cloud_agent", "cloudagent_port"))

    keylime_ca = config.get("cloud_agent", "keylime_ca")
    if keylime_ca == "default":
        keylime_ca = os.path.join(config.WORK_DIR, "cv_ca", "cacert.crt")

    server = CloudAgentHTTPServer(serveraddr, Handler, agent_uuid, contact_ip,
                                  ima_log_file, tpm_log_file_data)
    if server.mtls_cert_enabled:
        context = web_util.generate_mtls_context(server.mtls_cert_path,
                                                 server.rsakey_path,
                                                 keylime_ca,
                                                 logger=logger)
        server.socket = context.wrap_socket(server.socket, server_side=True)
    else:
        if (not config.getboolean(
                "cloud_agent", "enable_insecure_payload", fallback=False)
                and config.get("cloud_agent", "payload_script") != ""):
            raise RuntimeError(
                "agent mTLS is disabled, while a tenant can instruct the agent to execute code on the node. "
                'In order to allow the running of the agent, "enable_insecure_payload" has to be set to "True"'
            )

    serverthread = threading.Thread(target=server.serve_forever, daemon=True)

    # register it and get back a blob
    mtls_cert = "disabled"
    if server.mtls_cert:
        mtls_cert = server.mtls_cert.public_bytes(serialization.Encoding.PEM)

    keyblob = registrar_client.doRegisterAgent(registrar_ip, registrar_port,
                                               agent_uuid, ek_tpm, ekcert,
                                               aik_tpm, mtls_cert, contact_ip,
                                               contact_port)

    if keyblob is None:
        instance_tpm.flush_keys()
        raise Exception("Registration failed")

    # get the ephemeral registrar key
    key = instance_tpm.activate_identity(keyblob)

    if key is None:
        instance_tpm.flush_keys()
        raise Exception("Activation failed")

    # tell the registrar server we know the key
    retval = registrar_client.doActivateAgent(registrar_ip, registrar_port,
                                              agent_uuid, key)

    if not retval:
        instance_tpm.flush_keys()
        raise Exception("Registration failed on activate")

    # Start revocation listener in a new process to not interfere with tornado
    revocation_process = multiprocessing.Process(target=revocation_listener,
                                                 daemon=True)
    revocation_process.start()

    logger.info(
        "Starting Cloud Agent on %s:%s with API version %s. Use <Ctrl-C> to stop",
        serveraddr[0],
        serveraddr[1],
        keylime_api_version.current_version(),
    )
    serverthread.start()

    def shutdown_handler(*_):
        logger.info("TERM Signal received, shutting down...")
        logger.debug("Stopping revocation notifier...")
        revocation_process.terminate()
        logger.debug("Shutting down HTTP server...")
        server.shutdown()
        server.server_close()
        serverthread.join()
        logger.debug("HTTP server stopped...")
        revocation_process.join()
        logger.debug("Revocation notifier stopped...")
        secure_mount.umount()
        logger.debug("Umounting directories...")
        instance_tpm.flush_keys()
        logger.debug("Flushed keys successfully")
        sys.exit(0)

    signal.signal(signal.SIGTERM, shutdown_handler)
    signal.signal(signal.SIGQUIT, shutdown_handler)
    signal.signal(signal.SIGINT, shutdown_handler)

    # Keep the main thread alive by waiting for the server thread
    serverthread.join()
Exemplo n.º 8
0
def main():
    for ML in [config.MEASUREDBOOT_ML, config.IMA_ML]:
        if not os.access(ML, os.F_OK):
            logger.warning(
                "Measurement list path %s not accessible by agent. Any attempt to instruct it to access this path - via \"keylime_tenant\" CLI - will result in agent process dying",
                ML)

    ima_log_file = None
    if os.path.exists(config.IMA_ML):
        ima_log_file = open(config.IMA_ML, 'r', encoding="utf-8")

    tpm_log_file_data = None
    if os.path.exists(config.MEASUREDBOOT_ML):
        with open(config.MEASUREDBOOT_ML, 'rb') as tpm_log_file:
            tpm_log_file_data = base64.b64encode(tpm_log_file.read())

    if config.get('cloud_agent', 'agent_uuid') == 'dmidecode':
        if os.getuid() != 0:
            raise RuntimeError('agent_uuid is configured to use dmidecode, '
                               'but current process is not running as root.')
        cmd = ['which', 'dmidecode']
        ret = cmd_exec.run(cmd, raiseOnError=False)
        if ret['code'] != 0:
            raise RuntimeError('agent_uuid is configured to use dmidecode, '
                               'but it\'s is not found on the system.')

    # initialize the tmpfs partition to store keys if it isn't already available
    secdir = secure_mount.mount()

    # Now that operations requiring root privileges are done, drop privileges
    # if 'run_as' is available in the configuration.
    if os.getuid() == 0:
        run_as = config.get('cloud_agent', 'run_as', fallback='')
        if run_as != '':
            user_utils.chown(secdir, run_as)
            user_utils.change_uidgid(run_as)
            logger.info(f"Dropped privileges to {run_as}")
        else:
            logger.warning(
                "Cannot drop privileges since 'run_as' is empty or missing in keylime.conf agent section."
            )

    # Instanitate TPM class

    instance_tpm = tpm()
    # get params for initialization
    registrar_ip = config.get('cloud_agent', 'registrar_ip')
    registrar_port = config.get('cloud_agent', 'registrar_port')

    # get params for the verifier to contact the agent
    contact_ip = os.getenv("KEYLIME_AGENT_CONTACT_IP", None)
    if contact_ip is None and config.has_option('cloud_agent',
                                                'agent_contact_ip'):
        contact_ip = config.get('cloud_agent', 'agent_contact_ip')
    contact_port = os.getenv("KEYLIME_AGENT_CONTACT_PORT", None)
    if contact_port is None and config.has_option('cloud_agent',
                                                  'agent_contact_port'):
        contact_port = config.get('cloud_agent',
                                  'agent_contact_port',
                                  fallback="invalid")

    # change dir to working dir
    fs_util.ch_dir(config.WORK_DIR)

    # set a conservative general umask
    os.umask(0o077)

    # initialize tpm
    (ekcert, ek_tpm, aik_tpm) = instance_tpm.tpm_init(
        self_activate=False,
        config_pw=config.get('cloud_agent', 'tpm_ownerpassword')
    )  # this tells initialize not to self activate the AIK
    virtual_agent = instance_tpm.is_vtpm()

    # Warn if kernel version is <5.10 and another algorithm than SHA1 is used,
    # because otherwise IMA will not work
    kernel_version = tuple(platform.release().split("-")[0].split("."))
    if tuple(map(int, kernel_version)) < (
            5, 10,
            0) and instance_tpm.defaults["hash"] != algorithms.Hash.SHA1:
        logger.warning(
            "IMA attestation only works on kernel versions <5.10 with SHA1 as hash algorithm. "
            "Even if ascii_runtime_measurements shows \"%s\" as the "
            "algorithm, it might be just padding zeros",
            (instance_tpm.defaults["hash"]))

    if ekcert is None:
        if virtual_agent:
            ekcert = 'virtual'
        elif instance_tpm.is_emulator():
            ekcert = 'emulator'

    # now we need the UUID
    try:
        agent_uuid = config.get('cloud_agent', 'agent_uuid')
    except configparser.NoOptionError:
        agent_uuid = None
    if agent_uuid == 'hash_ek':
        ek_pubkey = pubkey_from_tpm2b_public(base64.b64decode(ek_tpm))
        ek_pubkey_pem = ek_pubkey.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo)
        agent_uuid = hashlib.sha256(ek_pubkey_pem).hexdigest()
    elif agent_uuid == 'generate' or agent_uuid is None:
        agent_uuid = str(uuid.uuid4())
    elif agent_uuid == 'dmidecode':
        cmd = ['dmidecode', '-s', 'system-uuid']
        ret = cmd_exec.run(cmd)
        sys_uuid = ret['retout'][0].decode('utf-8')
        agent_uuid = sys_uuid.strip()
        try:
            uuid.UUID(agent_uuid)
        except ValueError as e:
            raise RuntimeError(
                "The UUID returned from dmidecode is invalid: %s" % e)  # pylint: disable=raise-missing-from
    elif agent_uuid == 'hostname':
        agent_uuid = socket.getfqdn()
    elif agent_uuid == 'environment':
        agent_uuid = os.getenv("KEYLIME_AGENT_UUID", None)
        if agent_uuid is None:
            raise RuntimeError(
                "Env variable KEYLIME_AGENT_UUID is empty, but agent_uuid is set to 'environment'"
            )
    elif not validators.valid_uuid(agent_uuid):
        raise RuntimeError("The UUID is not valid")

    if not validators.valid_agent_id(agent_uuid):
        raise RuntimeError(
            "The agent ID set via agent uuid parameter use invalid characters")

    if config.STUB_VTPM and config.TPM_CANNED_VALUES is not None:
        # Use canned values for stubbing
        jsonIn = config.TPM_CANNED_VALUES
        if "add_vtpm_to_group" in jsonIn:
            # The value we're looking for has been canned!
            agent_uuid = jsonIn['add_vtpm_to_group']['retout']
        else:
            # Our command hasn't been canned!
            raise Exception("Command %s not found in canned json!" %
                            ("add_vtpm_to_group"))

    logger.info("Agent UUID: %s", agent_uuid)

    serveraddr = (config.get('cloud_agent', 'cloudagent_ip'),
                  config.getint('cloud_agent', 'cloudagent_port'))

    keylime_ca = config.get('cloud_agent', 'keylime_ca')
    if keylime_ca == "default":
        keylime_ca = os.path.join(config.WORK_DIR, 'cv_ca', 'cacert.crt')

    server = CloudAgentHTTPServer(serveraddr, Handler, agent_uuid, contact_ip,
                                  ima_log_file, tpm_log_file_data)
    context = web_util.generate_mtls_context(server.mtls_cert_path,
                                             server.rsakey_path,
                                             keylime_ca,
                                             logger=logger)
    server.socket = context.wrap_socket(server.socket, server_side=True)
    serverthread = threading.Thread(target=server.serve_forever, daemon=True)

    # register it and get back a blob
    mtls_cert = server.mtls_cert.public_bytes(serialization.Encoding.PEM)
    keyblob = registrar_client.doRegisterAgent(registrar_ip, registrar_port,
                                               agent_uuid, ek_tpm, ekcert,
                                               aik_tpm, mtls_cert, contact_ip,
                                               contact_port)

    if keyblob is None:
        instance_tpm.flush_keys()
        raise Exception("Registration failed")

    # get the ephemeral registrar key
    key = instance_tpm.activate_identity(keyblob)

    if key is None:
        instance_tpm.flush_keys()
        raise Exception("Activation failed")

    # tell the registrar server we know the key
    retval = registrar_client.doActivateAgent(registrar_ip, registrar_port,
                                              agent_uuid, key)

    if not retval:
        instance_tpm.flush_keys()
        raise Exception("Registration failed on activate")

    # Start revocation listener in a new process to not interfere with tornado
    revocation_process = multiprocessing.Process(target=revocation_listener,
                                                 daemon=True)
    revocation_process.start()

    logger.info(
        "Starting Cloud Agent on %s:%s with API version %s. Use <Ctrl-C> to stop",
        serveraddr[0], serveraddr[1], keylime_api_version.current_version())
    serverthread.start()

    def shutdown_handler(*_):
        logger.info("TERM Signal received, shutting down...")
        logger.debug("Stopping revocation notifier...")
        revocation_process.terminate()
        logger.debug("Shutting down HTTP server...")
        server.shutdown()
        server.server_close()
        serverthread.join()
        logger.debug("HTTP server stopped...")
        revocation_process.join()
        logger.debug("Revocation notifier stopped...")
        secure_mount.umount()
        logger.debug("Umounting directories...")
        instance_tpm.flush_keys()
        logger.debug("Flushed keys successfully")
        sys.exit(0)

    signal.signal(signal.SIGTERM, shutdown_handler)
    signal.signal(signal.SIGQUIT, shutdown_handler)
    signal.signal(signal.SIGINT, shutdown_handler)

    # Keep the main thread alive by waiting for the server thread
    serverthread.join()
Exemplo n.º 9
0
def revocation_listener():
    """
    This configures and starts the revocation listener. It is designed to be started in a separate process.
    """

    if config.has_option('cloud_agent', 'listen_notifications'):
        if not config.getboolean('cloud_agent', 'listen_notifications'):
            return

    # keep old typo "listen_notfications" around for a few versions
    if config.has_option('cloud_agent', 'listen_notfications'):
        logger.warning(
            'Option typo "listen_notfications" is deprecated. Please use "listen_notifications" instead.'
        )
        if not config.getboolean('cloud_agent', 'listen_notfications'):
            return

    secdir = secure_mount.mount()

    cert_path = config.get('cloud_agent', 'revocation_cert')
    if cert_path == "default":
        cert_path = os.path.join(secdir,
                                 "unzipped/RevocationNotifier-cert.crt")
    elif cert_path[0] != '/':
        # if it is a relative, convert to absolute in work_dir
        cert_path = os.path.abspath(os.path.join(config.WORK_DIR, cert_path))

    # Callback function handling the revocations
    def perform_actions(revocation):
        actionlist = []

        # load the actions from inside the keylime module
        actionlisttxt = config.get('cloud_agent', 'revocation_actions')
        if actionlisttxt.strip() != "":
            actionlist = actionlisttxt.split(',')
            actionlist = ["revocation_actions.%s" % i for i in actionlist]

        # load actions from unzipped
        action_list_path = os.path.join(secdir, "unzipped/action_list")
        if os.path.exists(action_list_path):
            with open(action_list_path, encoding="utf-8") as f:
                actionlisttxt = f.read()
            if actionlisttxt.strip() != "":
                localactions = actionlisttxt.strip().split(',')
                for action in localactions:
                    if not action.startswith('local_action_'):
                        logger.warning(
                            "Invalid local action: %s. Must start with local_action_",
                            action)
                    else:
                        actionlist.append(action)

                uzpath = "%s/unzipped" % secdir
                if uzpath not in sys.path:
                    sys.path.append(uzpath)

        for action in actionlist:
            logger.info("Executing revocation action %s", action)
            try:
                module = importlib.import_module(action)
                execute = getattr(module, 'execute')
                asyncio.get_event_loop().run_until_complete(
                    execute(revocation))
            except Exception as e:
                logger.warning(
                    "Exception during execution of revocation action %s: %s",
                    action, e)

    try:
        while True:
            try:
                revocation_notifier.await_notifications(
                    perform_actions, revocation_cert_path=cert_path)
            except Exception as e:
                logger.exception(e)
                logger.warning(
                    "No connection to revocation server, retrying in 10s...")
                time.sleep(10)
    except (KeyboardInterrupt, SystemExit):
        logger.info("Stopping revocation listener...")
Exemplo n.º 10
0
def init_mtls(section="cloud_verifier", generatedir="cv_ca", logger=None):
    """
    Generates mTLS SSLContext for either the cloud verifier or the registrar.
    """

    if not config.getboolean("general", "enable_tls"):
        logger.warning(
            "Warning: TLS is currently disabled, keys will be sent in the clear! This should only be used for testing."
        )
        return None

    if logger:
        logger.info("Setting up TLS...")
    my_cert = config.get(section, "my_cert")
    ca_cert = config.get(section, "ca_cert")
    my_priv_key = config.get(section, "private_key")
    my_key_pw = config.get(section, "private_key_pw")
    tls_dir = config.get(section, "tls_dir")

    if tls_dir == "generate":
        if my_cert != "default" or my_priv_key != "default" or ca_cert != "default":
            raise Exception(
                "To use tls_dir=generate, options ca_cert, my_cert, and private_key must all be set to 'default'"
            )

        if generatedir[0] != "/":
            generatedir = os.path.abspath(
                os.path.join(config.WORK_DIR, generatedir))
        tls_dir = generatedir
        ca_path = os.path.join(tls_dir, "cacert.crt")
        if os.path.exists(ca_path):
            if logger:
                logger.info(
                    "Existing CA certificate found in %s, not generating a new one",
                    tls_dir)
        else:
            if logger:
                logger.info(
                    "Generating a new CA in %s and a client certificate for connecting",
                    tls_dir)
                logger.info("use keylime_ca -d %s to manage this CA", tls_dir)
            if not os.path.exists(tls_dir):
                os.makedirs(tls_dir, 0o700)
            if my_key_pw == "default":
                if logger:
                    logger.warning(
                        "CAUTION: using default password for CA, please set private_key_pw to a strong password"
                    )
            ca_util.setpassword(my_key_pw)
            ca_util.cmd_init(tls_dir)
            ca_util.cmd_mkcert(tls_dir, socket.gethostname())
            ca_util.cmd_mkcert(tls_dir, "client")

    if tls_dir == "CV":
        if section != "registrar":
            raise Exception(
                f"You only use the CV option to tls_dir for the registrar not {section}"
            )
        tls_dir = os.path.abspath(os.path.join(config.WORK_DIR, "cv_ca"))
        if not os.path.exists(os.path.join(tls_dir, "cacert.crt")):
            raise Exception(
                "It appears that the verifier has not yet created a CA and certificates, please run the verifier first"
            )

    # if it is relative path, convert to absolute in WORK_DIR
    if tls_dir[0] != "/":
        tls_dir = os.path.abspath(os.path.join(config.WORK_DIR, tls_dir))

    if ca_cert == "default":
        ca_path = os.path.join(tls_dir, "cacert.crt")
    elif not os.path.isabs(ca_cert):
        ca_path = os.path.join(tls_dir, ca_cert)
    else:
        ca_path = ca_cert

    if my_cert == "default":
        my_cert = os.path.join(tls_dir, f"{socket.gethostname()}-cert.crt")
    elif not os.path.isabs(my_cert):
        my_cert = os.path.join(tls_dir, my_cert)
    else:
        pass

    if my_priv_key == "default":
        my_priv_key = os.path.join(tls_dir,
                                   f"{socket.gethostname()}-private.pem")
    elif not os.path.isabs(my_priv_key):
        my_priv_key = os.path.join(tls_dir, my_priv_key)

    check_client_cert = config.has_option(
        section, "check_client_cert") and config.getboolean(
            section, "check_client_cert")
    context = generate_mtls_context(my_cert,
                                    my_priv_key,
                                    ca_path,
                                    check_client_cert,
                                    my_key_pw,
                                    logger=logger)

    return context, (my_cert, my_priv_key, my_key_pw)
Exemplo n.º 11
0
def main():
    for ML in [ config.MEASUREDBOOT_ML, config.IMA_ML ] :
        if not os.access(ML, os.F_OK) :
            logger.warning("Measurement list path %s not accessible by agent. Any attempt to instruct it to access this path - via \"keylime_tenant\" CLI - will result in agent process dying", ML)

    if config.get('cloud_agent', 'agent_uuid') == 'dmidecode':
        if os.getuid() != 0:
            raise RuntimeError('agent_uuid is configured to use dmidecode, '
                               'but current process is not running as root.')
        cmd = ['which', 'dmidecode']
        ret = cmd_exec.run(cmd, raiseOnError=False)
        if ret['code'] != 0:
            raise RuntimeError('agent_uuid is configured to use dmidecode, '
                               'but it\'s is not found on the system.')

    # Instanitate TPM class

    instance_tpm = tpm()
    # get params for initialization
    registrar_ip = config.get('cloud_agent', 'registrar_ip')
    registrar_port = config.get('cloud_agent', 'registrar_port')

    # get params for the verifier to contact the agent
    contact_ip = os.getenv("KEYLIME_AGENT_CONTACT_IP", None)
    if contact_ip is None and config.has_option('cloud_agent', 'agent_contact_ip'):
        contact_ip = config.get('cloud_agent', 'agent_contact_ip')
    contact_port = os.getenv("KEYLIME_AGENT_CONTACT_PORT", None)
    if contact_port is None and config.has_option('cloud_agent', 'agent_contact_port'):
        contact_port = config.get('cloud_agent', 'agent_contact_port', fallback="invalid")

    # initialize the tmpfs partition to store keys if it isn't already available
    secdir = secure_mount.mount()

    # change dir to working dir
    config.ch_dir(config.WORK_DIR, logger)

    # initialize tpm
    (ekcert, ek_tpm, aik_tpm) = instance_tpm.tpm_init(self_activate=False, config_pw=config.get(
        'cloud_agent', 'tpm_ownerpassword'))  # this tells initialize not to self activate the AIK
    virtual_agent = instance_tpm.is_vtpm()

    if ekcert is None:
        if virtual_agent:
            ekcert = 'virtual'
        elif instance_tpm.is_emulator():
            ekcert = 'emulator'

    # now we need the UUID
    try:
        agent_uuid = config.get('cloud_agent', 'agent_uuid')
    except configparser.NoOptionError:
        agent_uuid = None
    if agent_uuid == 'openstack':
        agent_uuid = openstack.get_openstack_uuid()
    elif agent_uuid == 'hash_ek':
        agent_uuid = hashlib.sha256(ek_tpm).hexdigest()
    elif agent_uuid == 'generate' or agent_uuid is None:
        agent_uuid = str(uuid.uuid4())
    elif agent_uuid == 'dmidecode':
        cmd = ['dmidecode', '-s', 'system-uuid']
        ret = cmd_exec.run(cmd)
        sys_uuid = ret['retout'][0].decode('utf-8')
        agent_uuid = sys_uuid.strip()
        try:
            uuid.UUID(agent_uuid)
        except ValueError as e:
            raise RuntimeError("The UUID returned from dmidecode is invalid: %s" % e)  # pylint: disable=raise-missing-from
    elif agent_uuid == 'hostname':
        agent_uuid = socket.getfqdn()
    if config.STUB_VTPM and config.TPM_CANNED_VALUES is not None:
        # Use canned values for stubbing
        jsonIn = config.TPM_CANNED_VALUES
        if "add_vtpm_to_group" in jsonIn:
            # The value we're looking for has been canned!
            agent_uuid = jsonIn['add_vtpm_to_group']['retout']
        else:
            # Our command hasn't been canned!
            raise Exception("Command %s not found in canned json!" %
                            ("add_vtpm_to_group"))

    logger.info("Agent UUID: %s", agent_uuid)

    # register it and get back a blob
    keyblob = registrar_client.doRegisterAgent(
        registrar_ip, registrar_port, agent_uuid, ek_tpm, ekcert, aik_tpm, contact_ip, contact_port)

    if keyblob is None:
        instance_tpm.flush_keys()
        raise Exception("Registration failed")

    # get the ephemeral registrar key
    key = instance_tpm.activate_identity(keyblob)

    if key is None:
        instance_tpm.flush_keys()
        raise Exception("Activation failed")

    # tell the registrar server we know the key
    retval = False
    retval = registrar_client.doActivateAgent(
        registrar_ip, registrar_port, agent_uuid, key)

    if not retval:
        instance_tpm.flush_keys()
        raise Exception("Registration failed on activate")

    serveraddr = (config.get('cloud_agent', 'cloudagent_ip'),
                  config.getint('cloud_agent', 'cloudagent_port'))
    server = CloudAgentHTTPServer(serveraddr, Handler, agent_uuid)
    serverthread = threading.Thread(target=server.serve_forever)

    logger.info("Starting Cloud Agent on %s:%s with API version %s. Use <Ctrl-C> to stop", serveraddr[0], serveraddr[1], keylime_api_version.current_version())
    serverthread.start()

    # want to listen for revocations?
    if config.getboolean('cloud_agent', 'listen_notfications'):
        cert_path = config.get('cloud_agent', 'revocation_cert')
        if cert_path == "default":
            cert_path = os.path.join(secdir,
                                      "unzipped/RevocationNotifier-cert.crt")
        elif cert_path[0] != '/':
            # if it is a relative, convert to absolute in work_dir
            cert_path = os.path.abspath(
                os.path.join(config.WORK_DIR, cert_path))

        def perform_actions(revocation):
            actionlist = []

            # load the actions from inside the keylime module
            actionlisttxt = config.get('cloud_agent', 'revocation_actions')
            if actionlisttxt.strip() != "":
                actionlist = actionlisttxt.split(',')
                actionlist = ["revocation_actions.%s" % i for i in actionlist]

            # load actions from unzipped
            action_list_path = os.path.join(secdir, "unzipped/action_list")
            if os.path.exists(action_list_path):
                with open(action_list_path, encoding="utf-8") as f:
                    actionlisttxt = f.read()
                if actionlisttxt.strip() != "":
                    localactions = actionlisttxt.strip().split(',')
                    for action in localactions:
                        if not action.startswith('local_action_'):
                            logger.warning("Invalid local action: %s. Must start with local_action_", action)
                        else:
                            actionlist.append(action)

                    uzpath = "%s/unzipped" % secdir
                    if uzpath not in sys.path:
                        sys.path.append(uzpath)

            for action in actionlist:
                logger.info("Executing revocation action %s", action)
                try:
                    module = importlib.import_module(action)
                    execute = getattr(module, 'execute')
                    asyncio.get_event_loop().run_until_complete(execute(revocation))
                except Exception as e:
                    logger.warning("Exception during execution of revocation action %s: %s", action, e)
        try:
            while True:
                try:
                    revocation_notifier.await_notifications(
                        perform_actions, revocation_cert_path=cert_path)
                except Exception as e:
                    logger.exception(e)
                    logger.warning("No connection to revocation server, retrying in 10s...")
                    time.sleep(10)
        except KeyboardInterrupt:
            logger.info("TERM Signal received, shutting down...")
            instance_tpm.flush_keys()
            server.shutdown()
    else:
        try:
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            logger.info("TERM Signal received, shutting down...")
            instance_tpm.flush_keys()
            server.shutdown()