Пример #1
0
def convert_crl_to_pem(derfile, pemfile):
    if config.get('general', 'ca_implementation') == 'openssl':
        with open(pemfile, 'w', encoding="utf-8") as f:
            f.write("")
    else:
        cmd = ('openssl', 'crl', '-in', derfile, '-inform', 'der',
               '-out', pemfile)
        cmd_exec.run(cmd)
Пример #2
0
def convert_crl_to_pem(derfile, pemfile):
    if config.get('general', 'ca_implementation') == 'openssl':
        with open(pemfile, 'w') as f:
            f.write("")
    else:
        cmd_exec.run("openssl crl -in %s -inform der -out %s" %
                     (derfile, pemfile),
                     lock=False)
Пример #3
0
async def execute(revocation):
    json_meta = json.loads(revocation['meta_data'])
    serial = json_meta['cert_serial']
    subject = json_meta['subject']
    if revocation.get(
            'type', None) != 'revocation' or serial is None or subject is None:
        logger.error("Unsupported revocation message: %s" % revocation)

    # import the crl into NSS
    secdir = secure_mount.mount()
    logger.info("loading updated CRL from %s/unzipped/cacrl.der into NSS" %
                secdir)
    cmd_exec.run("crlutil -I -i %s/unzipped/cacrl.der -d sql:/etc/ipsec.d" %
                 secdir,
                 lock=False)

    # need to find any sa's that were established with that cert subject name
    output = cmd_exec.run("ipsec whack --trafficstatus",
                          lock=False,
                          raiseOnError=True)['retout']
    deletelist = set()
    id = ""
    for line in output:
        line = line.strip()
        try:
            idstart = line.index("id='") + 4
            idend = line[idstart:].index('\'')

            id = line[idstart:idstart + idend]

            privatestart = line.index("private#") + 8
            privateend = line[privatestart:].index("/")

            ip = line[privatestart:privatestart + privateend]
        except ValueError:
            # weirdly formatted line
            continue

    # kill all the commas
    id = id.replace(",", "")
    cursubj = {}
    for token in id.split():
        cur = token.split('=')
        cursubj[cur[0]] = cur[1]

    cert = {}
    for token in subject[1:].split("/"):
        cur = token.split('=')
        cert[cur[0]] = cur[1]

    if cert == cursubj:
        deletelist.add(ip)

    for todelete in deletelist:
        logger.info("deleting IPsec sa with %s" % todelete)
        cmd_exec.run("ipsec whack --crash %s" % todelete,
                     raiseOnError=False,
                     lock=False)
Пример #4
0
    def __run(self, cmd, expectedcode=tpm_abstract.AbstractTPM.EXIT_SUCESS, raiseOnError=True, lock=True, outputpaths=None):
        env = _get_cmd_env()

        # Backwards compat with string input (force all to be dict)
        if isinstance(outputpaths, str):
            outputpaths = [outputpaths]

        # Handle stubbing the TPM out
        fprt = tpm1.__fingerprint(cmd)
        if config.STUB_TPM and config.TPM_CANNED_VALUES is not None:
            stub = _stub_command(fprt, lock, outputpaths)
            if stub:
                return stub

        numtries = 0
        while True:
            if lock:
                with self.tpmutilLock:
                    retDict = cmd_exec.run(
                        cmd=cmd, expectedcode=expectedcode,
                        raiseOnError=False, outputpaths=outputpaths, env=env)
            else:
                retDict = cmd_exec.run(
                    cmd=cmd, expectedcode=expectedcode, raiseOnError=False,
                    outputpaths=outputpaths, env=env)

            code = retDict['code']
            retout = retDict['retout']

            # keep trying to communicate with TPM if there was an I/O error
            if code == tpm_abstract.AbstractTPM.TPM_IO_ERR:
                numtries += 1
                maxr = config.getint('cloud_agent', 'max_retries')
                if numtries >= maxr:
                    logger.error("TPM appears to be in use by another application.  Keylime is incompatible with other TPM TSS applications like trousers/tpm-tools. Please uninstall or disable.")
                    break
                retry = config.getfloat('cloud_agent', 'retry_interval')
                logger.info("Failed to call TPM %d/%d times, trying again in %f seconds..." % (numtries, maxr, retry))
                time.sleep(retry)
                continue

            break

        # Don't bother continuing if TPM call failed and we're raising on error
        if code != expectedcode and raiseOnError:
            raise Exception("Command: %s returned %d, expected %d, output %s" % (cmd, code, expectedcode, retout))

        # Metric output
        if lock or self.tpmutilLock.locked():
            _output_metrics(fprt, cmd, retDict, outputpaths)

        return retDict
Пример #5
0
def umount():
    """Umount all the devices mounted by Keylime."""

    # Make sure we leave tmpfs dir empty even if we did not mount it or
    # if we cannot unmount it. Ignore errors while deleting. The deletion
    # of the 'secure' directory will result in an error since it's a mount point.
    # Also, with config.MOUNT_SECURE being False we remove the directory
    secdir = get_secdir()
    if not config.MOUNT_SECURE or check_mounted(secdir):
        shutil.rmtree(secdir, ignore_errors=True)

    while _MOUNTED:
        directory = _MOUNTED.pop()
        logger.info("Unmounting %s", directory)
        if check_mounted(directory):
            cmd = ("umount", directory)
            ret = cmd_exec.run(cmd, raiseOnError=False)
            if ret["code"] != 0:
                logger.error(
                    "%s cannot be umounted. A running process can be keeping it bussy: %s",
                    directory,
                    str(ret["reterr"]),
                )
        else:
            logger.warning("%s already unmounted by another process",
                           directory)
Пример #6
0
def check_mounted(secdir):
    whatsmounted = cmd_exec.run("mount")['retout']
    whatsmounted_converted = config.convert(whatsmounted)
    for line in whatsmounted_converted:
        tokens = line.split()
        tmpfs = False
        if len(tokens) < 3:
            continue
        if tokens[0] == 'tmpfs':
            tmpfs = True
        if tokens[2] == secdir:
            if not tmpfs:
                logger.error(
                    "secure storage location %s already mounted "
                    "on wrong file system type: %s.  Unmount to"
                    "continue.", secdir, tokens[0])
                raise Exception(
                    f"secure storage location {secdir} already mounted on "
                    f"wrong file system type: {tokens[0]}.  Unmount to "
                    f"continue.")

            logger.debug(
                "secure storage location %s already mounted on tmpfs" % secdir)
            return True
    logger.debug("secure storage location %s not mounted " % secdir)
    return False
Пример #7
0
        def check_expiration():
            logger.info("checking CRL for expiration every hour")
            while True:  # pylint: disable=R1702
                try:
                    if (os.path.exists('cacrl.der') and
                            os.stat('cacrl.der').st_size):
                        cmd = ('openssl', 'crl', '-inform', 'der', '-in',
                               'cacrl.der', '-text', '-noout')
                        retout = cmd_exec.run(cmd)['retout']
                        for line in retout:
                            line = line.strip()
                            if line.startswith(b"Next Update:"):
                                expire = datetime.datetime.strptime(
                                    line[13:].decode('utf-8'), "%b %d %H:%M:%S %Y %Z")
                                # check expiration within 6 hours
                                in1hour = datetime.datetime.utcnow() + datetime.timedelta(hours=6)
                                if expire <= in1hour:
                                    logger.info(
                                        "Certificate to expire soon %s, re-issuing", expire)
                                    cmd_regencrl(workingdir)
                    # check a little less than every hour
                    time.sleep(3540)

                except KeyboardInterrupt:
                    logger.info("TERM Signal received, shutting down...")
                    # server.shutdown()
                    break
Пример #8
0
        def check_expiration():
            logger.info("checking CRL for expiration every hour")
            while True:
                try:
                    if os.path.exists('cacrl.der'):
                        retout = cmd_exec.run(
                            "openssl crl -inform der -in cacrl.der -text -noout",
                            lock=False)['retout']
                        for line in retout:
                            line = line.strip()
                            if line.startswith(b"Next Update:"):
                                expire = datetime.datetime.strptime(
                                    line[13:].decode('utf-8'),
                                    "%b %d %H:%M:%S %Y %Z")
                                # check expiration within 6 hours
                                in1hour = datetime.datetime.utcnow(
                                ) + datetime.timedelta(hours=6)
                                if expire <= in1hour:
                                    logger.info(
                                        "Certificate to expire soon %s, re-issuing"
                                        % expire)
                                    cmd_regencrl(workingdir)
                    # check a little less than every hour
                    time.sleep(3540)

                except KeyboardInterrupt:
                    logger.info("TERM Signal received, shutting down...")
                    #server.shutdown()
                    break
Пример #9
0
def mount():
    secdir = get_secdir()

    if not config.MOUNT_SECURE:
        if not os.path.isdir(secdir):
            os.makedirs(secdir)
        return secdir

    if not check_mounted(secdir):
        # ok now we know it isn't already mounted, go ahead and create and mount
        if not os.path.exists(secdir):
            os.makedirs(secdir, 0o700)
        size = config.get("cloud_agent", "secure_size")
        logger.info("mounting secure storage location %s on tmpfs", secdir)
        cmd = ("mount", "-t", "tmpfs", "-o", f"size={size},mode=0700", "tmpfs", secdir)
        cmd_exec.run(cmd)
        _MOUNTED.append(secdir)

    return secdir
Пример #10
0
def mount():
    secdir = os.path.join(config.WORK_DIR, "secure")

    if not config.MOUNT_SECURE:
        secdir = os.path.join(config.WORK_DIR, "tmpfs-dev")
        if not os.path.isdir(secdir):
            os.makedirs(secdir)
        return secdir

    if not check_mounted(secdir):
        # ok now we know it isn't already mounted, go ahead and create and mount
        if not os.path.exists(secdir):
            os.makedirs(secdir, 0o700)
        size = config.get('cloud_agent', 'secure_size')
        logger.info("mounting secure storage location %s on tmpfs", secdir)
        cmd = ('mount', '-t', 'tmpfs', '-o', f'size={size},mode=0700', 'tmpfs',
               secdir)
        cmd_exec.run(cmd)
        _MOUNTED.append(secdir)

    return secdir
Пример #11
0
def mount():
    secdir = config.WORK_DIR + "/secure"

    if not config.MOUNT_SECURE:
        secdir = config.WORK_DIR + "/tmpfs-dev"
        if not os.path.isdir(secdir):
            os.makedirs(secdir)
        return secdir

    if not check_mounted(secdir):
        # ok now we know it isn't already mounted, go ahead and create and mount
        if not os.path.exists(secdir):
            os.makedirs(secdir, 0o700)
        config.chownroot(secdir, logger)
        size = config.get('cloud_agent', 'secure_size')
        logger.info("mounting secure storage location %s on tmpfs" % secdir)
        cmd = ('mount', '-t', 'tmpfs', '-o', 'size=%s,mode=0700' % size,
               'tmpfs', secdir)
        cmd_exec.run(cmd, lock=False)

    return secdir
async def execute(revocation):
    serial = revocation.get("metadata",{}).get("cert_serial",None)
    if revocation.get('type',None) != 'revocation' or serial is None:
        logger.error("Unsupported revocation message: %s"%revocation)

    # load up the ca cert
    secdir = secure_mount.mount()
    ca = X509.load_cert('%s/unzipped/cacert.crt'%secdir)

    # need to find any sa's that were established with that cert serial
    output = cmd_exec.run("racoonctl show-sa ipsec",lock=False,raiseOnError=True)['retout']
    deletelist=set()
    for line in output:
        if not line.startswith(b"\t"):
            certder = cmd_exec.run("racoonctl get-cert inet %s"%line.strip(),raiseOnError=False,lock=False)['retout']
            if len(certder)==0:
                continue;
            certobj = X509.load_cert_der_string(b''.join(certder))

            # check that CA is the same.  the strip indexing bit is to remove the stuff around it 'keyid:THEACTUALKEYID\n'
            if ca.get_ext('subjectKeyIdentifier').get_value() != certobj.get_ext('authorityKeyIdentifier').get_value().strip()[6:]:
                continue

            if certobj.get_serial_number() == serial:
                deletelist.add(line.strip())

    for todelete in deletelist:
        logger.info("deleting IPsec sa between %s"%todelete)
        cmd_exec.run("racoonctl delete-sa isakmp inet %s"%todelete,lock=False)
        tokens = todelete.split()
        cmd_exec.run("racoonctl delete-sa isakmp inet %s %s"%(tokens[1],tokens[0]),lock=False)
Пример #13
0
def umount():
    """Umount all the devices mounted by Keylime."""
    while _MOUNTED:
        directory = _MOUNTED.pop()
        logger.info("Unmounting %s", directory)
        if check_mounted(directory):
            cmd = ("umount", directory)
            ret = cmd_exec.run(cmd, raiseOnError=False)
            if ret["code"] != 0:
                logger.error(
                    "%s cannot be umounted. "
                    "A running process can be keeping it bussy: %s", directory,
                    str(ret["reterr"]))
        else:
            logger.warning("%s already unmounted by another process",
                           directory)
Пример #14
0
async def execute(revocation):
    json_meta = json.loads(revocation["meta_data"])
    serial = json_meta["cert_serial"]
    if revocation.get("type", None) != "revocation" or serial is None:
        logger.error("Unsupported revocation message: %s" % revocation)

    # load up the ca cert
    secdir = secure_mount.mount()
    ca = ca_util.load_cert_by_path(f"{secdir}/unzipped/cacert.crt")

    # need to find any sa's that were established with that cert serial
    cmd = ("racoonctl", "show-sa", "ipsec")
    output = cmd_exec.run(cmd, raiseOnError=True)["retout"]
    deletelist = set()
    for line in output:
        if not line.startswith(b"\t"):
            cmd = ("racoonctl", "get-cert", "inet", line.strip())
            certder = cmd_exec.run(cmd, raiseOnError=False)["retout"]
            if len(certder) == 0:
                continue

            try:
                certobj = x509.load_der_x509_certificate(
                    data=b"".join(certder),
                    backend=default_backend(),
                )

                # check that CA is the same.
                ca_keyid = ca.extensions.get_extension_for_oid(
                    x509.oid.ExtensionOID.SUBJECT_KEY_IDENTIFIER
                ).value.digest
                cert_authkeyid = certobj.extensions.get_extension_for_oid(
                    x509.oid.ExtensionOID.AUTHORITY_KEY_IDENTIFIER
                ).value.key_identifier
            except (ValueError, x509.extensions.ExtensionNotFound):
                continue

            if ca_keyid != cert_authkeyid:
                continue

            if certobj.serial_number == serial:
                deletelist.add(line.strip())

    for todelete in deletelist:
        logger.info("deleting IPsec sa between %s" % todelete)
        cmd = ("racoonctl", "delete-sa", "isakmp", "inet", todelete)
        cmd_exec.run(cmd)
        tokens = todelete.split()
        cmd = ("racoonctl", "delete-sa", "isakmp", "inet", tokens[1], tokens[0])
        cmd_exec.run(cmd)
Пример #15
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')

    # 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()
    # try to get some TPM randomness into the system entropy pool
    instance_tpm.init_system_rand()

    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'].decode('utf-8')
        agent_uuid = sys_uuid.strip()
    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)

    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 use <Ctrl-C> to stop",
                serveraddr[0], serveraddr[1])
    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 = '%s/unzipped/RevocationNotifier-cert.crt' % (secdir)
        elif cert_path[0] != '/':
            # if it is a relative, convert to absolute in work_dir
            cert_path = os.path.abspath('%s/%s' % (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
            if os.path.exists("%s/unzipped/action_list" % secdir):
                with open("%s/unzipped/action_list" % secdir, 'r') 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()
Пример #16
0
    def __run(self, cmd, expectedcode=tpm_abstract.AbstractTPM.EXIT_SUCESS, raiseOnError=True, lock=True, outputpaths=None):
        env = os.environ.copy()
        env['TPM_SERVER_PORT']='9998'
        env['TPM_SERVER_NAME']='localhost'
        env['PATH']=env['PATH']+":%s"%common.TPM_TOOLS_PATH

        # Backwards compat with string input (force all to be dict)
        if isinstance(outputpaths, str):
            outputpaths = [outputpaths]

        # Handle stubbing the TPM out
        fprt = tpm1.__fingerprint(cmd)
        if common.STUB_TPM and common.TPM_CANNED_VALUES is not None:
            # Use canned values for stubbing
            yamlIn = common.TPM_CANNED_VALUES
            if fprt in yamlIn:
                # The value we're looking for has been canned!
                thisTiming = yamlIn[fprt]['timing']
                thisRetout = yamlIn[fprt]['retout']
                thisCode = yamlIn[fprt]['code']
                thisFileout = yamlIn[fprt]['fileout']
                fileoutEncoded = {}

                # Decode files that are supplied
                if outputpaths is not None and len(outputpaths) == 1:
                    if thisFileout != '':
                        fileoutEncoded[outputpaths[0]] = zlib.decompress(base64.b64decode(thisFileout))

                logger.debug("TPM call '%s' was stubbed out, with a simulated delay of %f sec"%(fprt,thisTiming))
                time.sleep(thisTiming)

                # Package for return
                returnDict = {
                    'retout': thisRetout,
                    'code': thisCode,
                    'fileouts': fileoutEncoded,
                    'timing': thisTiming,
                }
                return returnDict
            elif not lock:
                # non-lock calls don't go to the TPM (just let it pass through)
                pass
            else:
                # Our command hasn't been canned!
                raise Exception("Command %s not found in canned YAML!"%(fprt))

        numtries = 0
        while True:
            if lock:
                with self.tpmutilLock:
                    retDict = cmd_exec.run(cmd=cmd,expectedcode=expectedcode,raiseOnError=False,lock=lock,outputpaths=outputpaths,env=env)
            else:
                retDict = cmd_exec.run(cmd=cmd,expectedcode=expectedcode,raiseOnError=False,lock=lock,outputpaths=outputpaths,env=env)
            t0 = retDict['timing']['t0']
            t1 = retDict['timing']['t1']
            code = retDict['code']
            retout = retDict['retout']
            fileouts = retDict['fileouts']

            # keep trying to communicate with TPM if there was an I/O error
            if code== tpm_abstract.AbstractTPM.TPM_IO_ERR:
                numtries+=1
                maxr = self.config.getint('cloud_agent','max_retries')
                if numtries >= maxr:
                    logger.error("TPM appears to be in use by another application.  Keylime is incompatible with other TPM TSS applications like trousers/tpm-tools. Please uninstall or disable.")
                    break
                retry  = self.config.getfloat('cloud_agent','retry_interval')
                logger.info("Failed to call TPM %d/%d times, trying again in %f seconds..."%(numtries,maxr,retry))
                time.sleep(retry)
                continue
            else:
                break

        # Don't bother continuing if TPM call failed and we're raising on error
        if code!=expectedcode and raiseOnError:
            raise Exception("Command: %s returned %d, expected %d, output %s"%(cmd,code,expectedcode,retout))

        # Metric output
        if lock or self.tpmutilLock.locked():
            pad = ""
            if len(fprt) < 8:
                pad += "\t"
            if len(fprt) < 16:
                pad += "\t"
            if len(fprt) < 24:
                pad += "\t"

            filelen = 0
            if fileouts is not None:
                filelen = len(fileouts)

            # Print out benchmarking information for TPM (if requested)
            #print "\033[95mTIMING: %s%s\t:%f\toutlines:%d\tfilelines:%d\t%s\033[0m" % (fprt,pad,t1-t0,len(retout),filelen,cmd)
            if common.TPM_BENCHMARK_PATH is not None:
                with open(common.TPM_BENCHMARK_PATH, "ab") as bench:
                    bench.write("TIMING: %s%s\ttime:%f\toutlines:%d\tfilecount:%d\t%s\n" % (fprt,pad,t1-t0,len(retout),filelen,cmd))

            # Print out YAML canned values (if requested)
            # NOTE: resulting file will be missing the surrounding braces! (must add '{' and '}' for reading)
            if common.TPM_CANNED_VALUES_PATH is not None:
                with open(common.TPM_CANNED_VALUES_PATH, "ab") as can:
                    fileoutEncoded = ""
                    if outputpaths is not None and len(outputpaths) > 0:
                        if len(fileouts) == 1 and len(outputpaths) == 1:
                            fileoutEncoded = zlib.compress(base64.b64encode(iter(fileouts.values()).next()))
                        else:
                            raise Exception("Command %s is using multiple files unexpectedly!"%(fprt))

                    # tpm_cexec will need to know the nonce
                    nonce = ""
                    match = re.search("-nonce ([\w]+)", cmd)
                    if match:
                        nonce = match.group(1)

                    yamlObj = {'type':fprt,'retout':retout,'fileout':fileoutEncoded,'cmd':cmd,'timing':t1-t0,'code':code,'nonce':nonce}
                    can.write("\"%s\": %s,\n"%(fprt,yaml.dump(yamlObj,indent=4,sort_keys=True, Dumper=SafeDumper)))

        return retDict
Пример #17
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()
Пример #18
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()