Merged PR 541: Config item that allows services and applications to return actual health states without the need to scan
## Summary Added: - services_requires_scan - applications_requires_scan To allow the agents to get the actual health state of services and applications without the need to scan ## Test process https://dev.azure.com/ma-dev-uk/PrimAITE/_git/PrimAITE/pullrequest/541?_a=files&path=/tests/unit_tests/_primaite/_game/_agent/test_observations.py ## Checklist - [X] PR is linked to a **work item** - [X] **acceptance criteria** of linked ticket are met - [X] performed **self-review** of the code - [ ] written **tests** for any new functionality added with this PR - [ ] updated the **documentation** if this PR changes or adds functionality - [ ] written/updated **design docs** if this PR implements new functionality - [X] updated the **change log** - [X] ran **pre-commit** checks for code style - [ ] attended to any **TO-DOs** left in the code Related work items: #2864
This commit is contained in:
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Added
|
### Added
|
||||||
- Log observation space data by episode and step.
|
- Log observation space data by episode and step.
|
||||||
- ACL's are no longer applied to layer-2 traffic.
|
- ACL's are no longer applied to layer-2 traffic.
|
||||||
|
- Added `services_requires_scan` and `applications_requires_scan` to agent observation space config to allow the agents to be able to see actual health states of services and applications without requiring scans (Default `True`, set to `False` to allow agents to see actual health state without scanning).
|
||||||
|
|
||||||
## [3.3.0] - 2024-09-04
|
## [3.3.0] - 2024-09-04
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -52,6 +52,14 @@ class HostObservation(AbstractObservation, identifier="HOST"):
|
|||||||
"""
|
"""
|
||||||
If True, files and folders must be scanned to update the health state. If False, true state is always shown.
|
If True, files and folders must be scanned to update the health state. If False, true state is always shown.
|
||||||
"""
|
"""
|
||||||
|
services_requires_scan: Optional[bool] = None
|
||||||
|
"""
|
||||||
|
If True, services must be scanned to update the health state. If False, true state is always shown.
|
||||||
|
"""
|
||||||
|
applications_requires_scan: Optional[bool] = None
|
||||||
|
"""
|
||||||
|
If True, applications must be scanned to update the health state. If False, true state is always shown.
|
||||||
|
"""
|
||||||
include_users: Optional[bool] = True
|
include_users: Optional[bool] = True
|
||||||
"""If True, report user session information."""
|
"""If True, report user session information."""
|
||||||
|
|
||||||
@@ -71,6 +79,8 @@ class HostObservation(AbstractObservation, identifier="HOST"):
|
|||||||
monitored_traffic: Optional[Dict],
|
monitored_traffic: Optional[Dict],
|
||||||
include_num_access: bool,
|
include_num_access: bool,
|
||||||
file_system_requires_scan: bool,
|
file_system_requires_scan: bool,
|
||||||
|
services_requires_scan: bool,
|
||||||
|
applications_requires_scan: bool,
|
||||||
include_users: bool,
|
include_users: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -106,6 +116,12 @@ class HostObservation(AbstractObservation, identifier="HOST"):
|
|||||||
:param file_system_requires_scan: If True, the files and folders must be scanned to update the health state.
|
:param file_system_requires_scan: If True, the files and folders must be scanned to update the health state.
|
||||||
If False, the true state is always shown.
|
If False, the true state is always shown.
|
||||||
:type file_system_requires_scan: bool
|
:type file_system_requires_scan: bool
|
||||||
|
:param services_requires_scan: If True, services must be scanned to update the health state.
|
||||||
|
If False, the true state is always shown.
|
||||||
|
:type services_requires_scan: bool
|
||||||
|
:param applications_requires_scan: If True, applications must be scanned to update the health state.
|
||||||
|
If False, the true state is always shown.
|
||||||
|
:type applications_requires_scan: bool
|
||||||
:param include_users: If True, report user session information.
|
:param include_users: If True, report user session information.
|
||||||
:type include_users: bool
|
:type include_users: bool
|
||||||
"""
|
"""
|
||||||
@@ -119,7 +135,7 @@ class HostObservation(AbstractObservation, identifier="HOST"):
|
|||||||
# Ensure lists have lengths equal to specified counts by truncating or padding
|
# Ensure lists have lengths equal to specified counts by truncating or padding
|
||||||
self.services: List[ServiceObservation] = services
|
self.services: List[ServiceObservation] = services
|
||||||
while len(self.services) < num_services:
|
while len(self.services) < num_services:
|
||||||
self.services.append(ServiceObservation(where=None))
|
self.services.append(ServiceObservation(where=None, services_requires_scan=services_requires_scan))
|
||||||
while len(self.services) > num_services:
|
while len(self.services) > num_services:
|
||||||
truncated_service = self.services.pop()
|
truncated_service = self.services.pop()
|
||||||
msg = f"Too many services in Node observation space for node. Truncating service {truncated_service.where}"
|
msg = f"Too many services in Node observation space for node. Truncating service {truncated_service.where}"
|
||||||
@@ -127,7 +143,9 @@ class HostObservation(AbstractObservation, identifier="HOST"):
|
|||||||
|
|
||||||
self.applications: List[ApplicationObservation] = applications
|
self.applications: List[ApplicationObservation] = applications
|
||||||
while len(self.applications) < num_applications:
|
while len(self.applications) < num_applications:
|
||||||
self.applications.append(ApplicationObservation(where=None))
|
self.applications.append(
|
||||||
|
ApplicationObservation(where=None, applications_requires_scan=applications_requires_scan)
|
||||||
|
)
|
||||||
while len(self.applications) > num_applications:
|
while len(self.applications) > num_applications:
|
||||||
truncated_application = self.applications.pop()
|
truncated_application = self.applications.pop()
|
||||||
msg = f"Too many applications in Node observation space for node. Truncating {truncated_application.where}"
|
msg = f"Too many applications in Node observation space for node. Truncating {truncated_application.where}"
|
||||||
@@ -263,6 +281,10 @@ class HostObservation(AbstractObservation, identifier="HOST"):
|
|||||||
folder_config.file_system_requires_scan = config.file_system_requires_scan
|
folder_config.file_system_requires_scan = config.file_system_requires_scan
|
||||||
for nic_config in config.network_interfaces:
|
for nic_config in config.network_interfaces:
|
||||||
nic_config.include_nmne = config.include_nmne
|
nic_config.include_nmne = config.include_nmne
|
||||||
|
for service_config in config.services:
|
||||||
|
service_config.services_requires_scan = config.services_requires_scan
|
||||||
|
for application_config in config.applications:
|
||||||
|
application_config.applications_requires_scan = config.applications_requires_scan
|
||||||
|
|
||||||
services = [ServiceObservation.from_config(config=c, parent_where=where) for c in config.services]
|
services = [ServiceObservation.from_config(config=c, parent_where=where) for c in config.services]
|
||||||
applications = [ApplicationObservation.from_config(config=c, parent_where=where) for c in config.applications]
|
applications = [ApplicationObservation.from_config(config=c, parent_where=where) for c in config.applications]
|
||||||
@@ -293,5 +315,7 @@ class HostObservation(AbstractObservation, identifier="HOST"):
|
|||||||
monitored_traffic=config.monitored_traffic,
|
monitored_traffic=config.monitored_traffic,
|
||||||
include_num_access=config.include_num_access,
|
include_num_access=config.include_num_access,
|
||||||
file_system_requires_scan=config.file_system_requires_scan,
|
file_system_requires_scan=config.file_system_requires_scan,
|
||||||
|
services_requires_scan=config.services_requires_scan,
|
||||||
|
applications_requires_scan=config.applications_requires_scan,
|
||||||
include_users=config.include_users,
|
include_users=config.include_users,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -45,7 +45,13 @@ class NodesObservation(AbstractObservation, identifier="NODES"):
|
|||||||
include_num_access: Optional[bool] = None
|
include_num_access: Optional[bool] = None
|
||||||
"""Flag to include the number of accesses."""
|
"""Flag to include the number of accesses."""
|
||||||
file_system_requires_scan: bool = True
|
file_system_requires_scan: bool = True
|
||||||
"""If True, the folder must be scanned to update the health state. Tf False, the true state is always shown."""
|
"""If True, the folder must be scanned to update the health state. If False, the true state is always shown."""
|
||||||
|
services_requires_scan: bool = True
|
||||||
|
"""If True, the services must be scanned to update the health state.
|
||||||
|
If False, the true state is always shown."""
|
||||||
|
applications_requires_scan: bool = True
|
||||||
|
"""If True, the applications must be scanned to update the health state.
|
||||||
|
If False, the true state is always shown."""
|
||||||
include_users: Optional[bool] = True
|
include_users: Optional[bool] = True
|
||||||
"""If True, report user session information."""
|
"""If True, report user session information."""
|
||||||
num_ports: Optional[int] = None
|
num_ports: Optional[int] = None
|
||||||
@@ -193,6 +199,10 @@ class NodesObservation(AbstractObservation, identifier="NODES"):
|
|||||||
host_config.include_num_access = config.include_num_access
|
host_config.include_num_access = config.include_num_access
|
||||||
if host_config.file_system_requires_scan is None:
|
if host_config.file_system_requires_scan is None:
|
||||||
host_config.file_system_requires_scan = config.file_system_requires_scan
|
host_config.file_system_requires_scan = config.file_system_requires_scan
|
||||||
|
if host_config.services_requires_scan is None:
|
||||||
|
host_config.services_requires_scan = config.services_requires_scan
|
||||||
|
if host_config.applications_requires_scan is None:
|
||||||
|
host_config.applications_requires_scan = config.applications_requires_scan
|
||||||
if host_config.include_users is None:
|
if host_config.include_users is None:
|
||||||
host_config.include_users = config.include_users
|
host_config.include_users = config.include_users
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Dict
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from gymnasium import spaces
|
from gymnasium import spaces
|
||||||
from gymnasium.core import ObsType
|
from gymnasium.core import ObsType
|
||||||
@@ -19,7 +19,10 @@ class ServiceObservation(AbstractObservation, identifier="SERVICE"):
|
|||||||
service_name: str
|
service_name: str
|
||||||
"""Name of the service, used for querying simulation state dictionary"""
|
"""Name of the service, used for querying simulation state dictionary"""
|
||||||
|
|
||||||
def __init__(self, where: WhereType) -> None:
|
services_requires_scan: Optional[bool] = None
|
||||||
|
"""If True, services must be scanned to update the health state. If False, true state is always shown."""
|
||||||
|
|
||||||
|
def __init__(self, where: WhereType, services_requires_scan: bool) -> None:
|
||||||
"""
|
"""
|
||||||
Initialise a service observation instance.
|
Initialise a service observation instance.
|
||||||
|
|
||||||
@@ -28,6 +31,7 @@ class ServiceObservation(AbstractObservation, identifier="SERVICE"):
|
|||||||
:type where: WhereType
|
:type where: WhereType
|
||||||
"""
|
"""
|
||||||
self.where = where
|
self.where = where
|
||||||
|
self.services_requires_scan = services_requires_scan
|
||||||
self.default_observation = {"operating_status": 0, "health_status": 0}
|
self.default_observation = {"operating_status": 0, "health_status": 0}
|
||||||
|
|
||||||
def observe(self, state: Dict) -> ObsType:
|
def observe(self, state: Dict) -> ObsType:
|
||||||
@@ -44,7 +48,9 @@ class ServiceObservation(AbstractObservation, identifier="SERVICE"):
|
|||||||
return self.default_observation
|
return self.default_observation
|
||||||
return {
|
return {
|
||||||
"operating_status": service_state["operating_state"],
|
"operating_status": service_state["operating_state"],
|
||||||
"health_status": service_state["health_state_visible"],
|
"health_status": service_state["health_state_visible"]
|
||||||
|
if self.services_requires_scan
|
||||||
|
else service_state["health_state_actual"],
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -70,7 +76,9 @@ class ServiceObservation(AbstractObservation, identifier="SERVICE"):
|
|||||||
:return: Constructed service observation instance.
|
:return: Constructed service observation instance.
|
||||||
:rtype: ServiceObservation
|
:rtype: ServiceObservation
|
||||||
"""
|
"""
|
||||||
return cls(where=parent_where + ["services", config.service_name])
|
return cls(
|
||||||
|
where=parent_where + ["services", config.service_name], services_requires_scan=config.services_requires_scan
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ApplicationObservation(AbstractObservation, identifier="APPLICATION"):
|
class ApplicationObservation(AbstractObservation, identifier="APPLICATION"):
|
||||||
@@ -82,7 +90,12 @@ class ApplicationObservation(AbstractObservation, identifier="APPLICATION"):
|
|||||||
application_name: str
|
application_name: str
|
||||||
"""Name of the application, used for querying simulation state dictionary"""
|
"""Name of the application, used for querying simulation state dictionary"""
|
||||||
|
|
||||||
def __init__(self, where: WhereType) -> None:
|
applications_requires_scan: Optional[bool] = None
|
||||||
|
"""
|
||||||
|
If True, applications must be scanned to update the health state. If False, true state is always shown.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, where: WhereType, applications_requires_scan: bool) -> None:
|
||||||
"""
|
"""
|
||||||
Initialise an application observation instance.
|
Initialise an application observation instance.
|
||||||
|
|
||||||
@@ -92,6 +105,7 @@ class ApplicationObservation(AbstractObservation, identifier="APPLICATION"):
|
|||||||
:type where: WhereType
|
:type where: WhereType
|
||||||
"""
|
"""
|
||||||
self.where = where
|
self.where = where
|
||||||
|
self.applications_requires_scan = applications_requires_scan
|
||||||
self.default_observation = {"operating_status": 0, "health_status": 0, "num_executions": 0}
|
self.default_observation = {"operating_status": 0, "health_status": 0, "num_executions": 0}
|
||||||
|
|
||||||
# TODO: allow these to be configured in yaml
|
# TODO: allow these to be configured in yaml
|
||||||
@@ -128,7 +142,9 @@ class ApplicationObservation(AbstractObservation, identifier="APPLICATION"):
|
|||||||
return self.default_observation
|
return self.default_observation
|
||||||
return {
|
return {
|
||||||
"operating_status": application_state["operating_state"],
|
"operating_status": application_state["operating_state"],
|
||||||
"health_status": application_state["health_state_visible"],
|
"health_status": application_state["health_state_visible"]
|
||||||
|
if self.applications_requires_scan
|
||||||
|
else application_state["health_state_actual"],
|
||||||
"num_executions": self._categorise_num_executions(application_state["num_executions"]),
|
"num_executions": self._categorise_num_executions(application_state["num_executions"]),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,4 +177,7 @@ class ApplicationObservation(AbstractObservation, identifier="APPLICATION"):
|
|||||||
:return: Constructed application observation instance.
|
:return: Constructed application observation instance.
|
||||||
:rtype: ApplicationObservation
|
:rtype: ApplicationObservation
|
||||||
"""
|
"""
|
||||||
return cls(where=parent_where + ["applications", config.application_name])
|
return cls(
|
||||||
|
where=parent_where + ["applications", config.application_name],
|
||||||
|
applications_requires_scan=config.applications_requires_scan,
|
||||||
|
)
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ def test_host_observation(simulation):
|
|||||||
folders=[],
|
folders=[],
|
||||||
network_interfaces=[],
|
network_interfaces=[],
|
||||||
file_system_requires_scan=True,
|
file_system_requires_scan=True,
|
||||||
|
services_requires_scan=True,
|
||||||
|
applications_requires_scan=True,
|
||||||
include_users=False,
|
include_users=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ def test_service_observation(simulation):
|
|||||||
ntp_server = pc.software_manager.software.get("NTPServer")
|
ntp_server = pc.software_manager.software.get("NTPServer")
|
||||||
assert ntp_server
|
assert ntp_server
|
||||||
|
|
||||||
service_obs = ServiceObservation(where=["network", "nodes", pc.hostname, "services", "NTPServer"])
|
service_obs = ServiceObservation(
|
||||||
|
where=["network", "nodes", pc.hostname, "services", "NTPServer"], services_requires_scan=True
|
||||||
|
)
|
||||||
|
|
||||||
assert service_obs.space["operating_status"] == spaces.Discrete(7)
|
assert service_obs.space["operating_status"] == spaces.Discrete(7)
|
||||||
assert service_obs.space["health_status"] == spaces.Discrete(5)
|
assert service_obs.space["health_status"] == spaces.Discrete(5)
|
||||||
@@ -54,7 +56,9 @@ def test_application_observation(simulation):
|
|||||||
web_browser: WebBrowser = pc.software_manager.software.get("WebBrowser")
|
web_browser: WebBrowser = pc.software_manager.software.get("WebBrowser")
|
||||||
assert web_browser
|
assert web_browser
|
||||||
|
|
||||||
app_obs = ApplicationObservation(where=["network", "nodes", pc.hostname, "applications", "WebBrowser"])
|
app_obs = ApplicationObservation(
|
||||||
|
where=["network", "nodes", pc.hostname, "applications", "WebBrowser"], applications_requires_scan=True
|
||||||
|
)
|
||||||
|
|
||||||
web_browser.close()
|
web_browser.close()
|
||||||
observation_state = app_obs.observe(simulation.describe_state())
|
observation_state = app_obs.observe(simulation.describe_state())
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||||
|
import json
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from primaite.game.agent.observations import ObservationManager
|
from primaite.game.agent.observations import ApplicationObservation, ObservationManager, ServiceObservation
|
||||||
from primaite.game.agent.observations.file_system_observations import FileObservation, FolderObservation
|
from primaite.game.agent.observations.file_system_observations import FileObservation, FolderObservation
|
||||||
from primaite.game.agent.observations.host_observations import HostObservation
|
from primaite.game.agent.observations.host_observations import HostObservation
|
||||||
|
|
||||||
@@ -130,3 +131,227 @@ class TestFileSystemRequiresScan:
|
|||||||
[], files=[], num_files=0, include_num_access=False, file_system_requires_scan=False
|
[], files=[], num_files=0, include_num_access=False, file_system_requires_scan=False
|
||||||
)
|
)
|
||||||
assert obs_not_requiring_scan.observe(folder_state)["health_status"] == 3
|
assert obs_not_requiring_scan.observe(folder_state)["health_status"] == 3
|
||||||
|
|
||||||
|
|
||||||
|
class TestServicesRequiresScan:
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("yaml_option_string", "expected_val"),
|
||||||
|
(
|
||||||
|
("services_requires_scan: true", True),
|
||||||
|
("services_requires_scan: false", False),
|
||||||
|
(" ", True),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_obs_config(self, yaml_option_string, expected_val):
|
||||||
|
"""Check that the default behaviour is to set service_requires_scan to True."""
|
||||||
|
obs_cfg_yaml = f"""
|
||||||
|
type: CUSTOM
|
||||||
|
options:
|
||||||
|
components:
|
||||||
|
- type: NODES
|
||||||
|
label: NODES
|
||||||
|
options:
|
||||||
|
hosts:
|
||||||
|
- hostname: domain_controller
|
||||||
|
- hostname: web_server
|
||||||
|
services:
|
||||||
|
- service_name: WebServer
|
||||||
|
- service_name: DNSClient
|
||||||
|
- hostname: database_server
|
||||||
|
folders:
|
||||||
|
- folder_name: database
|
||||||
|
files:
|
||||||
|
- file_name: database.db
|
||||||
|
- hostname: backup_server
|
||||||
|
services:
|
||||||
|
- service_name: FTPServer
|
||||||
|
- hostname: security_suite
|
||||||
|
- hostname: client_1
|
||||||
|
- hostname: client_2
|
||||||
|
num_services: 3
|
||||||
|
num_applications: 0
|
||||||
|
num_folders: 1
|
||||||
|
num_files: 1
|
||||||
|
num_nics: 2
|
||||||
|
include_num_access: false
|
||||||
|
{yaml_option_string}
|
||||||
|
include_nmne: true
|
||||||
|
monitored_traffic:
|
||||||
|
icmp:
|
||||||
|
- NONE
|
||||||
|
tcp:
|
||||||
|
- DNS
|
||||||
|
routers:
|
||||||
|
- hostname: router_1
|
||||||
|
num_ports: 0
|
||||||
|
ip_list:
|
||||||
|
- 192.168.1.10
|
||||||
|
- 192.168.1.12
|
||||||
|
- 192.168.1.14
|
||||||
|
- 192.168.1.16
|
||||||
|
- 192.168.1.110
|
||||||
|
- 192.168.10.21
|
||||||
|
- 192.168.10.22
|
||||||
|
- 192.168.10.110
|
||||||
|
wildcard_list:
|
||||||
|
- 0.0.0.1
|
||||||
|
port_list:
|
||||||
|
- 80
|
||||||
|
- 5432
|
||||||
|
protocol_list:
|
||||||
|
- ICMP
|
||||||
|
- TCP
|
||||||
|
- UDP
|
||||||
|
num_rules: 10
|
||||||
|
|
||||||
|
- type: LINKS
|
||||||
|
label: LINKS
|
||||||
|
options:
|
||||||
|
link_references:
|
||||||
|
- router_1:eth-1<->switch_1:eth-8
|
||||||
|
- router_1:eth-2<->switch_2:eth-8
|
||||||
|
- switch_1:eth-1<->domain_controller:eth-1
|
||||||
|
- switch_1:eth-2<->web_server:eth-1
|
||||||
|
- switch_1:eth-3<->database_server:eth-1
|
||||||
|
- switch_1:eth-4<->backup_server:eth-1
|
||||||
|
- switch_1:eth-7<->security_suite:eth-1
|
||||||
|
- switch_2:eth-1<->client_1:eth-1
|
||||||
|
- switch_2:eth-2<->client_2:eth-1
|
||||||
|
- switch_2:eth-7<->security_suite:eth-2
|
||||||
|
- type: "NONE"
|
||||||
|
label: ICS
|
||||||
|
options: {{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
cfg = yaml.safe_load(obs_cfg_yaml)
|
||||||
|
manager = ObservationManager.from_config(cfg)
|
||||||
|
|
||||||
|
hosts: List[HostObservation] = manager.obs.components["NODES"].hosts
|
||||||
|
for i, host in enumerate(hosts):
|
||||||
|
services: List[ServiceObservation] = host.services
|
||||||
|
for j, service in enumerate(services):
|
||||||
|
val = service.services_requires_scan
|
||||||
|
print(f"host {i} service {j} {val}")
|
||||||
|
assert val == expected_val # Make sure services require scan by default
|
||||||
|
|
||||||
|
def test_services_requires_scan(self):
|
||||||
|
state = {"health_state_actual": 3, "health_state_visible": 1, "operating_state": 1}
|
||||||
|
|
||||||
|
obs_requiring_scan = ServiceObservation([], services_requires_scan=True)
|
||||||
|
assert obs_requiring_scan.observe(state)["health_status"] == 1 # should be visible value
|
||||||
|
|
||||||
|
obs_not_requiring_scan = ServiceObservation([], services_requires_scan=False)
|
||||||
|
assert obs_not_requiring_scan.observe(state)["health_status"] == 3 # should be actual value
|
||||||
|
|
||||||
|
|
||||||
|
class TestApplicationsRequiresScan:
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("yaml_option_string", "expected_val"),
|
||||||
|
(
|
||||||
|
("applications_requires_scan: true", True),
|
||||||
|
("applications_requires_scan: false", False),
|
||||||
|
(" ", True),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_obs_config(self, yaml_option_string, expected_val):
|
||||||
|
"""Check that the default behaviour is to set applications_requires_scan to True."""
|
||||||
|
obs_cfg_yaml = f"""
|
||||||
|
type: CUSTOM
|
||||||
|
options:
|
||||||
|
components:
|
||||||
|
- type: NODES
|
||||||
|
label: NODES
|
||||||
|
options:
|
||||||
|
hosts:
|
||||||
|
- hostname: domain_controller
|
||||||
|
- hostname: web_server
|
||||||
|
- hostname: database_server
|
||||||
|
folders:
|
||||||
|
- folder_name: database
|
||||||
|
files:
|
||||||
|
- file_name: database.db
|
||||||
|
- hostname: backup_server
|
||||||
|
- hostname: security_suite
|
||||||
|
- hostname: client_1
|
||||||
|
applications:
|
||||||
|
- application_name: WebBrowser
|
||||||
|
- hostname: client_2
|
||||||
|
applications:
|
||||||
|
- application_name: WebBrowser
|
||||||
|
- application_name: DatabaseClient
|
||||||
|
num_services: 0
|
||||||
|
num_applications: 3
|
||||||
|
num_folders: 1
|
||||||
|
num_files: 1
|
||||||
|
num_nics: 2
|
||||||
|
include_num_access: false
|
||||||
|
{yaml_option_string}
|
||||||
|
include_nmne: true
|
||||||
|
monitored_traffic:
|
||||||
|
icmp:
|
||||||
|
- NONE
|
||||||
|
tcp:
|
||||||
|
- DNS
|
||||||
|
routers:
|
||||||
|
- hostname: router_1
|
||||||
|
num_ports: 0
|
||||||
|
ip_list:
|
||||||
|
- 192.168.1.10
|
||||||
|
- 192.168.1.12
|
||||||
|
- 192.168.1.14
|
||||||
|
- 192.168.1.16
|
||||||
|
- 192.168.1.110
|
||||||
|
- 192.168.10.21
|
||||||
|
- 192.168.10.22
|
||||||
|
- 192.168.10.110
|
||||||
|
wildcard_list:
|
||||||
|
- 0.0.0.1
|
||||||
|
port_list:
|
||||||
|
- 80
|
||||||
|
- 5432
|
||||||
|
protocol_list:
|
||||||
|
- ICMP
|
||||||
|
- TCP
|
||||||
|
- UDP
|
||||||
|
num_rules: 10
|
||||||
|
|
||||||
|
- type: LINKS
|
||||||
|
label: LINKS
|
||||||
|
options:
|
||||||
|
link_references:
|
||||||
|
- router_1:eth-1<->switch_1:eth-8
|
||||||
|
- router_1:eth-2<->switch_2:eth-8
|
||||||
|
- switch_1:eth-1<->domain_controller:eth-1
|
||||||
|
- switch_1:eth-2<->web_server:eth-1
|
||||||
|
- switch_1:eth-3<->database_server:eth-1
|
||||||
|
- switch_1:eth-4<->backup_server:eth-1
|
||||||
|
- switch_1:eth-7<->security_suite:eth-1
|
||||||
|
- switch_2:eth-1<->client_1:eth-1
|
||||||
|
- switch_2:eth-2<->client_2:eth-1
|
||||||
|
- switch_2:eth-7<->security_suite:eth-2
|
||||||
|
- type: "NONE"
|
||||||
|
label: ICS
|
||||||
|
options: {{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
cfg = yaml.safe_load(obs_cfg_yaml)
|
||||||
|
manager = ObservationManager.from_config(cfg)
|
||||||
|
|
||||||
|
hosts: List[HostObservation] = manager.obs.components["NODES"].hosts
|
||||||
|
for i, host in enumerate(hosts):
|
||||||
|
services: List[ServiceObservation] = host.services
|
||||||
|
for j, service in enumerate(services):
|
||||||
|
val = service.services_requires_scan
|
||||||
|
print(f"host {i} service {j} {val}")
|
||||||
|
assert val == expected_val # Make sure applications require scan by default
|
||||||
|
|
||||||
|
def test_applications_requires_scan(self):
|
||||||
|
state = {"health_state_actual": 3, "health_state_visible": 1, "operating_state": 1, "num_executions": 1}
|
||||||
|
|
||||||
|
obs_requiring_scan = ApplicationObservation([], applications_requires_scan=True)
|
||||||
|
assert obs_requiring_scan.observe(state)["health_status"] == 1 # should be visible value
|
||||||
|
|
||||||
|
obs_not_requiring_scan = ApplicationObservation([], applications_requires_scan=False)
|
||||||
|
assert obs_not_requiring_scan.observe(state)["health_status"] == 3 # should be actual value
|
||||||
|
|||||||
Reference in New Issue
Block a user