privateboss's picture
Update AINetworkDefense_Competitive/env.py
4e12424 verified
Raw
History Blame Contribute Delete
6.35 kB
import numpy as np
import gymnasium as gym
from pettingzoo import ParallelEnv
from config import Config
class CyberPettingZooEnv(ParallelEnv):
metadata = {'render_modes': ['human'], "name": "cyber_network_defense_v2_gnn"}
def __init__(self):
super().__init__()
self.topology = Config.NETWORK_TOPOLOGY
self.total_nodes = Config.TOTAL_HOSTS()
self.features_per_node = 2
self.possible_agents = ["red", "blue"]
self.agents = self.possible_agents[:]
self.observation_spaces = {
agent: gym.spaces.Dict({
'node_features': gym.spaces.Box(low=0.0, high=1.0, shape=(self.total_nodes, self.features_per_node), dtype=np.float32),
'adjacency_matrix': gym.spaces.Box(low=0.0, high=1.0, shape=(self.total_nodes, self.total_nodes), dtype=np.float32)
}) for agent in self.possible_agents
}
self.action_spaces = {
'red': gym.spaces.Discrete(self.total_nodes * 4),
'blue': gym.spaces.Discrete(self.total_nodes * 2)
}
self.node_id_to_specs = {}
self._build_network_topology_map()
self._generate_static_adjacency_matrix()
def _build_network_topology_map(self):
node_id = 0
for subnet_name, specs in self.topology["subnets"].items():
for _ in range(specs["num_hosts"]):
self.node_id_to_specs[node_id] = {
"subnet": subnet_name,
"base_vulnerability": specs["base_vulnerability"]
}
node_id += 1
def _generate_static_adjacency_matrix(self):
"""Programmatically wires lateral communication pathways between subnets."""
A = np.zeros((self.total_nodes, self.total_nodes), dtype=np.float32)
# Define cross-subnet routing permissions
allowed_connections = {
"public_dmz": ["public_dmz", "dns_services"],
"dns_services": ["public_dmz", "dns_services", "corporate", "active_directory"],
"corporate": ["dns_services", "corporate", "active_directory"],
"active_directory": ["dns_services", "corporate", "active_directory", "secure_core"],
"secure_core": ["active_directory", "secure_core"]
}
# Map connections across individual node indexes
for i in range(self.total_nodes):
for j in range(self.total_nodes):
sub_i = self.node_id_to_specs[i]["subnet"]
sub_j = self.node_id_to_specs[j]["subnet"]
if sub_j in allowed_connections[sub_i]:
A[i, j] = 1.0
# Add Self-Loops (A + I)
A_tilde = A + np.eye(self.total_nodes, dtype=np.float32)
# Compute Symmetric Degree Normalization:
row_sum = np.sum(A_tilde, axis=1)
d_inv_sqrt = np.power(row_sum, -0.5, where=row_sum > 0)
d_inv_sqrt[row_sum == 0] = 0.0
D_inv_sqrt = np.diag(d_inv_sqrt)
self.normalized_adj = np.matmul(np.matmul(D_inv_sqrt, A_tilde), D_inv_sqrt).astype(np.float32)
def reset(self, seed=None, options=None):
self.agents = self.possible_agents[:]
self.state_matrix = np.zeros((self.total_nodes, self.features_per_node), dtype=np.float32)
obs_dict = {
'node_features': self.state_matrix.copy(),
'adjacency_matrix': self.normalized_adj.copy()
}
observations = {'red': obs_dict, 'blue': obs_dict}
infos = {'score_difference': 0.0, 'team_progress': 0.0}
return observations, infos
def _is_subnet_compromised(self, subnet_name):
for node_id, specs in self.node_id_to_specs.items():
if specs["subnet"] == subnet_name and self.state_matrix[node_id, 0] == 1.0:
return True
return False
def step(self, actions):
if not actions:
self.agents = []
return {}, {}, {}, {}, {}
red_action = actions.get('red', 0)
blue_action = actions.get('blue', 0)
red_target_node = red_action // 4
red_action_type = red_action % 4
blue_target_node = blue_action // 2
blue_action_type = blue_action % 2
red_target_node = min(max(0, red_target_node), self.total_nodes - 1)
blue_target_node = min(max(0, blue_target_node), self.total_nodes - 1)
red_target_specs = self.node_id_to_specs[red_target_node]
red_target_subnet = red_target_specs["subnet"]
# Defense Execution
if blue_action_type == 1:
self.state_matrix[blue_target_node, 1] = 1.0
# Attack Execution with Infrastructure Co-Dependencies
if red_action_type == 1:
if self.state_matrix[red_target_node, 1] == 0.0:
exploit_probability = red_target_specs["base_vulnerability"]
if red_target_subnet == "secure_core":
if not self._is_subnet_compromised("active_directory"):
exploit_probability = 0.0
elif red_target_subnet == "active_directory":
if self._is_subnet_compromised("dns_services"):
exploit_probability = min(1.0, exploit_probability * 2.0)
if np.random.rand() < exploit_probability:
self.state_matrix[red_target_node, 0] = 1.0
# Telemetry Preparation
total_compromised = np.sum(self.state_matrix[:, 0])
total_patched = np.sum(self.state_matrix[:, 1])
score_diff = float(total_compromised - total_patched)
progress = float(total_compromised / self.total_nodes) if self.total_nodes > 0 else 0.0
obs_dict = {
'node_features': self.state_matrix.copy(),
'adjacency_matrix': self.normalized_adj.copy()
}
observations = {'red': obs_dict, 'blue': obs_dict}
rewards = {'red': 0.0, 'blue': 0.0}
terminations = {'red': False, 'blue': False}
truncations = {'red': False, 'blue': False}
infos = {'score_difference': score_diff, 'team_progress': progress}
return observations, rewards, terminations, truncations, infos