def validate_agent_data(agent_data): if agent_data is None: return False, None # validate that the allowlist is proper JSON lists = json.loads(agent_data['allowlist']) # Validate exlude list contains valid regular expressions is_valid, _, err_msg = validators.valid_exclude_list(lists.get('exclude')) if not is_valid: err_msg += " Exclude list regex is misformatted. Please correct the issue and try again." return is_valid, err_msg
def _process_measurement_list(agentAttestState, lines, hash_alg, lists=None, m2w=None, pcrval=None, ima_keyrings=None, boot_aggregates=None): failure = Failure(Component.IMA) running_hash = agentAttestState.get_pcr_state(config.IMA_PCR, hash_alg) found_pcr = pcrval is None errors = {} pcrval_bytes = b"" if pcrval is not None: pcrval_bytes = codecs.decode(pcrval.encode("utf-8"), "hex") if lists is not None: if isinstance(lists, str): lists = json.loads(lists) allow_list = lists["allowlist"] exclude_list = lists["exclude"] else: allow_list = None exclude_list = None ima_log_hash_alg = algorithms.Hash.SHA1 if allow_list is not None: try: ima_log_hash_alg = algorithms.Hash( allow_list["ima"]["log_hash_alg"]) except ValueError: logger.warning( "Specified IMA log hash algorithm %s is not a valid algorithm! Defaulting to SHA1.", allow_list["ima"]["log_hash_alg"], ) if boot_aggregates and allow_list: if "boot_aggregate" not in allow_list["hashes"]: allow_list["hashes"]["boot_aggregate"] = [] for alg in boot_aggregates.keys(): for val in boot_aggregates[alg]: if val not in allow_list["hashes"]["boot_aggregate"]: allow_list["hashes"]["boot_aggregate"].append(val) is_valid, compiled_regex, err_msg = validators.valid_exclude_list( exclude_list) if not is_valid: # This should not happen as the exclude list has already been validated # by the verifier before acceping it. This is a safety net just in case. err_msg += " Exclude list will be ignored." logger.error(err_msg) # Setup device mapper validation dm_validator = None if allow_list is not None: dm_policy = allow_list["ima"]["dm_policy"] if dm_policy is not None: dm_validator = ima_dm.DmIMAValidator(dm_policy) dm_state = agentAttestState.get_ima_dm_state() # Only load state when using incremental attestation if agentAttestState.get_next_ima_ml_entry() != 0: dm_validator.state_load(dm_state) ima_validator = ast.Validator({ ast.ImaSig: functools.partial(_validate_ima_sig, compiled_regex, ima_keyrings, allow_list), ast.ImaNg: functools.partial(_validate_ima_ng, compiled_regex, allow_list), ast.Ima: functools.partial(_validate_ima_ng, compiled_regex, allow_list), ast.ImaBuf: functools.partial(_validate_ima_buf, compiled_regex, allow_list, ima_keyrings, dm_validator), }) # Iterative attestation may send us no log [len(lines) == 1]; compare last know PCR 10 state # against current PCR state. # Since IMA log append and PCR extend is not atomic, we may get a quote that does not yet take # into account the next appended measurement's [len(lines) == 2] PCR extension. if not found_pcr and len(lines) <= 2: found_pcr = running_hash == pcrval_bytes for linenum, line in enumerate(lines): # remove only the newline character, as there can be the space # as the delimiter character followed by an empty field at the # end line = line.strip("\n") if line == "": continue try: entry = ast.Entry(line, ima_validator, ima_hash_alg=ima_log_hash_alg, pcr_hash_alg=hash_alg) # update hash running_hash = hash_alg.hash(running_hash + entry.pcr_template_hash) validation_failure = entry.invalid() if validation_failure: failure.merge(validation_failure) errors[type(entry.mode)] = errors.get(type(entry.mode), 0) + 1 if not found_pcr: # End of list should equal pcr value found_pcr = running_hash == pcrval_bytes if found_pcr: logger.debug("Found match at linenum %s", linenum + 1) # We always want to have the very last line for the attestation, so # we keep the previous runninghash, which is not the last one! agentAttestState.update_ima_attestation( int(entry.pcr), running_hash, linenum + 1) if dm_validator: agentAttestState.set_ima_dm_state( dm_validator.state_dump()) # Keep old functionality for writing the parsed files with hashes into a file if m2w is not None and (type(entry.mode) in [ast.Ima, ast.ImaNg, ast.ImaSig]): hash_value = codecs.encode(entry.mode.digest.bytes, "hex") path = entry.mode.path.name m2w.write(f"{hash_value} {path}\n") except ast.ParserError: failure.add_event( "entry", f"Line was not parsable into a valid IMA entry: {line}", True, ["parser"]) logger.error("Line was not parsable into a valid IMA entry: %s", line) # check PCR value has been found if not found_pcr: logger.error("IMA measurement list does not match TPM PCR %s", pcrval) failure.add_event( "pcr_mismatch", f"IMA measurement list does not match TPM PCR {pcrval}", True) # Check if any validators failed if sum(errors.values()) > 0: error_msg = "IMA ERRORS: Some entries couldn't be validated. Number of failures in modes: " error_msg += ", ".join( [f"{k.__name__ } {v}" for k, v in errors.items()]) logger.error("%s.", error_msg) return codecs.encode(running_hash, "hex").decode("utf-8"), failure
def test_invalid(self): """Check an invalid exclude list.""" value = validators.valid_exclude_list([r"a["]) self.assertEqual( value, (False, None, "Invalid regex: unterminated character set.") )
def test_multi(self): """Check a multiple elements exclude list.""" value = validators.valid_exclude_list([r"a.*", r"b.*"]) self.assertTrue(value[0]) self.assertEqual(value[1].pattern, r"(a.*)|(b.*)") self.assertEqual(value[2], None)
def test_single(self): """Check a single exclude list element.""" value = validators.valid_exclude_list([r"a.*"]) self.assertTrue(value[0]) self.assertEqual(value[1].pattern, r"(a.*)") self.assertEqual(value[2], None)
def test_none(self): """Check that the empty list is valid.""" self.assertEqual(validators.valid_exclude_list(None), (True, None, None))