Initial commit of v1.0.0. Updated the .gitignore for the standard Python gitignore. Added Azure DevOps release pipeline for proper artifact release from the start.
This commit is contained in:
1
PRIMAITE/nodes/__init__.py
Normal file
1
PRIMAITE/nodes/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Crown Copyright (C) Dstl 2022. DEFCON 703. Shared in confidence.
|
||||
95
PRIMAITE/nodes/active_node.py
Normal file
95
PRIMAITE/nodes/active_node.py
Normal file
@@ -0,0 +1,95 @@
|
||||
# Crown Copyright (C) Dstl 2022. DEFCON 703. Shared in confidence.
|
||||
"""
|
||||
An Active Node (i.e. not an actuator)
|
||||
"""
|
||||
|
||||
from nodes.node import Node
|
||||
from common.enums import *
|
||||
|
||||
class ActiveNode(Node):
|
||||
"""
|
||||
Active Node class
|
||||
"""
|
||||
|
||||
def __init__(self, _id, _name, _type, _priority, _state, _ip_address, _os_state, _config_values):
|
||||
"""
|
||||
Init
|
||||
|
||||
Args:
|
||||
_id: The node ID
|
||||
_name: The node name
|
||||
_type: The node type (enum)
|
||||
_priority: The node priority (enum)
|
||||
_state: The node state (enum)
|
||||
_ip_address: The node IP address
|
||||
_os_state: The node Operating System state
|
||||
"""
|
||||
|
||||
super().__init__(_id, _name, _type, _priority, _state, _config_values)
|
||||
self.ip_address = _ip_address
|
||||
self.os_state = _os_state
|
||||
self.patching_count = 0
|
||||
|
||||
def set_ip_address(self, _ip_address):
|
||||
"""
|
||||
Sets IP address
|
||||
|
||||
Args:
|
||||
_ip_address: IP address
|
||||
"""
|
||||
|
||||
self.ip_address = _ip_address
|
||||
|
||||
def get_ip_address(self):
|
||||
"""
|
||||
Gets IP address
|
||||
|
||||
Returns:
|
||||
IP address
|
||||
"""
|
||||
return self.ip_address
|
||||
|
||||
def set_os_state(self, _os_state):
|
||||
"""
|
||||
Sets operating system state
|
||||
|
||||
Args:
|
||||
_os_state: Operating system state
|
||||
"""
|
||||
|
||||
self.os_state = _os_state
|
||||
if _os_state == SOFTWARE_STATE.PATCHING:
|
||||
self.patching_count = self.config_values.os_patching_duration
|
||||
|
||||
def set_os_state_if_not_compromised(self, _os_state):
|
||||
"""
|
||||
Sets operating system state if the node is not compromised
|
||||
|
||||
Args:
|
||||
_os_state: Operating system state
|
||||
"""
|
||||
|
||||
if self.os_state != SOFTWARE_STATE.COMPROMISED:
|
||||
self.os_state = _os_state
|
||||
if _os_state == SOFTWARE_STATE.PATCHING:
|
||||
self.patching_count = self.config_values.os_patching_duration
|
||||
|
||||
def get_os_state(self):
|
||||
"""
|
||||
Gets operating system state
|
||||
|
||||
Returns:
|
||||
Operating system state
|
||||
"""
|
||||
|
||||
return self.os_state
|
||||
|
||||
def update_os_patching_status(self):
|
||||
"""
|
||||
Updates operating system status based on patching cycle
|
||||
"""
|
||||
|
||||
self.patching_count -= 1
|
||||
if self.patching_count <= 0:
|
||||
self.patching_count = 0
|
||||
self.os_state = SOFTWARE_STATE.GOOD
|
||||
176
PRIMAITE/nodes/node.py
Normal file
176
PRIMAITE/nodes/node.py
Normal file
@@ -0,0 +1,176 @@
|
||||
# Crown Copyright (C) Dstl 2022. DEFCON 703. Shared in confidence.
|
||||
"""
|
||||
The base Node class
|
||||
"""
|
||||
|
||||
from common.enums import *
|
||||
|
||||
class Node:
|
||||
"""
|
||||
Node class
|
||||
"""
|
||||
|
||||
def __init__(self, _id, _name, _type, _priority, _state, _config_values):
|
||||
"""
|
||||
Init
|
||||
|
||||
Args:
|
||||
_id: The node id
|
||||
_name: The name of the node
|
||||
_type: The type of the node
|
||||
_priority: The priority of the node
|
||||
_state: The state of the node
|
||||
"""
|
||||
|
||||
self.id = _id
|
||||
self.name = _name
|
||||
self.type = _type
|
||||
self.priority = _priority
|
||||
self.operating_state = _state
|
||||
self.resetting_count = 0
|
||||
self.config_values = _config_values
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Returns the name of the node
|
||||
"""
|
||||
|
||||
return self.name
|
||||
|
||||
def set_id(self, _id):
|
||||
"""
|
||||
Sets the node ID
|
||||
|
||||
Args:
|
||||
_id: The node ID
|
||||
"""
|
||||
|
||||
self.id = _id
|
||||
|
||||
def get_id(self):
|
||||
"""
|
||||
Gets the node ID
|
||||
|
||||
Returns:
|
||||
The node ID
|
||||
"""
|
||||
|
||||
return self.id
|
||||
|
||||
def set_name(self, _name):
|
||||
"""
|
||||
Sets the node name
|
||||
|
||||
Args:
|
||||
_name: The node name
|
||||
"""
|
||||
|
||||
self.name = _name
|
||||
|
||||
def get_name(self):
|
||||
"""
|
||||
Gets the node name
|
||||
|
||||
Returns:
|
||||
The node name
|
||||
"""
|
||||
|
||||
return self.name
|
||||
|
||||
def set_type(self, _type):
|
||||
"""
|
||||
Sets the node type
|
||||
|
||||
Args:
|
||||
_type: The node type
|
||||
"""
|
||||
|
||||
self.type = _type
|
||||
|
||||
def get_type(self):
|
||||
"""
|
||||
Gets the node type
|
||||
|
||||
Returns:
|
||||
The node type
|
||||
"""
|
||||
|
||||
return self.type
|
||||
|
||||
def set_priority(self, _priority):
|
||||
"""
|
||||
Sets the node priority
|
||||
|
||||
Args:
|
||||
_priority: The node priority
|
||||
"""
|
||||
|
||||
self.priority = _priority
|
||||
|
||||
def get_priority(self):
|
||||
"""
|
||||
Gets the node priority
|
||||
|
||||
Returns:
|
||||
The node priority
|
||||
"""
|
||||
|
||||
return self.priority
|
||||
|
||||
def set_state(self, _state):
|
||||
"""
|
||||
Sets the node state
|
||||
|
||||
Args:
|
||||
_state: The node state
|
||||
"""
|
||||
|
||||
self.operating_state = _state
|
||||
|
||||
def get_state(self):
|
||||
"""
|
||||
Gets the node operating state
|
||||
|
||||
Returns:
|
||||
The node operating state
|
||||
"""
|
||||
|
||||
return self.operating_state
|
||||
|
||||
def turn_on(self):
|
||||
"""
|
||||
Sets the node state to ON
|
||||
"""
|
||||
|
||||
self.operating_state = HARDWARE_STATE.ON
|
||||
|
||||
def turn_off(self):
|
||||
"""
|
||||
Sets the node state to OFF
|
||||
"""
|
||||
|
||||
self.operating_state = HARDWARE_STATE.OFF
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Sets the node state to Resetting and starts the reset count
|
||||
"""
|
||||
|
||||
self.operating_state = HARDWARE_STATE.RESETTING
|
||||
self.resetting_count = self.config_values.node_reset_duration
|
||||
|
||||
def update_resetting_status(self):
|
||||
"""
|
||||
Updates the resetting count
|
||||
"""
|
||||
|
||||
self.resetting_count -= 1
|
||||
if self.resetting_count <= 0:
|
||||
self.resetting_count = 0
|
||||
self.operating_state = HARDWARE_STATE.ON
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
104
PRIMAITE/nodes/node_state_instruction.py
Normal file
104
PRIMAITE/nodes/node_state_instruction.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# Crown Copyright (C) Dstl 2022. DEFCON 703. Shared in confidence.
|
||||
"""
|
||||
Defines node behaviour for PoL
|
||||
"""
|
||||
|
||||
class NodeStateInstruction(object):
|
||||
"""
|
||||
The Node State Instruction class
|
||||
"""
|
||||
|
||||
def __init__(self, _id, _start_step, _end_step, _node_id, _node_pol_type, _service_name, _state, _is_entry_node=False):
|
||||
"""
|
||||
Init
|
||||
|
||||
Args:
|
||||
_id: The node state instruction id
|
||||
_start_step: The start step of the instruction
|
||||
_end_step: The end step of the instruction
|
||||
_node_id: The id of the associated node
|
||||
_node_pol_type: The pattern of life type
|
||||
_service_name: The service name
|
||||
_state: The state (node or service)
|
||||
_is_entry_node: Indicator for entry node (default = False)
|
||||
"""
|
||||
|
||||
self.id = _id
|
||||
self.start_step = _start_step
|
||||
self.end_step = _end_step
|
||||
self.node_id = _node_id
|
||||
self.node_pol_type = _node_pol_type
|
||||
self.service_name = _service_name # Not used when not a service instruction
|
||||
self.state = _state
|
||||
self.is_entry_node = _is_entry_node
|
||||
|
||||
def get_start_step(self):
|
||||
"""
|
||||
Gets the start step
|
||||
|
||||
Returns:
|
||||
The start step
|
||||
"""
|
||||
|
||||
return self.start_step
|
||||
|
||||
def get_end_step(self):
|
||||
"""
|
||||
Gets the end step
|
||||
|
||||
Returns:
|
||||
The end step
|
||||
"""
|
||||
|
||||
return self.end_step
|
||||
|
||||
def get_node_id(self):
|
||||
"""
|
||||
Gets the node ID
|
||||
|
||||
Returns:
|
||||
The node ID
|
||||
"""
|
||||
|
||||
return self.node_id
|
||||
|
||||
def get_node_pol_type(self):
|
||||
"""
|
||||
Gets the node pattern of life type (enum)
|
||||
|
||||
Returns:
|
||||
The node pattern of life type (enum)
|
||||
"""
|
||||
|
||||
return self.node_pol_type
|
||||
|
||||
def get_service_name(self):
|
||||
"""
|
||||
Gets the service name
|
||||
|
||||
Returns:
|
||||
The service name
|
||||
"""
|
||||
|
||||
return self.service_name
|
||||
|
||||
def get_state(self):
|
||||
"""
|
||||
Gets the state (node or service)
|
||||
|
||||
Returns:
|
||||
The state (node or service)
|
||||
"""
|
||||
|
||||
return self.state
|
||||
|
||||
def get_is_entry_node(self):
|
||||
"""
|
||||
Informs of entry node
|
||||
|
||||
Returns:
|
||||
True if entry node
|
||||
"""
|
||||
|
||||
return self.is_entry_node
|
||||
|
||||
37
PRIMAITE/nodes/passive_node.py
Normal file
37
PRIMAITE/nodes/passive_node.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# Crown Copyright (C) Dstl 2022. DEFCON 703. Shared in confidence.
|
||||
"""
|
||||
The Passive Node class (i.e. an actuator)
|
||||
"""
|
||||
|
||||
from nodes.node import Node
|
||||
|
||||
class PassiveNode(Node):
|
||||
"""
|
||||
The Passive Node class
|
||||
"""
|
||||
|
||||
def __init__(self, _id, _name, _type, _priority, _state, _config_values):
|
||||
"""
|
||||
Init
|
||||
|
||||
Args:
|
||||
_id: The node id
|
||||
_name: The name of the node
|
||||
_type: The type of the node
|
||||
_priority: The priority of the node
|
||||
_state: The state of the node
|
||||
"""
|
||||
|
||||
# Pass through to Super for now
|
||||
super().__init__(_id, _name, _type, _priority, _state, _config_values)
|
||||
|
||||
def get_ip_address(self):
|
||||
"""
|
||||
Gets the node IP address
|
||||
|
||||
Returns:
|
||||
The node IP address
|
||||
"""
|
||||
|
||||
# No concept of IP address for passive nodes for now
|
||||
return ""
|
||||
161
PRIMAITE/nodes/service_node.py
Normal file
161
PRIMAITE/nodes/service_node.py
Normal file
@@ -0,0 +1,161 @@
|
||||
# Crown Copyright (C) Dstl 2022. DEFCON 703. Shared in confidence.
|
||||
"""
|
||||
A Service Node (i.e. not an actuator)
|
||||
"""
|
||||
|
||||
from nodes.active_node import ActiveNode
|
||||
from common.enums import *
|
||||
|
||||
class ServiceNode(ActiveNode):
|
||||
"""
|
||||
ServiceNode class
|
||||
"""
|
||||
|
||||
def __init__(self, _id, _name, _type, _priority, _state, _ip_address, _os_state, _config_values):
|
||||
"""
|
||||
Init
|
||||
|
||||
Args:
|
||||
_id: The node id
|
||||
_name: The name of the node
|
||||
_type: The type of the node
|
||||
_priority: The priority of the node
|
||||
_state: The state of the node
|
||||
_ipAddress: The IP address of the node
|
||||
_osState: The operating system state of the node
|
||||
"""
|
||||
|
||||
super().__init__(_id, _name, _type, _priority, _state, _ip_address, _os_state, _config_values)
|
||||
self.services = {}
|
||||
|
||||
def add_service(self, _service):
|
||||
"""
|
||||
Adds a service to the node
|
||||
|
||||
Args:
|
||||
_service: The service to add
|
||||
"""
|
||||
|
||||
self.services[_service.get_name()] = _service
|
||||
|
||||
def get_services(self):
|
||||
"""
|
||||
Gets the dictionary of services on this node
|
||||
|
||||
Returns:
|
||||
Dictionary of services on this node
|
||||
"""
|
||||
|
||||
return self.services
|
||||
|
||||
def has_service(self, _protocol):
|
||||
"""
|
||||
Indicates whether a service is on a node
|
||||
|
||||
Returns:
|
||||
True if service (protocol) is on the node
|
||||
"""
|
||||
|
||||
for service_key, service_value in self.services.items():
|
||||
if service_key == _protocol:
|
||||
return True
|
||||
else:
|
||||
pass
|
||||
return False
|
||||
|
||||
def service_running(self, _protocol):
|
||||
"""
|
||||
Indicates whether a service is in a running state on the node
|
||||
|
||||
Returns:
|
||||
True if service (protocol) is in a running state on the node
|
||||
"""
|
||||
|
||||
for service_key, service_value in self.services.items():
|
||||
if service_key == _protocol:
|
||||
if service_value.get_state() != SOFTWARE_STATE.PATCHING:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
pass
|
||||
return False
|
||||
|
||||
def service_is_overwhelmed(self, _protocol):
|
||||
"""
|
||||
Indicates whether a service is in an overwhelmed state on the node
|
||||
|
||||
Returns:
|
||||
True if service (protocol) is in an overwhelmed state on the node
|
||||
"""
|
||||
|
||||
for service_key, service_value in self.services.items():
|
||||
if service_key == _protocol:
|
||||
if service_value.get_state() == SOFTWARE_STATE.OVERWHELMED:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
pass
|
||||
return False
|
||||
|
||||
def set_service_state(self, _protocol, _state):
|
||||
"""
|
||||
Sets the state of a service (protocol) on the node
|
||||
|
||||
Args:
|
||||
_protocol: The service (protocol)
|
||||
_state: The state value
|
||||
"""
|
||||
|
||||
for service_key, service_value in self.services.items():
|
||||
if service_key == _protocol:
|
||||
# Can't set to compromised if you're in a patching state
|
||||
if (_state == SOFTWARE_STATE.COMPROMISED and service_value.get_state() != SOFTWARE_STATE.PATCHING) or _state != SOFTWARE_STATE.COMPROMISED:
|
||||
service_value.set_state(_state)
|
||||
else:
|
||||
# Do nothing
|
||||
pass
|
||||
if _state == SOFTWARE_STATE.PATCHING:
|
||||
service_value.patching_count = self.config_values.service_patching_duration
|
||||
else:
|
||||
# Do nothing
|
||||
pass
|
||||
|
||||
def set_service_state_if_not_compromised(self, _protocol, _state):
|
||||
"""
|
||||
Sets the state of a service (protocol) on the node if the operating state is not "compromised"
|
||||
|
||||
Args:
|
||||
_protocol: The service (protocol)
|
||||
_state: The state value
|
||||
"""
|
||||
|
||||
for service_key, service_value in self.services.items():
|
||||
if service_key == _protocol:
|
||||
if service_value.get_state() != SOFTWARE_STATE.COMPROMISED:
|
||||
service_value.set_state(_state)
|
||||
if _state == SOFTWARE_STATE.PATCHING:
|
||||
service_value.patching_count = self.config_values.service_patching_duration
|
||||
|
||||
def get_service_state(self, _protocol):
|
||||
"""
|
||||
Gets the state of a service
|
||||
|
||||
Returns:
|
||||
The state of the service
|
||||
"""
|
||||
|
||||
for service_key, service_value in self.services.items():
|
||||
if service_key == _protocol:
|
||||
return service_value.get_state()
|
||||
|
||||
def update_services_patching_status(self):
|
||||
"""
|
||||
Updates the patching counter for any service that are patching
|
||||
"""
|
||||
|
||||
for service_key, service_value in self.services.items():
|
||||
if service_value.get_state() == SOFTWARE_STATE.PATCHING:
|
||||
service_value.reduce_patching_count()
|
||||
|
||||
Reference in New Issue
Block a user