199 lines
6.4 KiB
Python
199 lines
6.4 KiB
Python
# Crown Copyright (C) Dstl 2022. DEFCON 703. Shared in confidence.
|
|
"""A Service Node (i.e. not an actuator)."""
|
|
import logging
|
|
from typing import Final
|
|
|
|
from primaite.common.enums import HARDWARE_STATE, SOFTWARE_STATE
|
|
from primaite.nodes.active_node import ActiveNode
|
|
|
|
_LOGGER: Final[logging.Logger] = logging.getLogger(__name__)
|
|
|
|
|
|
class ServiceNode(ActiveNode):
|
|
"""ServiceNode class."""
|
|
|
|
def __init__(
|
|
self,
|
|
_id,
|
|
_name,
|
|
_type,
|
|
_priority,
|
|
_state,
|
|
_ip_address,
|
|
_os_state,
|
|
_file_system_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
|
|
_file_system_state: The file system state of the node
|
|
"""
|
|
super().__init__(
|
|
_id,
|
|
_name,
|
|
_type,
|
|
_priority,
|
|
_state,
|
|
_ip_address,
|
|
_os_state,
|
|
_file_system_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
|
|
"""
|
|
if self.operating_state != HARDWARE_STATE.OFF:
|
|
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
|
|
else:
|
|
_LOGGER.info(
|
|
f"The Nodes operating state is OFF so the state of a service "
|
|
f"cannot be changed. "
|
|
f"Node:{self.id}, "
|
|
f"Node Operating State:{self.operating_state}, "
|
|
f"Node Service Protocol:{_protocol}, "
|
|
f"Node Service State: {_state}"
|
|
)
|
|
|
|
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
|
|
"""
|
|
if self.operating_state != HARDWARE_STATE.OFF:
|
|
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
|
|
)
|
|
else:
|
|
_LOGGER.info(
|
|
f"The Nodes operating state is OFF so the state of a service "
|
|
f"cannot be changed. "
|
|
f"Node:{self.id}, "
|
|
f"Node Operating State:{self.operating_state}, "
|
|
f"Node Service Protocol:{_protocol}, "
|
|
f"Node Service State:{_state}"
|
|
)
|
|
|
|
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()
|