From da20c0e9e6143ca029df8e20dc7aa77d10ed7385 Mon Sep 17 00:00:00 2001 From: SunilSamra Date: Mon, 17 Jul 2023 13:00:58 +0100 Subject: [PATCH] #901 - Removed bool apply_implicit_rule - Set default implicit_rule to EXPLICIT DENY - Added position to ACLs in laydown configs - Removed apply_implicit_rule from training configs --- docs/source/config.rst | 1 + src/primaite/acl/access_control_list.py | 65 ++++++---------- .../lay_down_config_1_DDOS_basic.yaml | 1 + .../lay_down_config_2_DDOS_basic.yaml | 9 +++ .../lay_down_config_3_DOS_very_basic.yaml | 3 + .../lay_down_config_5_data_manipulation.yaml | 17 ++++ .../training/training_config_main.yaml | 2 - src/primaite/config/training_config.py | 6 +- src/primaite/environment/primaite_env.py | 4 +- tests/config/obs_tests/laydown.yaml | 2 + .../main_config_ACCESS_CONTROL_LIST.yaml | 2 - .../obs_tests/main_config_without_obs.yaml | 2 - ..._space_fixed_blue_actions_main_config.yaml | 4 - .../single_action_space_main_config.yaml | 1 - tests/test_acl.py | 77 +++++++++++++++++-- 15 files changed, 128 insertions(+), 68 deletions(-) diff --git a/docs/source/config.rst b/docs/source/config.rst index 12b0996c..8367faf0 100644 --- a/docs/source/config.rst +++ b/docs/source/config.rst @@ -485,3 +485,4 @@ The lay down config file consists of the following attributes: * **destination** [IP address]: Defines the destination IP address for the rule in xxx.xxx.xxx.xxx format * **protocol** [freetext]: Defines the protocol for the rule. Must match a value in the services list * **port** [int]: Defines the port for the rule. Must match a value in the ports list + * **position** [int]: Defines where to place the ACL rule in the list. Lower index or (higher up in the list) means they are checked first. Index starts at 0 (Python indexes). diff --git a/src/primaite/acl/access_control_list.py b/src/primaite/acl/access_control_list.py index cee78664..47a5ac00 100644 --- a/src/primaite/acl/access_control_list.py +++ b/src/primaite/acl/access_control_list.py @@ -12,23 +12,16 @@ _LOGGER: Final[logging.Logger] = logging.getLogger(__name__) class AccessControlList: """Access Control List class.""" - def __init__(self, apply_implicit_rule, implicit_permission, max_acl_rules): + def __init__(self, implicit_permission, max_acl_rules): """Init.""" - # Bool option in main_config to decide to use implicit rule or not - self.apply_implicit_rule: bool = apply_implicit_rule # Implicit ALLOW or DENY firewall spec - if self.apply_implicit_rule: - self.acl_implicit_permission = implicit_permission - else: - self.acl_implicit_permission = "NA" - # Last rule in the ACL list - # Implicit rule + self.acl_implicit_permission = implicit_permission + # Implicit rule in ACL list self.acl_implicit_rule = None - if self.apply_implicit_rule: - if self.acl_implicit_permission == RulePermissionType.DENY: - self.acl_implicit_rule = ACLRule("DENY", "ANY", "ANY", "ANY", "ANY") - elif self.acl_implicit_permission == RulePermissionType.ALLOW: - self.acl_implicit_rule = ACLRule("ALLOW", "ANY", "ANY", "ANY", "ANY") + if self.acl_implicit_permission == RulePermissionType.DENY: + self.acl_implicit_rule = ACLRule("DENY", "ANY", "ANY", "ANY", "ANY") + elif self.acl_implicit_permission == RulePermissionType.ALLOW: + self.acl_implicit_rule = ACLRule("ALLOW", "ANY", "ANY", "ANY", "ANY") # Maximum number of ACL Rules in ACL self.max_acl_rules: int = max_acl_rules @@ -37,17 +30,8 @@ class AccessControlList: @property def acl(self): - """Public access method for private _acl. - - Adds implicit rule to the BACK of the list after ALL the OTHER ACL rules and - pads out rest of list (if it is empty) with None. - """ - if self.acl_implicit_rule is not None: - acl_list = self._acl + [self.acl_implicit_rule] - else: - acl_list = self._acl - - return acl_list + [None] * (self.max_acl_rules - len(acl_list)) + """Public access method for private _acl.""" + return self._acl + [self.acl_implicit_rule] def check_address_match(self, _rule: ACLRule, _source_ip_address: str, _dest_ip_address: str) -> bool: """Checks for IP address matches. @@ -136,7 +120,7 @@ class AccessControlList: else: _LOGGER.info(f"Position {position_index} is an invalid/overwrites implicit firewall rule") - def remove_rule(self, _permission, _source_ip, _dest_ip, _protocol, _port): + def remove_rule(self, _permission: str, _source_ip: str, _dest_ip: str, _protocol: str, _port: str) -> None: """ Removes a rule. @@ -147,17 +131,17 @@ class AccessControlList: _protocol: the protocol _port: the port """ - # Add check so you cant remove implicit rule - rule = ACLRule(_permission, _source_ip, _dest_ip, _protocol, str(_port)) - # There will not always be something removable since the agent will be trying random things - try: - self.acl.remove(rule) - except Exception: - return + rule_to_delete = ACLRule(_permission, _source_ip, _dest_ip, _protocol, str(_port)) + delete_rule_hash = hash(rule_to_delete) + + for index in range(0, len(self._acl)): + if isinstance(self._acl[index], ACLRule) and hash(self._acl[index]) == delete_rule_hash: + self._acl[index] = None def remove_all_rules(self): """Removes all rules.""" - self.acl.clear() + for i in range(len(self._acl)): + self._acl[i] = None def get_dictionary_hash(self, _permission, _source_ip, _dest_ip, _protocol, _port): """ @@ -188,15 +172,12 @@ class AccessControlList: :rtype: Dict[str, ACLRule] """ relevant_rules = {} - - for rule_key, rule_value in self.acl.items(): - if self.check_address_match(rule_value, _source_ip_address, _dest_ip_address): - if ( - rule_value.get_protocol() == _protocol or rule_value.get_protocol() == "ANY" or _protocol == "ANY" - ) and ( - str(rule_value.get_port()) == str(_port) or rule_value.get_port() == "ANY" or str(_port) == "ANY" + for rule in self.acl: + if self.check_address_match(rule, _source_ip_address, _dest_ip_address): + if (rule.get_protocol() == _protocol or rule.get_protocol() == "ANY" or _protocol == "ANY") and ( + str(rule.get_port()) == str(_port) or rule.get_port() == "ANY" or str(_port) == "ANY" ): # There's a matching rule. - relevant_rules[rule_key] = rule_value + relevant_rules[self._acl.index(rule)] = rule return relevant_rules diff --git a/src/primaite/config/_package_data/lay_down/lay_down_config_1_DDOS_basic.yaml b/src/primaite/config/_package_data/lay_down/lay_down_config_1_DDOS_basic.yaml index 3f0c546a..dad0ff4b 100644 --- a/src/primaite/config/_package_data/lay_down/lay_down_config_1_DDOS_basic.yaml +++ b/src/primaite/config/_package_data/lay_down/lay_down_config_1_DDOS_basic.yaml @@ -163,3 +163,4 @@ destination: ANY protocol: ANY port: ANY + position: 0 diff --git a/src/primaite/config/_package_data/lay_down/lay_down_config_2_DDOS_basic.yaml b/src/primaite/config/_package_data/lay_down/lay_down_config_2_DDOS_basic.yaml index 39bf7dac..e91859d2 100644 --- a/src/primaite/config/_package_data/lay_down/lay_down_config_2_DDOS_basic.yaml +++ b/src/primaite/config/_package_data/lay_down/lay_down_config_2_DDOS_basic.yaml @@ -243,6 +243,7 @@ destination: 192.168.10.14 protocol: TCP port: 80 + position: 0 - item_type: ACL_RULE id: '26' permission: ALLOW @@ -250,6 +251,7 @@ destination: 192.168.10.14 protocol: TCP port: 80 + position: 1 - item_type: ACL_RULE id: '27' permission: ALLOW @@ -257,6 +259,7 @@ destination: 192.168.10.14 protocol: TCP port: 80 + position: 2 - item_type: ACL_RULE id: '28' permission: ALLOW @@ -264,6 +267,7 @@ destination: 192.168.20.15 protocol: TCP port: 80 + position: 3 - item_type: ACL_RULE id: '29' permission: ALLOW @@ -271,6 +275,7 @@ destination: 192.168.10.13 protocol: TCP port: 80 + position: 4 - item_type: ACL_RULE id: '30' permission: DENY @@ -278,6 +283,7 @@ destination: 192.168.20.15 protocol: TCP port: 80 + position: 5 - item_type: ACL_RULE id: '31' permission: DENY @@ -285,6 +291,7 @@ destination: 192.168.20.15 protocol: TCP port: 80 + position: 6 - item_type: ACL_RULE id: '32' permission: DENY @@ -292,6 +299,7 @@ destination: 192.168.20.15 protocol: TCP port: 80 + position: 7 - item_type: ACL_RULE id: '33' permission: DENY @@ -299,6 +307,7 @@ destination: 192.168.10.14 protocol: TCP port: 80 + position: 8 - item_type: RED_POL id: '34' start_step: 20 diff --git a/src/primaite/config/_package_data/lay_down/lay_down_config_3_DOS_very_basic.yaml b/src/primaite/config/_package_data/lay_down/lay_down_config_3_DOS_very_basic.yaml index 619a0d35..453b6abb 100644 --- a/src/primaite/config/_package_data/lay_down/lay_down_config_3_DOS_very_basic.yaml +++ b/src/primaite/config/_package_data/lay_down/lay_down_config_3_DOS_very_basic.yaml @@ -111,6 +111,7 @@ destination: 192.168.1.4 protocol: TCP port: 80 + position: 0 - item_type: ACL_RULE id: '12' permission: ALLOW @@ -118,6 +119,7 @@ destination: 192.168.1.4 protocol: TCP port: 80 + position: 1 - item_type: ACL_RULE id: '13' permission: ALLOW @@ -125,6 +127,7 @@ destination: 192.168.1.3 protocol: TCP port: 80 + position: 2 - item_type: RED_POL id: '14' start_step: 20 diff --git a/src/primaite/config/_package_data/lay_down/lay_down_config_5_data_manipulation.yaml b/src/primaite/config/_package_data/lay_down/lay_down_config_5_data_manipulation.yaml index 75ab72cf..96596514 100644 --- a/src/primaite/config/_package_data/lay_down/lay_down_config_5_data_manipulation.yaml +++ b/src/primaite/config/_package_data/lay_down/lay_down_config_5_data_manipulation.yaml @@ -345,6 +345,7 @@ destination: 192.168.2.10 protocol: ANY port: ANY + position: 0 - item_type: ACL_RULE id: '34' permission: ALLOW @@ -352,6 +353,7 @@ destination: 192.168.2.14 protocol: ANY port: ANY + position: 1 - item_type: ACL_RULE id: '35' permission: ALLOW @@ -359,6 +361,7 @@ destination: 192.168.2.14 protocol: ANY port: ANY + position: 2 - item_type: ACL_RULE id: '36' permission: ALLOW @@ -366,6 +369,7 @@ destination: 192.168.2.10 protocol: ANY port: ANY + position: 3 - item_type: ACL_RULE id: '37' permission: ALLOW @@ -373,6 +377,7 @@ destination: 192.168.10.11 protocol: ANY port: ANY + position: 4 - item_type: ACL_RULE id: '38' permission: ALLOW @@ -380,6 +385,7 @@ destination: 192.168.10.12 protocol: ANY port: ANY + position: 5 - item_type: ACL_RULE id: '39' permission: ALLOW @@ -387,6 +393,7 @@ destination: 192.168.2.14 protocol: ANY port: ANY + position: 6 - item_type: ACL_RULE id: '40' permission: ALLOW @@ -394,6 +401,7 @@ destination: 192.168.2.10 protocol: ANY port: ANY + position: 7 - item_type: ACL_RULE id: '41' permission: ALLOW @@ -401,6 +409,7 @@ destination: 192.168.2.16 protocol: ANY port: ANY + position: 8 - item_type: ACL_RULE id: '42' permission: ALLOW @@ -408,6 +417,7 @@ destination: 192.168.2.16 protocol: ANY port: ANY + position: 9 - item_type: ACL_RULE id: '43' permission: ALLOW @@ -415,6 +425,7 @@ destination: 192.168.2.10 protocol: ANY port: ANY + position: 10 - item_type: ACL_RULE id: '44' permission: ALLOW @@ -422,6 +433,7 @@ destination: 192.168.2.14 protocol: ANY port: ANY + position: 11 - item_type: ACL_RULE id: '45' permission: ALLOW @@ -429,6 +441,7 @@ destination: 192.168.2.16 protocol: ANY port: ANY + position: 12 - item_type: ACL_RULE id: '46' permission: ALLOW @@ -436,6 +449,7 @@ destination: 192.168.1.12 protocol: ANY port: ANY + position: 13 - item_type: ACL_RULE id: '47' permission: ALLOW @@ -443,6 +457,7 @@ destination: 192.168.1.12 protocol: ANY port: ANY + position: 14 - item_type: ACL_RULE id: '48' permission: ALLOW @@ -450,6 +465,7 @@ destination: 192.168.1.12 protocol: ANY port: ANY + position: 15 - item_type: ACL_RULE id: '49' permission: DENY @@ -457,6 +473,7 @@ destination: ANY protocol: ANY port: ANY + position: 16 - item_type: RED_POL id: '50' start_step: 50 diff --git a/src/primaite/config/_package_data/training/training_config_main.yaml b/src/primaite/config/_package_data/training/training_config_main.yaml index a626e6c6..91deee71 100644 --- a/src/primaite/config/_package_data/training/training_config_main.yaml +++ b/src/primaite/config/_package_data/training/training_config_main.yaml @@ -91,8 +91,6 @@ session_type: TRAIN_EVAL # The high value for the observation space observation_space_high_value: 1000000000 -# Choice whether to have an ALLOW or DENY implicit rule or not (TRUE or FALSE) -apply_implicit_rule: False # Implicit ACL firewall rule at end of ACL list to be the default action (ALLOW or DENY) implicit_acl_rule: DENY # Total number of ACL rules allowed in the environment diff --git a/src/primaite/config/training_config.py b/src/primaite/config/training_config.py index d74f5993..3e7fb603 100644 --- a/src/primaite/config/training_config.py +++ b/src/primaite/config/training_config.py @@ -99,11 +99,7 @@ class TrainingConfig: sb3_output_verbose_level: SB3OutputVerboseLevel = SB3OutputVerboseLevel.NONE "Stable Baselines3 learn/eval output verbosity level" - # Access Control List/Rules - apply_implicit_rule: str = True - "User choice to have Implicit ALLOW or DENY." - - implicit_acl_rule: RulePermissionType = RulePermissionType.ALLOW + implicit_acl_rule: RulePermissionType = RulePermissionType.DENY "ALLOW or DENY implicit firewall rule to go at the end of list of ACL list." max_number_acl_rules: int = 30 diff --git a/src/primaite/environment/primaite_env.py b/src/primaite/environment/primaite_env.py index b74fbbd3..1c3d733f 100644 --- a/src/primaite/environment/primaite_env.py +++ b/src/primaite/environment/primaite_env.py @@ -123,7 +123,6 @@ class Primaite(Env): # Create the Access Control List self.acl = AccessControlList( - self.training_config.apply_implicit_rule, self.training_config.implicit_acl_rule, self.training_config.max_number_acl_rules, ) @@ -1013,6 +1012,7 @@ class Primaite(Env): acl_rule_destination = item["destination"] acl_rule_protocol = item["protocol"] acl_rule_port = item["port"] + acl_rule_position = item["position"] self.acl.add_rule( acl_rule_permission, @@ -1020,7 +1020,7 @@ class Primaite(Env): acl_rule_destination, acl_rule_protocol, acl_rule_port, - 0, + acl_rule_position, ) def create_services_list(self, services): diff --git a/tests/config/obs_tests/laydown.yaml b/tests/config/obs_tests/laydown.yaml index ef77ce83..e45a92e5 100644 --- a/tests/config/obs_tests/laydown.yaml +++ b/tests/config/obs_tests/laydown.yaml @@ -91,6 +91,7 @@ destination: 192.168.1.2 protocol: TCP port: 80 + position: 0 - item_type: ACL_RULE id: '7' permission: ALLOW @@ -98,3 +99,4 @@ destination: 192.168.1.1 protocol: TCP port: 80 + position: 0 diff --git a/tests/config/obs_tests/main_config_ACCESS_CONTROL_LIST.yaml b/tests/config/obs_tests/main_config_ACCESS_CONTROL_LIST.yaml index cc31f7ca..927c9f44 100644 --- a/tests/config/obs_tests/main_config_ACCESS_CONTROL_LIST.yaml +++ b/tests/config/obs_tests/main_config_ACCESS_CONTROL_LIST.yaml @@ -17,8 +17,6 @@ num_train_episodes: 1 # Number of time_steps for training per episode num_train_steps: 5 -# Choice whether to have an ALLOW or DENY implicit rule or not (TRUE or FALSE) -apply_implicit_rule: True # Implicit ACL firewall rule at end of lists to be default action or no rule can be selected (ALLOW or DENY) implicit_acl_rule: DENY # Total number of ACL rules allowed in the environment diff --git a/tests/config/obs_tests/main_config_without_obs.yaml b/tests/config/obs_tests/main_config_without_obs.yaml index 21726f90..5abe4303 100644 --- a/tests/config/obs_tests/main_config_without_obs.yaml +++ b/tests/config/obs_tests/main_config_without_obs.yaml @@ -39,8 +39,6 @@ agent_load_file: C:\[Path]\[agent_saved_filename.zip] # Environment config values # The high value for the observation space observation_space_high_value: 1_000_000_000 -# Choice whether to have an ALLOW or DENY implicit rule or not (TRUE or FALSE) -apply_implicit_rule: True # Implicit ACL firewall rule at end of lists to be default action or no rule can be selected (ALLOW or DENY) implicit_acl_rule: DENY # Reward values diff --git a/tests/config/single_action_space_fixed_blue_actions_main_config.yaml b/tests/config/single_action_space_fixed_blue_actions_main_config.yaml index 4644c9d9..6a5ce126 100644 --- a/tests/config/single_action_space_fixed_blue_actions_main_config.yaml +++ b/tests/config/single_action_space_fixed_blue_actions_main_config.yaml @@ -37,10 +37,6 @@ load_agent: False # File path and file name of agent if you're loading one in agent_load_file: C:\[Path]\[agent_saved_filename.zip] - - -# Choice whether to have an ALLOW or DENY implicit rule or not (True or False) -apply_implicit_rule: True # Implicit ACL firewall rule at end of lists to be default action or no rule can be selected (ALLOW or DENY) implicit_acl_rule: DENY # Total number of ACL rules allowed in the environment diff --git a/tests/config/single_action_space_main_config.yaml b/tests/config/single_action_space_main_config.yaml index ef0f8064..00d2e2e1 100644 --- a/tests/config/single_action_space_main_config.yaml +++ b/tests/config/single_action_space_main_config.yaml @@ -47,7 +47,6 @@ agent_load_file: C:\[Path]\[agent_saved_filename.zip] observation_space_high_value: 1000000000 # Choice whether to have an ALLOW or DENY implicit rule or not (TRUE or FALSE) -apply_implicit_rule: True implicit_acl_rule: DENY max_number_acl_rules: 10 # Reward values diff --git a/tests/test_acl.py b/tests/test_acl.py index 0d00a778..088da5eb 100644 --- a/tests/test_acl.py +++ b/tests/test_acl.py @@ -7,7 +7,7 @@ from primaite.acl.acl_rule import ACLRule def test_acl_address_match_1(): """Test that matching IP addresses produce True.""" - acl = AccessControlList(True, "DENY", 10) + acl = AccessControlList("DENY", 10) rule = ACLRule("ALLOW", "192.168.1.1", "192.168.1.2", "TCP", "80") @@ -16,7 +16,7 @@ def test_acl_address_match_1(): def test_acl_address_match_2(): """Test that mismatching IP addresses produce False.""" - acl = AccessControlList(True, "DENY", 10) + acl = AccessControlList("DENY", 10) rule = ACLRule("ALLOW", "192.168.1.1", "192.168.1.2", "TCP", "80") @@ -25,7 +25,7 @@ def test_acl_address_match_2(): def test_acl_address_match_3(): """Test the ANY condition for source IP addresses produce True.""" - acl = AccessControlList(True, "DENY", 10) + acl = AccessControlList("DENY", 10) rule = ACLRule("ALLOW", "ANY", "192.168.1.2", "TCP", "80") @@ -34,7 +34,7 @@ def test_acl_address_match_3(): def test_acl_address_match_4(): """Test the ANY condition for dest IP addresses produce True.""" - acl = AccessControlList(True, "DENY", 10) + acl = AccessControlList("DENY", 10) rule = ACLRule("ALLOW", "192.168.1.1", "ANY", "TCP", "80") @@ -44,7 +44,7 @@ def test_acl_address_match_4(): def test_check_acl_block_affirmative(): """Test the block function (affirmative).""" # Create the Access Control List - acl = AccessControlList(True, "DENY", 10) + acl = AccessControlList("DENY", 10) # Create a rule acl_rule_permission = "ALLOW" @@ -62,14 +62,13 @@ def test_check_acl_block_affirmative(): acl_rule_port, acl_position_in_list, ) - print(len(acl.acl), "len of acl list\n", acl.acl[0]) assert acl.is_blocked("192.168.1.1", "192.168.1.2", "TCP", "80") == False def test_check_acl_block_negative(): """Test the block function (negative).""" # Create the Access Control List - acl = AccessControlList(True, "DENY", 10) + acl = AccessControlList("DENY", 10) # Create a rule acl_rule_permission = "DENY" @@ -94,7 +93,7 @@ def test_check_acl_block_negative(): def test_rule_hash(): """Test the rule hash.""" # Create the Access Control List - acl = AccessControlList(True, "DENY", 10) + acl = AccessControlList("DENY", 10) rule = ACLRule("DENY", "192.168.1.1", "192.168.1.2", "TCP", "80") hash_value_local = hash(rule) @@ -102,3 +101,65 @@ def test_rule_hash(): hash_value_remote = acl.get_dictionary_hash("DENY", "192.168.1.1", "192.168.1.2", "TCP", "80") assert hash_value_local == hash_value_remote + + +def test_delete_rule(): + """Adds 3 rules and deletes 1 rule and checks its deletion.""" + # Create the Access Control List + acl = AccessControlList("ALLOW", 10) + + # Create a first rule + acl_rule_permission = "DENY" + acl_rule_source = "192.168.1.1" + acl_rule_destination = "192.168.1.2" + acl_rule_protocol = "TCP" + acl_rule_port = "80" + acl_position_in_list = "0" + + acl.add_rule( + acl_rule_permission, + acl_rule_source, + acl_rule_destination, + acl_rule_protocol, + acl_rule_port, + acl_position_in_list, + ) + + # Create a second rule + acl_rule_permission = "DENY" + acl_rule_source = "20" + acl_rule_destination = "30" + acl_rule_protocol = "FTP" + acl_rule_port = "21" + acl_position_in_list = "2" + + acl.add_rule( + acl_rule_permission, + acl_rule_source, + acl_rule_destination, + acl_rule_protocol, + acl_rule_port, + acl_position_in_list, + ) + + # Create a third rule + acl_rule_permission = "ALLOW" + acl_rule_source = "192.168.1.3" + acl_rule_destination = "192.168.1.1" + acl_rule_protocol = "UDP" + acl_rule_port = "60" + acl_position_in_list = "4" + + acl.add_rule( + acl_rule_permission, + acl_rule_source, + acl_rule_destination, + acl_rule_protocol, + acl_rule_port, + acl_position_in_list, + ) + # Remove the second ACL rule added from the list + acl.remove_rule("DENY", "20", "30", "FTP", "21") + + assert len(acl.acl) == 10 + assert acl.acl[2] is None