Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blocking actions, global defender, bugfixes #239

Closed
wants to merge 89 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
4c4d4f4
readme. Add documentation for BlockIP action
eldraco Jun 26, 2024
9eea200
game_components. Add BlockIP action
eldraco Jun 26, 2024
7496b73
netsecenv_conf. Delete the global defender and add a new defender wit…
eldraco Jun 26, 2024
7ea6cbc
utils. First adaptation to add a defender. Make the functions to read…
eldraco Jun 27, 2024
08710e5
netsecenv. Delete the simplistic defender in netsecenv. Refactor func…
eldraco Jun 27, 2024
0193c87
netsecenv. In the yaml conf, add the new action BlockIP and its prob …
eldraco Jun 27, 2024
228f173
Use latest agents
eldraco Jun 27, 2024
22c7648
netsecenv. Delete unused initializations pre-multiagents
eldraco Jul 17, 2024
14d0c91
netsecenv. Add new function to get goal description
eldraco Jul 17, 2024
11d0c83
netsecenv. Delete old create starting state. Migrated
eldraco Jul 17, 2024
e06b17b
netsecenv. Refactor so we dont use acronyms. Always very clear
eldraco Jul 17, 2024
c757ae1
netsecenv. Delete old reset() for the defender that does not exist an…
eldraco Jul 17, 2024
18781ef
netsecenv. Fix bug that the action was added twice to the list of pla…
eldraco Jul 17, 2024
2ff7b45
netsecenv conf. Migrate attackers to Attacker and defenders to Defend…
eldraco Jul 17, 2024
aea8632
utils. Migrate the functions to be generic and useful for attackers a…
eldraco Jul 17, 2024
7b30763
coor. Small refactor of error
eldraco Jul 17, 2024
9f97c16
game_components. Add better debugging
eldraco Jul 18, 2024
0bf50fd
netsecenv. Add BlockIP action
eldraco Jul 18, 2024
5054a6e
coord. Pring in log also when the goal was not reached
eldraco Jul 18, 2024
663b719
Improve main README for block actions
eldraco Jul 18, 2024
3a2e1de
components documentation. Add blockip description
eldraco Jul 18, 2024
bddfea6
In conf of the env for goals/start position. Add the known_blocks
eldraco Jul 18, 2024
fc652f6
game_comp. Add the known_blocks to the state
eldraco Jul 18, 2024
42e4f5b
netsecenv. Add the known_blocks to the state
eldraco Jul 18, 2024
b2c8085
utils. Add the known_blocks to the state
eldraco Jul 18, 2024
ddb4851
netsecenv. conf file adapt for defender
eldraco Aug 1, 2024
75399ca
test. Fix configuration of env
eldraco Aug 1, 2024
952a4f2
netsecenv_conf. Delete the global defender and add a new defender wit…
eldraco Jun 26, 2024
994f47f
netsecenv conf. Migrate attackers to Attacker and defenders to Defend…
eldraco Jul 17, 2024
d27bd59
In conf of the env for goals/start position. Add the known_blocks
eldraco Jul 18, 2024
9319e57
netsecenv. conf file adapt for defender
eldraco Aug 1, 2024
64c12b8
test. Fix configuration of env
eldraco Aug 1, 2024
9016286
README. Add the new defender agent conf and description
eldraco Aug 1, 2024
0731c52
utils. changes for ruff
eldraco Aug 1, 2024
c6940ba
netsecgame. Changes for ruff
eldraco Aug 1, 2024
14d00f9
netsecgame. Add the blocks to the state execution action.
eldraco Aug 1, 2024
bebeba0
coord. Add the blocks to the coordinator
eldraco Aug 1, 2024
7b78feb
Add termination variable to the server
ondrej-lukas Oct 7, 2024
5d8a92d
Fix proccessing of start state and goal desription
ondrej-lukas Oct 7, 2024
ee3cb1c
add support for a wild card 'all_local' in controlled hosts
ondrej-lukas Oct 7, 2024
84d2ea5
Improve start position for defender
ondrej-lukas Oct 7, 2024
50ac5b0
Fixed error in nto returning data in _execute_block_action
ondrej-lukas Oct 7, 2024
56a40a8
Fix return values
ondrej-lukas Oct 7, 2024
861927e
Fix restoring GameState Object from dict
ondrej-lukas Oct 7, 2024
b6c6fe2
Add 'known_blocks" to tests
ondrej-lukas Oct 7, 2024
961e953
remove unused code
ondrej-lukas Oct 8, 2024
d34ead6
More code cleanup
ondrej-lukas Oct 8, 2024
d9ee1a0
Add types to parameters and output
ondrej-lukas Oct 8, 2024
afde545
trajectories are stored in coordinator
ondrej-lukas Oct 8, 2024
4a8bd6f
Create Base world class
ondrej-lukas Oct 8, 2024
3f15fdd
create general class for the AIDojo world
ondrej-lukas Oct 8, 2024
08c472d
Move aidojo_world to separate file
ondrej-lukas Oct 8, 2024
6a42332
Add comments
ondrej-lukas Oct 8, 2024
d22ad68
Add default value to world_name argument
ondrej-lukas Oct 8, 2024
c6cbd30
Use world_type parameter to determine which environment to instantiate
ondrej-lukas Oct 8, 2024
d1dfb10
Separate code for the real-world playing into its own version of the …
ondrej-lukas Oct 8, 2024
9be6483
Add comments for clarity
ondrej-lukas Oct 8, 2024
c2d3b9b
Add real-world option to the environment selection
ondrej-lukas Oct 8, 2024
a89c42f
Add class description
ondrej-lukas Oct 8, 2024
37364e4
fix typo
ondrej-lukas Oct 8, 2024
7648e9d
remove action_type as action parameter
ondrej-lukas Oct 8, 2024
00bc7b0
removed unused code
ondrej-lukas Oct 8, 2024
650a92a
Add debug logging in block_action
ondrej-lukas Oct 9, 2024
bd8202c
Move worlds in the separate folder
ondrej-lukas Oct 9, 2024
3f7c6d1
Do not use Real World NSG as default
ondrej-lukas Oct 9, 2024
4cce0b0
Create a class for cyst wrapper
ondrej-lukas Oct 9, 2024
267e1db
fix graph building
ondrej-lukas Jul 25, 2024
8156dba
reset trajectory at the beginning of each episode
ondrej-lukas Jul 26, 2024
f92b8f5
Add optional parameter lo limit amount of trajectories to read
ondrej-lukas Jul 26, 2024
170acb5
Separate behavioral graphs code
ondrej-lukas Jul 26, 2024
f811637
fix Shutdown on ctrl+C
ondrej-lukas Jul 26, 2024
5fbc889
Do not work with zero-len trajectories
ondrej-lukas Sep 10, 2024
14d2427
Fix merging issues
ondrej-lukas Oct 9, 2024
f2001bf
Merge pull request #237 from stratosphereips/ondra-behavioral-graphs
ondrej-lukas Oct 9, 2024
49e5a25
add blocks to GameState ordered string
ondrej-lukas Oct 10, 2024
e905399
Set logging level to WARNING
ondrej-lukas Oct 16, 2024
305eaae
Add winrate to graphs
ondrej-lukas Oct 16, 2024
a367062
add backward compatibility to GS.from_dict
ondrej-lukas Oct 16, 2024
63b5ec1
Include only the required files in the Dockerfile copy
ondrej-lukas Oct 21, 2024
1ce2d64
Make sure to change the ip when createing the image
ondrej-lukas Oct 21, 2024
be9a4b8
Fixed correct paths
ondrej-lukas Oct 22, 2024
e500813
add init file to worlds
ondrej-lukas Oct 23, 2024
7d4d0de
fix path
ondrej-lukas Oct 23, 2024
05abd19
Add detection functionality
ondrej-lukas Oct 23, 2024
00cd35d
Add option to have global defender
ondrej-lukas Oct 23, 2024
7f3a959
Merge pull request #238 from stratosphereips/add-optional-gobal-defender
ondrej-lukas Oct 23, 2024
2d77899
Disable global defender by default
ondrej-lukas Oct 23, 2024
7e8b46c
Add update for the global defender in the readme
ondrej-lukas Oct 23, 2024
8d69a12
Fix typos and grammar README.md
ondrej-lukas Oct 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.git
.github
.gitignore
.gitmodules
.pytest_cache
.ruff_cache
.vscode
docs/
figures/
mlruns/
tests/
trajectories/
NetSecGameAgents/
notebooks/
readme_images/
tests/
*trajectories*.json
README.md
29 changes: 29 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Use an official Python 3.12 runtime as a parent image
FROM python:3.12-slim

# Set the working directory in the container
ENV DESTINATION_DIR=/aidojo


# Install system dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
git \
build-essential \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --upgrade pip

COPY . ${DESTINATION_DIR}/

# Set the working directory in the container
WORKDIR ${DESTINATION_DIR}

# Install any necessary Python dependencies
# If a requirements.txt file is in the repository
RUN if [ -f requirements.txt ]; then pip install --no-cache-dir -r requirements.txt; fi

# change the server ip to 0.0.0.0
RUN sed -i 's/"host": "127.0.0.1"/"host": "0.0.0.0"/' coordinator.conf

# Run the Python script when the container launches
CMD ["python3", "coordinator.py"]
130 changes: 66 additions & 64 deletions README.md

Large diffs are not rendered by default.

83 changes: 66 additions & 17 deletions coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
import json
import asyncio
from datetime import datetime
from env.network_security_game import NetworkSecurityEnvironment
from env.worlds.network_security_game import NetworkSecurityEnvironment
from env.worlds.network_security_game_real_world import NetworkSecurityEnvironmentRealWorld
from env.worlds.aidojo_world import AIDojoWorld
from env.game_components import Action, Observation, ActionType, GameStatus, GameState
from utils.utils import observation_as_dict, get_logging_level
from pathlib import Path
import os
import signal
from env.global_defender import stochastic_with_threshold

class AIDojo:
def __init__(self, host: str, port: int, net_sec_config: str, world_type) -> None:
Expand Down Expand Up @@ -90,7 +93,14 @@ def __init__(self, actions_queue, answers_queue, max_connections):
self.max_connections = max_connections
self.current_connections = 0
self.logger = logging.getLogger("AIDojo-Server")
self._stop = False

def close(self)->None:
self.logger.info(
"Stopping server"
)
self._stop = True

async def handle_new_agent(self, reader, writer):
async def send_data_to_agent(writer, data: str) -> None:
"""
Expand All @@ -113,7 +123,7 @@ async def send_data_to_agent(writer, data: str) -> None:
try:
addr = writer.get_extra_info("peername")
self.logger.info(f"New agent connected: {addr}")
while True:
while not self._stop:
data = await reader.read(500)
raw_message = data.decode().strip()
if len(raw_message):
Expand Down Expand Up @@ -151,23 +161,36 @@ async def send_data_to_agent(writer, data: str) -> None:
finally:
# Decrement the count of current connections
self.current_connections -= 1
writer.close()
return

async def __call__(self, reader, writer):
await self.handle_new_agent(reader, writer)


class Coordinator:
def __init__(self, actions_queue, answers_queue, net_sec_config, allowed_roles, world_type="netsecenv"):
# communication channels for asyncio
self._actions_queue = actions_queue
self._answers_queue = answers_queue
self.ALLOWED_ROLES = allowed_roles
self.logger = logging.getLogger("AIDojo-Coordinator")
self._world = NetworkSecurityEnvironment(net_sec_config)
# world definition
match world_type:
case "netsecenv":
self._world = NetworkSecurityEnvironment(net_sec_config)
case "netsecenv-real-world":
self._world = NetworkSecurityEnvironmentRealWorld(net_sec_config)
case _:
self._world = AIDojoWorld(net_sec_config)
self.world_type = world_type



self._starting_positions_per_role = self._get_starting_position_per_role()
self._win_conditions_per_role = self._get_win_condition_per_role()
self._goal_description_per_role = self._get_goal_description_per_role()
self._steps_limit = self._world.task_config.get_max_steps()

self._use_global_defender = self._world.task_config.get_use_global_defender()
# player information
self.agents = {}
# step counter per agent_addr (int)
Expand All @@ -182,13 +205,15 @@ def __init__(self, actions_queue, answers_queue, net_sec_config, allowed_roles,
# goal reach status per agent_addr (bool)
self._agent_goal_reached = {}
self._agent_episode_ends = {}
self._agent_detected = {}
# trajectories per agent_addr
self._agent_trajectories = {}

@property
def episode_end(self)->bool:
# Terminate episode if at least one player wins or reaches the timeout
return any(self._agent_episode_ends.values())
self.logger.debug(f"End evaluation: {self._agent_episode_ends.values()}")
return all(self._agent_episode_ends.values())

def convert_msg_dict_to_json(self, msg_dict)->str:
try:
Expand Down Expand Up @@ -237,7 +262,7 @@ async def run(self):
self.logger.info(f"Coordinator received from RESET request from agent {agent_addr}")
if all(self._reset_requests.values()):
# should we discard the queue here?
self.logger.info(f"All agents requested reset, action_q:{self._actions_queue.empty()}, answers_q{self._answers_queue.empty()}")
self.logger.info(f"All agents requested reset, action_q:{self._actions_queue.empty()}, answers_q:{self._answers_queue.empty()}")
self._world.reset()
self._get_goal_description_per_role()
self._get_win_condition_per_role()
Expand Down Expand Up @@ -265,7 +290,7 @@ async def run(self):
except asyncio.CancelledError:
self.logger.info("\tTerminating by CancelledError")
except Exception as e:
self.logger.error(f"Exception in main_coordinator(): {e}")
self.logger.error(f"Exception in Class coordinator(): {e}")
raise e

def _initialize_new_player(self, agent_addr:tuple, agent_name:str, agent_role:str) -> Observation:
Expand All @@ -280,8 +305,9 @@ def _initialize_new_player(self, agent_addr:tuple, agent_name:str, agent_role:st
self._agent_starting_position[agent_addr] = self._starting_positions_per_role[agent_role]
self._agent_states[agent_addr] = self._world.create_state_from_view(self._agent_starting_position[agent_addr])
self._agent_goal_reached[agent_addr] = self._goal_reached(agent_addr)
self._agent_detected[agent_addr] = self._check_detection(agent_addr, None)
self._agent_episode_ends[agent_addr] = False
if self._world.task_config.get_store_trajectories():
if self._world.task_config.get_store_trajectories() or self._use_global_defender:
self._agent_trajectories[agent_addr] = self._reset_trajectory(agent_addr)
self.logger.info(f"\tAgent {agent_name} ({agent_addr}), registred as {agent_role}")
return Observation(self._agent_states[agent_addr], 0, False, {})
Expand Down Expand Up @@ -324,7 +350,7 @@ def _get_win_condition_per_role(self)-> dict:
win_conditions = {}
for agent_role in self.ALLOWED_ROLES:
try:
win_conditions[agent_role] = self._world.re_map_goal_dict(
win_conditions[agent_role] = self._world.update_goal_dict(
self._world.task_config.get_win_conditions(agent_role=agent_role)
)
except KeyError:
Expand Down Expand Up @@ -394,10 +420,11 @@ def _create_response_to_reset_game_action(self, agent_addr: tuple) -> dict:
f"Coordinator responding to RESET request from agent {agent_addr}"
)
# store trajectory in file if needed
self._store_trajectory_to_file(agent_addr)
if self._world.task_config.get_store_trajectories():
self._store_trajectory_to_file(agent_addr)
new_observation = Observation(self._agent_states[agent_addr], 0, self.episode_end, {})
# reset trajectory
self._reset_trajectory(agent_addr)
self._agent_trajectories[agent_addr] = self._reset_trajectory(agent_addr)
output_message_dict = {
"to_agent": agent_addr,
"status": str(GameStatus.OK),
Expand Down Expand Up @@ -458,9 +485,11 @@ def _process_generic_action(self, agent_addr: tuple, action: Action) -> dict:

current_state = self._agent_states[agent_addr]
# Build new Observation for the agent
self._agent_states[agent_addr] = self._world.step(current_state, action, agent_addr, self.world_type)
self._agent_states[agent_addr] = self._world.step(current_state, action, agent_addr)
self._agent_goal_reached[agent_addr] = self._goal_reached(agent_addr)

self._agent_detected[agent_addr] = self._check_detection(agent_addr, action)

reward = self._world._rewards["step"]
obs_info = {}
end_reason = None
Expand All @@ -473,6 +502,11 @@ def _process_generic_action(self, agent_addr: tuple, action: Action) -> dict:
self._agent_episode_ends[agent_addr] = True
obs_info = {"end_reason": "max_steps"}
end_reason = "max_steps"
elif self._agent_detected[agent_addr]:
reward += self._world._rewards["detection"]
self._agent_episode_ends[agent_addr] = True
obs_info = {"end_reason": "max_steps"}

# record step in trajecory
self._add_step_to_trajectory(agent_addr, action, reward,self._agent_states[agent_addr], end_reason)
new_observation = Observation(self._agent_states[agent_addr], reward, self.episode_end, info=obs_info)
Expand Down Expand Up @@ -522,10 +556,12 @@ def _goal_reached(self, agent_addr:tuple)->bool:
self.logger.info(f"Goal check for {agent_addr}({self.agents[agent_addr][1]})")
agents_state = self._agent_states[agent_addr]
agent_role = self.agents[agent_addr][1]
win_condition = self._world.re_map_goal_dict(self._win_conditions_per_role[agent_role])
win_condition = self._world.update_goal_dict(self._win_conditions_per_role[agent_role])
goal_check = self._check_goal(agents_state, win_condition)
if goal_check:
self.logger.info("\tGoal reached!")
else:
self.logger.info("\tGoal not reached!")
return goal_check

def _check_goal(self, state:GameState, goal_conditions:dict)->bool:
Expand Down Expand Up @@ -556,10 +592,23 @@ def goal_dict_satistfied(goal_dict:dict, known_dict: dict)-> bool:
goal_reached["controlled_hosts"] = set(goal_conditions["controlled_hosts"]) <= set(state.controlled_hosts)
goal_reached["services"] = goal_dict_satistfied(goal_conditions["known_services"], state.known_services)
goal_reached["data"] = goal_dict_satistfied(goal_conditions["known_data"], state.known_data)
goal_reached["known_blocks"] = goal_dict_satistfied(goal_conditions["known_blocks"], state.known_blocks)
self.logger.debug(f"\t{goal_reached}")
return all(goal_reached.values())


def _check_detection(self, agent_addr:tuple, last_action:Action)->bool:
self.logger.info(f"Detection check for {agent_addr}({self.agents[agent_addr][1]})")
detection = False
if last_action:
if self._use_global_defender:
self.logger.warning("Global defender - ONLY use for backward compatibility!")
episode_actions = self._agent_trajectories[agent_addr]["actions"] if "actions" in self._agent_trajectories[agent_addr] else []
detection = stochastic_with_threshold(last_action, episode_actions)
if detection:
self.logger.info("\tDetected!")
else:
self.logger.info("\tNot detected!")
return detection
__version__ = "v0.2.1"


Expand Down Expand Up @@ -601,7 +650,7 @@ def goal_dict_satistfied(goal_dict:dict, known_dict: dict)-> bool:
action="store",
required=False,
type=str,
default="WARNING",
default="INFO",
)

args = parser.parse_args()
Expand Down Expand Up @@ -641,4 +690,4 @@ def goal_dict_satistfied(goal_dict:dict, known_dict: dict)-> bool:
# Create AI Dojo
ai_dojo = AIDojo(host, port, task_config_file, world_type)
# Run it!
ai_dojo.run()
ai_dojo.run()
1 change: 1 addition & 0 deletions docs/Components.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ GameState is an object that represents a view of the NetSecGame environment in a
- `known_services`: Dictionary of services that the agent is aware of.
The dictionary format: {`IP`: {`Service`}} where [IP](#ip) object is a key and the value is a set of [Service](#service) objects located in the `IP`.
- `known_data`: Dictionary of data instances that the agent is aware of. The dictionary format: {`IP`: {`Data`}} where [IP](#ip) object is a key and the value is a set of [Data](#data) objects located in the `IP`.
- `known_blocks`: Dictionary of firewall blocks the agent is aware of. It is a dictionary with format: {`target_IP`: {`blocked_IP`, `blocked_IP`}}. Where `target_IP` is the [IP](#ip) where the FW rule was applied (usually a router) and `blocked_IP` is the IP address that is blocked. For now the blocks happen in both input and output direction simultaneously.


## Actions
Expand Down
Loading
Loading