def __init__(self, object_generator, config=None, stix_version="2.1"): """ Initialize this reference graph generator. :param object_generator: An instance of the built-in object generator to use for generating the objects. :param config: Config settings for the generator. """ self.__config = config or Config() self.__object_generator = object_generator self.__stix_version = stix_version # A big problem that must be solved is how to stop the graph from # growing too large. We can't simply stop generating objects, because # there must be no dangling references. We also can't just keep # generating objects because the graph can balloon to enormous size. # My solution is to take advantage of the reference minimization # capability of the built-in object generator. If we minimize # references, we minimize the number of new objects which need to be # generated, and that acts to put the brakes on growth. We can't # minimize references all the time, because then the graph wouldn't # be able to grow much. So we need two generators: one which generates # objects "normally" and is used to grow the graph, and one which # minimizes references, which is used to halt growth when we need it to # stop. halt_generator_config_dict = vars(object_generator.config).copy() halt_generator_config_dict["minimize_ref_properties"] = True halt_generator_config = stix2generator.generation.object_generator\ .Config( **halt_generator_config_dict ) self.__halt_generator = stix2generator.create_object_generator( halt_generator_config)
def test_stix2_parsing(): obj_gen_config = stix2generator.generation.object_generator.Config( minimize_ref_properties=False) obj_gen = stix2generator.create_object_generator(obj_gen_config) ref_graph_gen = stix2generator.generation.reference_graph_generator \ .ReferenceGraphGenerator(obj_gen, stix_version="2.1") graph1 = { "identity--74fa9f1b-897e-40dc-8f1c-d2f531c956bb": { "id": "identity--74fa9f1b-897e-40dc-8f1c-d2f531c956bb", "type": "identity", "spec_version": "2.1" # Omit the required "name" property. # Should be ok since the property is not used by any generators, # and we don't expect this dict to be parsed and produce any # validation errors. } } _, graph2 = ref_graph_gen.generate(preexisting_objects=graph1) # ensure graph2 absorbed graph1 assert all(id_ in graph2 for id_ in graph1) # ensure our preexisting identity is still a dict assert isinstance(graph2["identity--74fa9f1b-897e-40dc-8f1c-d2f531c956bb"], dict)
def test_nonconst_object_spec_all_types(cleanup_stix2): spec = { "type": "object", "properties": { "type": "my-type", "int": {"type": "integer"}, "number": {"type": "number"}, "boolean": {"type": "boolean"}, "string": {"type": "string"}, "array": {"type": "array", "items": {"type": "integer"}}, "object": { "type": "object", "properties": { "subprop1": {"type": "integer"}, "subprop2": {"type": "string"} } } } } stix2_register_custom(spec, "my-type", "2.1") gen = stix2generator.create_object_generator() random_dict = gen.generate_from_spec(spec) stix_obj = stix2.parse(random_dict, version="2.1") # Make sure all props from the dict came through in the stix2 object for prop, value in random_dict.items(): assert stix_obj[prop] == value
def test_not_parsing(num_trials): obj_gen_config = stix2generator.generation.object_generator.Config( minimize_ref_properties=False ) obj_gen = stix2generator.create_object_generator(obj_gen_config) ref_graph_config = stix2generator.generation.reference_graph_generator \ .Config( parse=False ) ref_graph_gen = stix2generator.generation.reference_graph_generator \ .ReferenceGraphGenerator(obj_gen, ref_graph_config, stix_version="2.1") identity = stix2.v21.Identity( name="Alice" ) for _ in range(num_trials): graph1 = { identity.id: identity } _, graph2 = ref_graph_gen.generate(preexisting_objects=graph1) # ensure graph2 absorbed graph1 assert graph1.keys() <= graph2.keys() # Ensure the only parsed object is our original identity. for id_, obj in graph2.items(): if id_ == identity.id: assert isinstance(obj, stix2.v21.Identity) else: assert isinstance(obj, dict)
def test_stix2_parsing(num_trials): obj_gen_config = stix2generator.generation.object_generator.Config( minimize_ref_properties=False) obj_gen = stix2generator.create_object_generator(obj_gen_config) ref_graph_gen = stix2generator.generation.reference_graph_generator \ .ReferenceGraphGenerator(obj_gen, stix_version="2.1") identity = { "id": "identity--74fa9f1b-897e-40dc-8f1c-d2f531c956bb", "type": "identity", "spec_version": "2.1" # Omit the required "name" property. # Should be ok since the property is not used by any generators, # and we don't expect this dict to be parsed and produce any # validation errors. } for _ in range(num_trials): graph1 = {identity["id"]: identity} _, graph2 = ref_graph_gen.generate(preexisting_objects=graph1) # ensure graph2 absorbed graph1 assert graph1.keys() <= graph2.keys() # ensure our preexisting identity is still a dict, but other objects # were parsed. for id_, obj in graph2.items(): if id_ == identity["id"]: assert isinstance(obj, dict) else: assert isinstance(obj, stix2.base._STIXBase)
def generator_all_props(): """ Creates a generator which includes all optional properties. """ config = stix2generator.generation.object_generator.Config( minimize_ref_properties=False, optional_property_probability=1) generator = stix2generator.create_object_generator(config, None, "2.1") return generator
def generator_random_props(): """ Creates a generator which randomly includes or excludes properties. """ config = stix2generator.generation.object_generator.Config( minimize_ref_properties=False) generator = stix2generator.create_object_generator(config, None, "2.1") return generator
def test_bad_seed(): obj_gen_config = stix2generator.generation.object_generator.Config( minimize_ref_properties=False) obj_gen = stix2generator.create_object_generator(obj_gen_config) ref_graph_gen = stix2generator.generation.reference_graph_generator \ .ReferenceGraphGenerator(obj_gen, stix_version="2.1") with pytest.raises( stix2generator.exceptions.GeneratableSTIXTypeNotFoundError): ref_graph_gen.generate("foo")
def test_no_dangling_references(num_trials): obj_gen_config = stix2generator.generation.object_generator.Config( minimize_ref_properties=False) obj_gen = stix2generator.create_object_generator(obj_gen_config) ref_graph_gen = stix2generator.generation.reference_graph_generator \ .ReferenceGraphGenerator( obj_gen ) for _ in range(num_trials): _, graph = ref_graph_gen.generate() assert not stix2generator.test.utils.has_dangling_references(graph)
def test_seeds(num_trials, seed_type): obj_gen_config = stix2generator.generation.object_generator.Config( minimize_ref_properties=False) obj_gen = stix2generator.create_object_generator(obj_gen_config) ref_graph_gen = stix2generator.generation.reference_graph_generator \ .ReferenceGraphGenerator(obj_gen, stix_version="2.1") for _ in range(num_trials): _, graph = ref_graph_gen.generate(seed_type) # Ensure the graph has at least one object of type seed_type. assert any( stix2generator.utils.is_stix_type( obj, seed_type, stix_version="2.1") for obj in graph.values())
def test_graph_random(num_trials): obj_gen_config = stix2generator.generation.object_generator.Config( minimize_ref_properties=False) obj_gen = stix2generator.create_object_generator(obj_gen_config) graph_gen_config = stix2generator.generation.reference_graph_generator\ .Config( graph_type="random", inverse_property_constraints="delete" ) ref_graph_gen = stix2generator.generation.reference_graph_generator\ .ReferenceGraphGenerator(obj_gen, graph_gen_config) for _ in range(num_trials): _, graph = ref_graph_gen.generate() assert not stix2generator.test.utils.has_dangling_references(graph) assert stix2generator.test.utils.is_connected(graph)
def test_preexisting_objects(): obj_gen_config = stix2generator.generation.object_generator.Config( minimize_ref_properties=False) obj_gen = stix2generator.create_object_generator(obj_gen_config) ref_graph_gen = stix2generator.generation.reference_graph_generator \ .ReferenceGraphGenerator(obj_gen, stix_version="2.1") _, graph1 = ref_graph_gen.generate() _, graph2 = ref_graph_gen.generate(preexisting_objects=graph1) # just ensure graph2 absorbed graph1. Anything else we can test? assert all(id_ in graph2 for id_ in graph1) # ensure all objects got parsed ok assert all( isinstance(obj, stix2.base._STIXBase) for obj in graph2.values())
def test_graph_delete_inverse_properties(num_trials): obj_gen_config = stix2generator.generation.object_generator.Config( minimize_ref_properties=False) obj_gen = stix2generator.create_object_generator(obj_gen_config) graph_gen_config = stix2generator.generation.reference_graph_generator\ .Config( inverse_property_constraints="delete" ) ref_graph_gen = stix2generator.generation.reference_graph_generator\ .ReferenceGraphGenerator(obj_gen, graph_gen_config) for _ in range(num_trials): # I feel like I should hard-code a STIX object type I know to contain # lots of reference properties and have applicable constraints, to have # a high likelihood that reference properties will be generated, the # graph will grow, and there will be some applicable constraints to # test. _, graph = ref_graph_gen.generate("network-traffic") assert not _constraints_applicable(graph)
def default_object_generator(): return stix2generator.create_object_generator(stix_version="2.1")