def test_get_node_pos(self):
     num_rows = 3
     num_cols = 3
     network_config = NetworkConfig(num_rows, num_cols)
     assert network_config.get_node_pos(0) == (0, 1)
     assert network_config.get_node_pos(1) == (1, 0)
     assert network_config.get_node_pos(2) == (1, 1)
     assert network_config.get_node_pos(3) == (1, 2)
     assert network_config.get_node_pos(4) == (2, 1)
 def test_get_coords_of_adjacency_matrix_id(self):
     num_rows = 2
     num_cols = 3
     network_config = NetworkConfig(num_rows, num_cols)
     assert network_config.get_coords_of_adjacency_matrix_id(0) == (0, 0)
     assert network_config.get_coords_of_adjacency_matrix_id(1) == (0, 1)
     assert network_config.get_coords_of_adjacency_matrix_id(2) == (0, 2)
     assert network_config.get_coords_of_adjacency_matrix_id(3) == (1, 0)
     assert network_config.get_coords_of_adjacency_matrix_id(4) == (1, 1)
     assert network_config.get_coords_of_adjacency_matrix_id(5) == (1, 2)
Example #3
0
 def randomize_attacker_position(self, network_config: NetworkConfig):
     temp_rows = list(range(1, network_config.num_rows))
     temp_cols = list(range(0, network_config.num_cols))
     positions = []
     for r in temp_rows:
         for c in temp_cols:
             node_id = network_config.get_node_id((r, c))
             if network_config.node_list[node_id] == NodeType.SERVER.value \
                     or network_config.node_list[node_id] == NodeType.START.value:
                 positions.append((r, c))
     rnd_idx = np.random.choice(list(range(len(positions))))
     rnd_pos = positions[rnd_idx]
     id = network_config.get_node_id(rnd_pos)
     if network_config.node_list[id] == NodeType.START.value:
         rnd_pos = network_config.start_pos
     self.attacker_pos = rnd_pos
 def test_node_list(self):
     num_rows = 3
     num_cols = 3
     network_config = NetworkConfig(num_rows, num_cols)
     node_list = network_config.node_list
     assert len(node_list) == num_rows*num_cols - 4
     for i in range(len(node_list)):
         assert node_list[i] is not None
 def test_initialization(self):
     num_rows = 2
     num_cols = 3
     network_config = NetworkConfig(num_rows, num_cols)
     assert network_config.graph_layout.shape == (num_rows, num_cols)
     for i in range(network_config.num_rows):
         for j in range(network_config.num_cols):
             assert network_config.graph_layout[i][j] is not None
     assert network_config.adjacency_matrix.shape == (num_rows*num_cols, num_rows*num_cols)
     for i in range(network_config.num_rows*network_config.num_cols):
         for j in range(network_config.num_cols*network_config.num_rows):
             assert network_config.adjacency_matrix[i][j] == 1 or network_config.adjacency_matrix[i][j] == 0
Example #6
0
 def test_copy(self):
     state = GameState()
     num_attack_types = 10
     rows = 4
     cols = 4
     network_config = NetworkConfig(rows, cols)
     num_nodes = len(network_config.node_list)
     state.default_state(list(range(num_nodes)), (3, 1), num_attack_types,
                         network_config)
     copy = state.copy()
     assert copy.num_hacks == state.num_hacks
     assert np.array_equal(copy.attack_values, state.attack_values)
     assert np.array_equal(copy.defense_det, state.defense_det)
     assert np.array_equal(copy.defense_values, state.defense_values)
 def test_get_node_id(self):
     num_rows = 3
     num_cols = 3
     network_config = NetworkConfig(num_rows, num_cols)
     assert network_config.get_node_id((0,1)) == 0
     assert network_config.get_node_id((1, 0)) == 1
     assert network_config.get_node_id((1, 1)) == 2
     assert network_config.get_node_id((1, 2)) == 3
     assert network_config.get_node_id((2, 1)) == 4
     assert network_config.get_node_id((2, 2)) == -1
     assert network_config.get_node_id((2, 0)) == -1
     assert network_config.get_node_id((0, 0)) == -1
     assert network_config.get_node_id((0, 2)) == -1
Example #8
0
 def test_default_state(self):
     state = GameState()
     rows = 4
     cols = 4
     network_config = NetworkConfig(rows, cols)
     num_nodes = len(network_config.node_list)
     num_attack_types = 10
     state.default_state(list(range(num_nodes)), (3, 1), num_attack_types,
                         network_config)
     assert state.attack_values.shape == (num_nodes, 10)
     assert state.defense_values.shape == (num_nodes, 10)
     assert state.defense_det.shape == (num_nodes, )
     assert state.attacker_pos == (3, 1)
     assert state.done == False
     assert state.hacked == False
     assert state.num_hacks == 0
     assert state.detected == False
     assert state.num_games == 0
Example #9
0
 def test_simulate_attack(self):
     rows = 4
     cols = 4
     network_config = NetworkConfig(rows, cols)
     num_nodes = len(network_config.node_list)
     state = GameState()
     num_attack_types = 10
     state.default_state(list(range(num_nodes)), (3, 1), num_attack_types,
                         network_config)
     attack_node_id = 3
     attack_type = 4
     state.defense_values[attack_node_id][attack_type] = 5
     state.attack_values[attack_node_id][attack_type] = 5
     assert not state.simulate_attack(attack_node_id, attack_type,
                                      network_config)
     state.defense_values[attack_node_id][attack_type] = 5
     state.attack_values[attack_node_id][attack_type] = 6
     assert state.simulate_attack(attack_node_id, attack_type,
                                  network_config)
Example #10
0
 def test_defend(self):
     state = GameState()
     rows = 4
     cols = 4
     network_config = NetworkConfig(rows, cols)
     num_nodes = len(network_config.node_list)
     num_attack_types = 10
     state.default_state(list(range(num_nodes)), (3, 1), num_attack_types,
                         network_config)
     defend_node_id = 3
     defense_type = 4
     max_value = 10
     old_count = state.defense_values[defend_node_id][defense_type]
     state.defend(defend_node_id, defense_type, max_value, network_config)
     assert state.defense_values[defend_node_id][defense_type] < max_value
     assert state.defense_values[defend_node_id][
         defense_type] == old_count + 1
     state.defense_values[defend_node_id][defense_type] = 10
     state.defend(defend_node_id, defense_type, max_value, network_config)
     assert state.defense_values[defend_node_id][defense_type] == max_value
Example #11
0
 def test_new_game(self):
     rows = 4
     cols = 4
     network_config = NetworkConfig(rows, cols)
     state = GameState()
     num_nodes = len(network_config.node_list)
     num_attack_types = 10
     state.default_state(list(range(num_nodes)), (3, 1), num_attack_types,
                         network_config)
     init_state = state.copy()
     old_game_count = state.num_games
     state.new_game(init_state)
     assert state.num_games == old_game_count + 1
     assert state.done == False
     assert state.detected == False
     state.default_state(list(range(num_nodes)), (3, 1), num_attack_types,
                         network_config)
     init_state = state.copy()
     state.hacked = True
     old_hacked_count = 0
     state.new_game(init_state)
     assert state.num_hacks == old_hacked_count + 1
Example #12
0
    def get_attacker_observation(
            self,
            network_config: NetworkConfig,
            local_view=False,
            reconnaissance=False,
            reconnaissance_bool_features=False) -> np.ndarray:
        """
        Converts the state of the dynamical system into an observation for the attacker. As the environment
        is a partially observed markov decision process, the attacker observation is only a subset of the game state

        :param network_config: the network configuration of the game
        :param local_view: boolean flag indicating whether observations are provided in a local view or not
        :param reconnaissance: boolean flag indicating whether reconnaissance states should be included
        :param reconnaissance_bool_features: boolean flag whether to include boolean features that indicate if
                                             reconnaissance have been done for a certain defense type
        :return: An observation of the environment
        """
        if not reconnaissance:
            # +1 to have an extra feature that indicates if this is the node that the attacker is currently in
            attack_observation = np.zeros((len(network_config.node_list),
                                           self.attack_values.shape[1] + 1))
        elif reconnaissance and not reconnaissance_bool_features:
            # +1 to have an extra feature that indicates if this is the node that the attacker is currently in
            attack_observation = np.zeros(
                (len(network_config.node_list),
                 (self.attack_values.shape[1] * 2 + 1)))
        else:
            # +2 to have an extra feature that indicates if this is the node that the attacker is currently in and reconnaissance bool feature
            attack_observation = np.zeros(
                (len(network_config.node_list),
                 (self.attack_values.shape[1] * 2 + 2)))

        current_pos = self.attacker_pos
        current_node_id = network_config.get_node_id(current_pos)
        current_row, current_col = current_pos
        current_adjacency_matrix_id = network_config.get_adjacency_matrix_id(
            current_row, current_col)

        if local_view:
            neighbors = []

        for node_id in range(len(network_config.node_list)):
            pos = network_config.get_node_pos(node_id)
            node_row, node_col = pos
            node_adjacency_matrix_id = network_config.get_adjacency_matrix_id(
                node_row, node_col)
            if local_view:
                if network_config.adjacency_matrix[current_adjacency_matrix_id][node_adjacency_matrix_id] \
                        and node_id != current_node_id:
                    if not reconnaissance:
                        neighbor_data = np.append(self.attack_values[node_id],
                                                  node_id)
                    elif reconnaissance and not reconnaissance_bool_features:
                        neighbor_data = np.append(
                            np.append(self.attack_values[node_id], node_id),
                            self.reconnaissance_state[node_id]),
                    else:
                        reconaissance_bool = [0]
                        if node_id in self.reconnaissance_actions:
                            reconaissance_bool = [1]
                        neighbor_data = np.append(
                            np.append(
                                np.append(self.attack_values[node_id],
                                          node_id),
                                self.reconnaissance_state[node_id]),
                            reconaissance_bool)
                    neighbor_row, neighbor_col = network_config.get_node_pos(
                        node_id)
                    neighbors.append(
                        (neighbor_row, neighbor_col, neighbor_data))
            else:
                if node_id == current_node_id:
                    if not reconnaissance:
                        attack_observation[node_id] = np.append(
                            self.attack_values[node_id], 1)
                    elif reconnaissance and not reconnaissance_bool_features:
                        attack_observation[node_id] = np.append(
                            np.append(self.attack_values[node_id], 1),
                            self.reconnaissance_state[node_id])
                    else:
                        reconaissance_bool = [0]
                        if node_id in self.reconnaissance_actions:
                            reconaissance_bool = [1]
                        attack_observation[node_id] = np.append(
                            np.append(
                                np.append(self.attack_values[node_id], 1),
                                self.reconnaissance_state[node_id]),
                            reconaissance_bool)
                elif network_config.fully_observed:
                    attack_observation[node_id] = np.append(
                        self.attack_values[node_id], 0)
                elif network_config.adjacency_matrix[
                        current_adjacency_matrix_id][node_adjacency_matrix_id]:
                    if not reconnaissance:
                        attack_observation[node_id] = np.append(
                            self.attack_values[node_id], 0)
                    elif reconnaissance and not reconnaissance_bool_features:
                        attack_observation[node_id] = np.append(
                            np.append(self.attack_values[node_id], 0),
                            self.reconnaissance_state[node_id])
                    else:
                        reconaissance_bool = [0]
                        if node_id in self.reconnaissance_actions:
                            reconaissance_bool = [1]
                        attack_observation[node_id] = np.append(
                            np.append(
                                np.append(self.attack_values[node_id], 0),
                                self.reconnaissance_state[node_id]),
                            reconaissance_bool)
                elif reconnaissance:
                    if not reconnaissance_bool_features:
                        attack_values = np.zeros((self.attack_values.shape[1]))
                        attack_observation[node_id] = np.append(
                            np.append(attack_values, 0),
                            self.reconnaissance_state[node_id])
                    else:
                        reconaissance_bool = [0]
                        if node_id in self.reconnaissance_actions:
                            reconaissance_bool = [1]
                        attack_values = np.zeros((self.attack_values.shape[1]))
                        attack_observation[node_id] = np.append(
                            np.append(np.append(attack_values, 0),
                                      self.reconnaissance_state[node_id]),
                            reconaissance_bool)

        if local_view:
            # sort by row then col
            sorted_neighbors = sorted(neighbors, key=lambda x: (x[0], x[1]))
            neighbor_data = np.array(
                list(map(lambda x: x[2], sorted_neighbors)))
            neighbor_ids = neighbor_data[:, self.attack_values.shape[1]]
            if not reconnaissance:
                local_view_obs = np.full((network_config.max_neighbors,
                                          self.attack_values.shape[1] + 1), -1)
            elif reconnaissance and not reconnaissance_bool_features:
                local_view_obs = np.full((network_config.max_neighbors,
                                          self.attack_values.shape[1] * 2 + 1),
                                         -1)
            else:
                local_view_obs = np.full((network_config.max_neighbors,
                                          self.attack_values.shape[1] * 2 + 2),
                                         -1)
            for n in range(network_config.max_neighbors):
                rel_neighbor_pos = network_config.relative_neighbor_positions[
                    n]
                neighbor_pos = (current_row + rel_neighbor_pos[0],
                                current_col + rel_neighbor_pos[1])
                for i in range(len(neighbor_ids)):
                    node_id = neighbor_ids[i]
                    node_pos = network_config.get_node_pos(node_id)
                    if node_pos == neighbor_pos and node_pos[0] <= current_row:
                        local_view_obs[n] = neighbor_data[i]
            attack_observation = np.array(local_view_obs)
        return attack_observation
Example #13
0
    def set_state(self,
                  node_list: List,
                  num_attack_types: int,
                  defense_val: int = 2,
                  attack_val: int = 0,
                  num_vulnerabilities_per_node: int = 1,
                  det_val: int = 2,
                  vulnerability_val: int = 0,
                  num_vulnerabilities_per_layer: int = 1,
                  network_config: NetworkConfig = None,
                  randomize_state: bool = False,
                  randomize_visibility: bool = False,
                  visibility_p: float = 0.5):
        """
        Sets the state

        :param node_list: list of nodes
        :param num_attack_types:  number of attack types
        :param defense_val: defense value for defense types that are not vulnerable
        :param attack_val: attack value for attack types
        :param num_vulnerabilities_per_node: number of vulnerabilities per node
        :param det_val: detection value per node
        :param vulnerability_val: defense value for defense types that are vulnerable
        :param num_vulnerabilities_per_layer: number of vulnerabilities per layer
        :param network_config: network configuration
        :param randomize_state: boolean flag whether to create the state randomly
        :param randomize_visibility: boolean flag whether to randomize visibility for partially observed envs
        :return: None
        """
        num_nodes = len(node_list)
        attack_values = np.zeros((num_nodes, num_attack_types))
        defense_values = np.zeros((num_nodes, num_attack_types))
        det_values = np.zeros(num_nodes)
        reconnaissance_state = np.full(
            (num_nodes, num_attack_types),
            constants.GAME_CONFIG.INITIAL_RECONNAISSANCE_STATE)

        d_val = defense_val
        a_val = attack_val
        de_val = det_val
        v_val = vulnerability_val

        vulnerabilities_per_layer = np.zeros(
            (network_config.num_rows, network_config.num_cols))
        for row in range(1, network_config.num_rows - 1):
            vulnerabilities = np.random.choice(
                network_config.num_cols,
                size=num_vulnerabilities_per_layer,
                replace=False)
            vulnerabilities_per_layer[row][vulnerabilities] = 1

        for node_id in range(num_nodes):
            row, col = network_config.get_node_pos(node_id)
            num_vuln = min(num_vulnerabilities_per_node, num_attack_types)
            vulnerabilities = []
            if vulnerabilities_per_layer[row][col] == 1 or node_list[
                    node_id] == NodeType.DATA.value:
                vulnerabilities = np.random.choice(
                    num_attack_types,
                    size=num_vuln)  # random vulnerability per node
            if node_list[node_id] == NodeType.DATA.value or node_list[
                    node_id] == NodeType.SERVER.value:
                d_vals = []
                a_vals = []
                for at in range(num_attack_types):
                    if randomize_state:
                        d_val = max(
                            self.min_random_d_val,
                            np.random.choice(
                                list(
                                    range(self.min_random_d_val,
                                          defense_val + 1))))
                        a_val = max(
                            self.min_random_a_val,
                            np.random.choice(
                                list(
                                    range(self.min_random_a_val,
                                          attack_val + 1))))
                        de_val = max(
                            self.min_random_det_val,
                            np.random.choice(
                                list(
                                    range(self.min_random_det_val,
                                          det_val + 1))))
                        v_val = max(
                            vulnerability_val,
                            np.random.choice(
                                list(
                                    range(vulnerability_val,
                                          self.max_random_v_val + 1))))
                    d_vals.append(d_val)
                    a_vals.append(a_val)
                defense_values[node_id] = d_vals
                det_values[node_id] = de_val
                attack_values[node_id] = a_vals
                for vuln_id in vulnerabilities:
                    defense_values[node_id][
                        vuln_id] = v_val  # vulnerability (lower defense)

        if randomize_visibility:
            for node_id in range(len(reconnaissance_state)):
                if np.random.rand() < visibility_p:
                    reconnaissance_state[node_id] = defense_values[node_id]
                    self.reconnaissance_actions.append(node_id)
        self.attack_values = attack_values.astype(np.int32)
        self.defense_values = defense_values.astype(np.int32)
        self.defense_det = det_values.astype(np.int32)
        self.reconnaissance_state = reconnaissance_state.astype(np.int32)
Example #14
0
 def test_data_col(self):
     num_rows = 2
     num_cols = 3
     network_config = NetworkConfig(num_rows, num_cols)
     assert network_config.data_col == 1
Example #15
0
 def test_start_row(self):
     num_rows = 2
     num_cols = 3
     network_config = NetworkConfig(num_rows, num_cols)
     assert network_config.start_row == 1
Example #16
0
    def __init__(self,
                 network_config: NetworkConfig = None,
                 manual_attacker: bool = True,
                 num_layers: int = 1,
                 num_servers_per_layer: int = 2,
                 num_attack_types: int = 10,
                 max_value: int = 9,
                 initial_state: GameState = None,
                 manual_defender: bool = False,
                 initial_state_path: str = None,
                 dense_rewards=False,
                 min_random_a_val: int = 0,
                 min_random_d_val: int = 0,
                 min_random_det_val: int = 0,
                 dense_rewards_v2=False,
                 reconnaissance_actions: bool = False,
                 max_random_v_val: int = 1,
                 dense_rewards_v3=False):
        """
        Class constructor, initializes the DTO

        :param network_config: the network configuration of the game (e.g. number of nodes and their connectivity)
        :param manual_attacker: whether the attacker is controlled manually or by an agent
        :param manual_attacker: whether the defender is controlled manually or by an agent
        :param num_layers: the number of layers in the network
        :param num_servers_per_layer: the number of servers per layer in the network
        :param num_attack_types: the number of attack types
        :param max_value: max value for a defense/attack attribute
        :param initial_state: the initial state
        :param initial_state_path: path to the initial state saved on disk
        :param dense_rewards: if true, give hacker dense rewards (reward for each intermediate server hacked)
        :param dense_rewards_v2: if true, give defender reward only when blocking
        :param min_random_a_val: minimum attack value when randomizing the state
        :param min_random_d_val: minimum defense value when randomizing the state
        :param min_random_det_val: minimum detection value when randomizing the state
        :param reconnaissance_actions: a boolean flag that indicates whether reconnaissance activities are enabled for
                                       the attacker
        :param max_random_v_val: maximum random vulnerability value when usign randomized environment
        """
        self.reconnaissance_actions = reconnaissance_actions
        self.manual_attacker = manual_attacker
        self.manual_defender = manual_defender
        self.num_layers = num_layers
        self.num_servers_per_layer = num_servers_per_layer
        self.num_attack_types = num_attack_types
        self.max_value = max_value
        self.min_random_a_val = min_random_a_val
        self.min_random_d_val = min_random_d_val
        self.min_random_det_val = min_random_det_val
        self.num_rows = self.num_layers + 2
        self.num_nodes = self.num_layers * self.num_servers_per_layer + 2  # +2 for Start and Data Nodes
        self.num_cols = self.num_servers_per_layer
        self.set_attack_actions()
        self.num_defense_actions = (self.num_attack_types + 1) * self.num_nodes
        self.num_states = self.num_nodes
        self.network_config = network_config
        self.initial_state_path = initial_state_path
        self.defense_val = 2
        self.attack_val = 0
        self.num_vulnerabilities_per_node = 1
        self.det_val = 2
        self.dense_rewards_v2 = dense_rewards_v2
        self.dense_rewards_v3 = dense_rewards_v3
        self.vulnerabilitiy_val = 0
        self.max_random_v_val = max_random_v_val
        self.num_vulnerabilities_per_layer = None
        if network_config is None:
            self.network_config = NetworkConfig(self.num_rows,
                                                self.num_cols,
                                                connected_layers=False)
        self.initial_state = initial_state
        if self.initial_state is None and self.initial_state_path is not None:
            self.initial_state = GameState.load(self.initial_state)
        if self.initial_state is None and self.initial_state_path is None:
            self.initial_state = GameState(
                min_random_a_val=min_random_a_val,
                min_random_det_val=min_random_det_val,
                min_random_d_val=min_random_d_val,
                max_value=self.max_value,
                max_random_v_val=self.max_random_v_val)
            self.initial_state.default_state(
                self.network_config.node_list,
                self.network_config.start_pos,
                self.num_attack_types,
                network_config=self.network_config,
            )
        self.dense_rewards = dense_rewards