From 4f62301b6e4332a012468854d79d99bc34c4a5fe Mon Sep 17 00:00:00 2001 From: Thorsten Sick Date: Wed, 2 Jun 2021 12:02:58 +0200 Subject: [PATCH] Full unit test coverage for config.py --- app/config.py | 17 +- plugins/base/plugin_base.py | 5 +- .../running_vm/running_vm_plugin.py | 2 - .../vm_controller/vagrant/vagrant_plugin.py | 31 +--- tests/data/attacks_missing.yaml | 6 + tests/data/basic.yaml | 17 +- tests/data/basic_empty_sensor.yaml | 155 ++++++++++++++++++ tests/data/basic_loot_missing.yaml | 149 +++++++++++++++++ tests/data/basic_results_missing.yaml | 149 +++++++++++++++++ tests/test_config.py | 54 ++++++ 10 files changed, 548 insertions(+), 37 deletions(-) create mode 100644 tests/data/basic_empty_sensor.yaml create mode 100644 tests/data/basic_loot_missing.yaml create mode 100644 tests/data/basic_results_missing.yaml diff --git a/app/config.py b/app/config.py index e8151a5..b8bbb53 100644 --- a/app/config.py +++ b/app/config.py @@ -151,6 +151,9 @@ class ExperimentConfig(): self._attackers = [] self.load(configfile) + # Test essential data that is a hard requirement. Should throw errors if anything is wrong + self.loot_dir() + def load(self, configfile): """ Loads the configuration file @@ -194,7 +197,13 @@ class ExperimentConfig(): def loot_dir(self): """ Returns the loot dir """ - return self.raw_config["results"]["loot_dir"] + if "results" not in self.raw_config or self.raw_config["results"] is None: + raise ConfigurationError("results missing in configuration") + try: + res = self.raw_config["results"]["loot_dir"] + except KeyError: + raise ConfigurationError("results/loot_dir not properly set in configuration") + return res def kali_conf(self, attack): """ Get kali config for a specific kali attack @@ -274,5 +283,9 @@ class ExperimentConfig(): """ if "sensors" not in self.raw_config: return {} - if name not in self.raw_config["sensors"]: + if self.raw_config["sensors"] is None: # Better for unit tests that way. + return {} + if name in self.raw_config["sensors"]: return self.raw_config["sensors"][name] + + return {} diff --git a/plugins/base/plugin_base.py b/plugins/base/plugin_base.py index e11229b..4b9560d 100644 --- a/plugins/base/plugin_base.py +++ b/plugins/base/plugin_base.py @@ -78,8 +78,9 @@ class BasePlugin(): @param config: A dict with system configuration relevant for all plugins """ - self.sysconf["abs_machinepath_internal"] = config["abs_machinepath_internal"] - self.sysconf["abs_machinepath_external"] = config["abs_machinepath_external"] + # TODO: Verify if it works properly. It should wotk thanks to the new design + # self.sysconf["abs_machinepath_internal"] = config["abs_machinepath_internal"] + # self.sysconf["abs_machinepath_external"] = config["abs_machinepath_external"] self.load_default_config() def process_config(self, config): diff --git a/plugins/default/vm_controller/running_vm/running_vm_plugin.py b/plugins/default/vm_controller/running_vm/running_vm_plugin.py index 8bfb77e..b771264 100644 --- a/plugins/default/vm_controller/running_vm/running_vm_plugin.py +++ b/plugins/default/vm_controller/running_vm/running_vm_plugin.py @@ -16,8 +16,6 @@ class RunningVMPlugin(SSHFeatures, MachineryPlugin): def __init__(self): super().__init__() - # super(SSHFeatures).__init__() - # super(MachineryPlugin).__init__() self.plugin_path = __file__ self.vagrantfilepath = None self.vagrantfile = None diff --git a/plugins/default/vm_controller/vagrant/vagrant_plugin.py b/plugins/default/vm_controller/vagrant/vagrant_plugin.py index f2f6717..1917f21 100644 --- a/plugins/default/vm_controller/vagrant/vagrant_plugin.py +++ b/plugins/default/vm_controller/vagrant/vagrant_plugin.py @@ -91,21 +91,6 @@ class VagrantPlugin(SSHFeatures, MachineryPlugin): else: return super().connect() - # if self.config.os() == "linux": - # super - - # if self.config.os() == "windows": - # args = {"key_filename": os.path.join(self.sysconf["abs_machinepath_external"], self.config.ssh_keyfile())} - # if self.config.ssh_password(): - # args["password"] = self.config.ssh_password() - # uhp = self.get_ip() - # print(uhp) - # print(args) - # print(self.config.ssh_user()) - # self.c = Connection(uhp, user=self.config.ssh_user(), connect_kwargs=args) - # print(self.c) - # return self.c - def get_state(self): """ Get detailed state of a machine """ @@ -127,20 +112,6 @@ class VagrantPlugin(SSHFeatures, MachineryPlugin): def get_ip(self): """ Return the machine ip """ - # TODO: Create special code to extract windows IPs - - # TODO: Find a smarter way to get the ip - - # ips = [] - # cmd = "ifconfig" - # res = self.vm_manager.__call_remote_run__(cmd) - - # for line in res.split("\n"): - # m = re.match(r".*inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*", line) - # if m: - # print(m.group(1)) - # ips.append(m.group(1)) - - filename = os.path.join(self.sysconf["abs_machinepath_external"], "ip4.txt") + filename = os.path.join(self.get_machine_path_external(), "ip4.txt") with open(filename, "rt") as fh: return fh.readline().strip() diff --git a/tests/data/attacks_missing.yaml b/tests/data/attacks_missing.yaml index 489a8cd..377f040 100644 --- a/tests/data/attacks_missing.yaml +++ b/tests/data/attacks_missing.yaml @@ -137,6 +137,8 @@ kali_conf: ### # A file containing potential passwords pwdfile: passwords.txt + missing: + # intentionally left blank ### # Settings for the results being harvested @@ -144,3 +146,7 @@ results: ### # The directory the loot will be in loot_dir: loot + +### +# General sensor config config +# sensors: diff --git a/tests/data/basic.yaml b/tests/data/basic.yaml index 8607465..3331743 100644 --- a/tests/data/basic.yaml +++ b/tests/data/basic.yaml @@ -146,4 +146,19 @@ kali_conf: results: ### # The directory the loot will be in - loot_dir: loot \ No newline at end of file + loot_dir: loot + +### +# General sensor config config +sensors: + ### + # Windows sensor plugin configuration + windows_sensor: + ### + # Name of the dll to use. Must match AV version + # dll_name: aswidptestdll.dll + dll_name: windows_sensor.dll + + ### + # Folder where the sensor tool is located + sensor_tool_folder: windows_sensor \ No newline at end of file diff --git a/tests/data/basic_empty_sensor.yaml b/tests/data/basic_empty_sensor.yaml new file mode 100644 index 0000000..9aad123 --- /dev/null +++ b/tests/data/basic_empty_sensor.yaml @@ -0,0 +1,155 @@ + +### +# Caldera configuration +caldera: + ### + # API key for caldera. See caldera configuration. Default is ADMIN123 + apikey: ADMIN123 + +### +# Attacks configuration +attackers: + ### + # Configuration for the first attacker. One should normally be enough + attacker: + + ### + # Defining VM controller settings for this machine + vm_controller: + ### + # Type of the VM controller, Options are "vagrant" + type: vagrant + ### + # # path where the vagrantfile is in + vagrantfilepath: systems + + ### + # Name of machine in Vagrantfile + vm_name: attacker + + ### + # machinepath is a path where the machine specific files and logs are stored. Relative to the Vagrantfile path + # and will be mounted internally as /vagrant/ + # If machinepoath is not set AttackX will try "vm_name" + machinepath: attacker1 + + ### + # OS of the VM guest. Options are so far "windows", "linux" + os: linux + + ### + # Do not destroy/create the machine: Set this to "yes". + use_existing_machine: yes + +### +# List of targets +targets: + ### + # Specific target + target1: + vm_controller: + type: vagrant + vagrantfilepath: systems + + vm_name: target1 + os: linux + ### + # Targets need a unique PAW name for caldera + paw: target1 + ### + # Targets need to be in a group for caldera + group: red + + machinepath: target1 + # Do not destroy/create the machine: Set this to "yes". + use_existing_machine: yes + + target2: + #root: systems/target1 + vm_controller: + type: vagrant + vagrantfilepath: systems + + vm_name: target2 + os: windows + paw: target2w + group: red + + machinepath: target2w + + # Do not destroy/create the machine: Set this to "yes". + use_existing_machine: yes + ### + # Optional setting to activate force when halting the machine. Windows guests sometime get stuck + halt_needs_force: yes + + ### + # If SSH without vagrant support is used (Windows !) we need a user name (uppercase) + ssh_user: ATTACKX + + ### + # For non-vagrant ssh connections a ssh keyfile stored in the machinepath is required. + ssh_keyfile: id_rsa.3 + +### +# General attack config +attacks: + ### + # configure the seconds the system idles between the attacks. Makes it slower. But attack and defense logs will be simpler to match + nap_time: 5 + +### +# A list of caldera attacks to run against the targets. +caldera_attacks: + ### + # Linux specific attacks. A list of caldera ability IDs + linux: + - "bd527b63-9f9e-46e0-9816-b8434d2b8989" + ### + # Windows specific attacks. A list of caldera ability IDs + windows: + - "bd527b63-9f9e-46e0-9816-b8434d2b8989" + +### +# Kali tool based attacks. Will result in kali commandline tools to be called. Currently supported are: "hydra" +kali_attacks: + ### + # Linux specific attacks, a list + linux: + - hydra + ### + # Windows specific attacks, a list + windows: + - hydra + +### +# Configuration for the kali attack tools +kali_conf: + ### + # Hydra configuration + hydra: + ### + # A list of protocols to brute force against. Supported: "ssh" + protocols: + - ssh + #- ftp + #- ftps + ### + # A file containing potential user names + userfile: users.txt + ### + # A file containing potential passwords + pwdfile: passwords.txt + +### +# Settings for the results being harvested +results: + ### + # The directory the loot will be in + loot_dir: loot + +### +# General sensor config config +sensors: + +foo: \ No newline at end of file diff --git a/tests/data/basic_loot_missing.yaml b/tests/data/basic_loot_missing.yaml new file mode 100644 index 0000000..504624b --- /dev/null +++ b/tests/data/basic_loot_missing.yaml @@ -0,0 +1,149 @@ + +### +# Caldera configuration +caldera: + ### + # API key for caldera. See caldera configuration. Default is ADMIN123 + apikey: ADMIN123 + +### +# Attacks configuration +attackers: + ### + # Configuration for the first attacker. One should normally be enough + attacker: + + ### + # Defining VM controller settings for this machine + vm_controller: + ### + # Type of the VM controller, Options are "vagrant" + type: vagrant + ### + # # path where the vagrantfile is in + vagrantfilepath: systems + + ### + # Name of machine in Vagrantfile + vm_name: attacker + + ### + # machinepath is a path where the machine specific files and logs are stored. Relative to the Vagrantfile path + # and will be mounted internally as /vagrant/ + # If machinepoath is not set AttackX will try "vm_name" + machinepath: attacker1 + + ### + # OS of the VM guest. Options are so far "windows", "linux" + os: linux + + ### + # Do not destroy/create the machine: Set this to "yes". + use_existing_machine: yes + +### +# List of targets +targets: + ### + # Specific target + target1: + vm_controller: + type: vagrant + vagrantfilepath: systems + + vm_name: target1 + os: linux + ### + # Targets need a unique PAW name for caldera + paw: target1 + ### + # Targets need to be in a group for caldera + group: red + + machinepath: target1 + # Do not destroy/create the machine: Set this to "yes". + use_existing_machine: yes + + target2: + #root: systems/target1 + vm_controller: + type: vagrant + vagrantfilepath: systems + + vm_name: target2 + os: windows + paw: target2w + group: red + + machinepath: target2w + + # Do not destroy/create the machine: Set this to "yes". + use_existing_machine: yes + ### + # Optional setting to activate force when halting the machine. Windows guests sometime get stuck + halt_needs_force: yes + + ### + # If SSH without vagrant support is used (Windows !) we need a user name (uppercase) + ssh_user: ATTACKX + + ### + # For non-vagrant ssh connections a ssh keyfile stored in the machinepath is required. + ssh_keyfile: id_rsa.3 + +### +# General attack config +attacks: + ### + # configure the seconds the system idles between the attacks. Makes it slower. But attack and defense logs will be simpler to match + nap_time: 5 + +### +# A list of caldera attacks to run against the targets. +caldera_attacks: + ### + # Linux specific attacks. A list of caldera ability IDs + linux: + - "bd527b63-9f9e-46e0-9816-b8434d2b8989" + ### + # Windows specific attacks. A list of caldera ability IDs + windows: + - "bd527b63-9f9e-46e0-9816-b8434d2b8989" + +### +# Kali tool based attacks. Will result in kali commandline tools to be called. Currently supported are: "hydra" +kali_attacks: + ### + # Linux specific attacks, a list + linux: + - hydra + ### + # Windows specific attacks, a list + windows: + - hydra + +### +# Configuration for the kali attack tools +kali_conf: + ### + # Hydra configuration + hydra: + ### + # A list of protocols to brute force against. Supported: "ssh" + protocols: + - ssh + #- ftp + #- ftps + ### + # A file containing potential user names + userfile: users.txt + ### + # A file containing potential passwords + pwdfile: passwords.txt + +### +# Settings for the results being harvested +results: + ### + # The directory the loot will be in + fake_loot_dir: loot diff --git a/tests/data/basic_results_missing.yaml b/tests/data/basic_results_missing.yaml new file mode 100644 index 0000000..f659ecc --- /dev/null +++ b/tests/data/basic_results_missing.yaml @@ -0,0 +1,149 @@ + +### +# Caldera configuration +caldera: + ### + # API key for caldera. See caldera configuration. Default is ADMIN123 + apikey: ADMIN123 + +### +# Attacks configuration +attackers: + ### + # Configuration for the first attacker. One should normally be enough + attacker: + + ### + # Defining VM controller settings for this machine + vm_controller: + ### + # Type of the VM controller, Options are "vagrant" + type: vagrant + ### + # # path where the vagrantfile is in + vagrantfilepath: systems + + ### + # Name of machine in Vagrantfile + vm_name: attacker + + ### + # machinepath is a path where the machine specific files and logs are stored. Relative to the Vagrantfile path + # and will be mounted internally as /vagrant/ + # If machinepoath is not set AttackX will try "vm_name" + machinepath: attacker1 + + ### + # OS of the VM guest. Options are so far "windows", "linux" + os: linux + + ### + # Do not destroy/create the machine: Set this to "yes". + use_existing_machine: yes + +### +# List of targets +targets: + ### + # Specific target + target1: + vm_controller: + type: vagrant + vagrantfilepath: systems + + vm_name: target1 + os: linux + ### + # Targets need a unique PAW name for caldera + paw: target1 + ### + # Targets need to be in a group for caldera + group: red + + machinepath: target1 + # Do not destroy/create the machine: Set this to "yes". + use_existing_machine: yes + + target2: + #root: systems/target1 + vm_controller: + type: vagrant + vagrantfilepath: systems + + vm_name: target2 + os: windows + paw: target2w + group: red + + machinepath: target2w + + # Do not destroy/create the machine: Set this to "yes". + use_existing_machine: yes + ### + # Optional setting to activate force when halting the machine. Windows guests sometime get stuck + halt_needs_force: yes + + ### + # If SSH without vagrant support is used (Windows !) we need a user name (uppercase) + ssh_user: ATTACKX + + ### + # For non-vagrant ssh connections a ssh keyfile stored in the machinepath is required. + ssh_keyfile: id_rsa.3 + +### +# General attack config +attacks: + ### + # configure the seconds the system idles between the attacks. Makes it slower. But attack and defense logs will be simpler to match + nap_time: 5 + +### +# A list of caldera attacks to run against the targets. +caldera_attacks: + ### + # Linux specific attacks. A list of caldera ability IDs + linux: + - "bd527b63-9f9e-46e0-9816-b8434d2b8989" + ### + # Windows specific attacks. A list of caldera ability IDs + windows: + - "bd527b63-9f9e-46e0-9816-b8434d2b8989" + +### +# Kali tool based attacks. Will result in kali commandline tools to be called. Currently supported are: "hydra" +kali_attacks: + ### + # Linux specific attacks, a list + linux: + - hydra + ### + # Windows specific attacks, a list + windows: + - hydra + +### +# Configuration for the kali attack tools +kali_conf: + ### + # Hydra configuration + hydra: + ### + # A list of protocols to brute force against. Supported: "ssh" + protocols: + - ssh + #- ftp + #- ftps + ### + # A file containing potential user names + userfile: users.txt + ### + # A file containing potential passwords + pwdfile: passwords.txt + +### +# Settings for the results being harvested +#results: + ### + # The directory the loot will be in + # loot_dir: loot diff --git a/tests/test_config.py b/tests/test_config.py index 408a02e..23f3db2 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -511,6 +511,24 @@ class TestExperimentConfig(unittest.TestCase): self.assertEqual(ex.raw_config["caldera"]["apikey"], "ADMIN123") self.assertEqual(ex.caldera_apikey(), "ADMIN123") + def test_loot_dir(self): + """ Test with existing loot dir """ + + ex = ExperimentConfig("tests/data/basic.yaml") + self.assertEqual(ex.loot_dir(), "loot") + + def test_missing_loot_dir(self): + """ Test with missing loot dir """ + + with self.assertRaises(ConfigurationError): + ExperimentConfig("tests/data/basic_loot_missing.yaml") + + def test_missing_results(self): + """ Test with missing results """ + + with self.assertRaises(ConfigurationError): + ExperimentConfig("tests/data/basic_results_missing.yaml") + def test_basic_loading_targets_read(self): """ Targets in config: can be found """ ex = ExperimentConfig("tests/data/basic.yaml") @@ -542,6 +560,14 @@ class TestExperimentConfig(unittest.TestCase): data = ex.kali_conf("hydra") self.assertEqual(data["userfile"], "users.txt") + def test_kali_config_missing_attack_data(self): + """ Getting kali config for a specific attack: Missing """ + + ex = ExperimentConfig("tests/data/attacks_missing.yaml") + + data = ex.kali_conf("missing") + self.assertEqual(data, {}) + def test_missing_caldera_config_obfuscator(self): """ A config file with no caldera config at all """ @@ -664,6 +690,34 @@ class TestExperimentConfig(unittest.TestCase): self.assertEqual(ex.get_caldera_attacks("windows"), ["bd527b63-9f9e-46e0-9816-b8434d2b8989", "foo", "bar"]) + def test_basic_sensor_config(self): + """ Test global configuration for a specific sensor """ + + ex = ExperimentConfig("tests/data/basic.yaml") + + self.assertEqual(ex.get_sensor_config("windows_sensor"), {"dll_name": "windows_sensor.dll", "sensor_tool_folder": "windows_sensor"}) + + def test_basic_sensor_config_missing(self): + """ Test global configuration for a specific and missing sensor """ + + ex = ExperimentConfig("tests/data/basic.yaml") + + self.assertEqual(ex.get_sensor_config("missing_windows_sensor"), {}) + + def test_basic_sensor_entry_missing(self): + """ Test global configuration for a specific and missing sensor entry""" + + ex = ExperimentConfig("tests/data/attacks_missing.yaml") + + self.assertEqual(ex.get_sensor_config("windows_sensor"), {}) + + def test_basic_sensor_entry_empty(self): + """ Test global configuration for a specific and empty sensor entry""" + + ex = ExperimentConfig("tests/data/basic_empty_sensor.yaml") + + self.assertEqual(ex.get_sensor_config("windows_sensor"), {}) + if __name__ == '__main__': unittest.main()