From 82da21b0737b194eb1a53438faa9a4c7f3c7f5f2 Mon Sep 17 00:00:00 2001 From: "Czar.Echavez" Date: Tue, 3 Oct 2023 16:56:35 +0100 Subject: [PATCH] \#1943: - changelog added - added documentation + example of using web server + web browser - extended web server so that it also accepts ip addresses - web server can differentiate between a normal page request and one that propagates into a DB request - rename WebServerService -> WebServer --- CHANGELOG.md | 1 + .../system/ftp_client_server.rst | 2 +- .../simulation_components/system/software.rst | 1 + .../web_browser_and_web_server_service.rst | 110 ++++++++++++++++++ src/primaite/simulator/network/networks.py | 4 +- .../system/applications/web_browser.py | 16 ++- .../system/services/dns/dns_client.py | 5 + .../services/web_server/web_server_service.py | 18 ++- .../system/test_web_client_server.py | 39 ++++++- 9 files changed, 180 insertions(+), 16 deletions(-) create mode 100644 docs/source/simulation_components/system/web_browser_and_web_server_service.rst diff --git a/CHANGELOG.md b/CHANGELOG.md index 7147f82b..5d73f454 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ SessionManager. - Data Manipulator Bot - A red agent service which sends a payload to a target machine. (By default this payload is a SQL query that breaks a database) - DNS Services: `DNSClient` and `DNSServer` - FTP Services: `FTPClient` and `FTPServer` +- HTTP Services: `WebBrowser` to simulate a web client and `WebServer` ## [2.0.0] - 2023-07-26 diff --git a/docs/source/simulation_components/system/ftp_client_server.rst b/docs/source/simulation_components/system/ftp_client_server.rst index 0e4aeea3..f6011de2 100644 --- a/docs/source/simulation_components/system/ftp_client_server.rst +++ b/docs/source/simulation_components/system/ftp_client_server.rst @@ -63,7 +63,7 @@ Implementation Example Usage ----------- +------------- Dependencies ^^^^^^^^^^^^ diff --git a/docs/source/simulation_components/system/software.rst b/docs/source/simulation_components/system/software.rst index 921dfb9e..b2985393 100644 --- a/docs/source/simulation_components/system/software.rst +++ b/docs/source/simulation_components/system/software.rst @@ -19,3 +19,4 @@ Contents data_manipulation_bot dns_client_server ftp_client_server + web_browser_and_web_server_service diff --git a/docs/source/simulation_components/system/web_browser_and_web_server_service.rst b/docs/source/simulation_components/system/web_browser_and_web_server_service.rst new file mode 100644 index 00000000..a02ac621 --- /dev/null +++ b/docs/source/simulation_components/system/web_browser_and_web_server_service.rst @@ -0,0 +1,110 @@ +.. only:: comment + + © Crown-owned copyright 2023, Defence Science and Technology Laboratory UK + +Web Browser and Web Server Service +================================== + +Web Server Service +------------------ +Provides a Web Server simulation by extending the base Service class. + +Key capabilities +^^^^^^^^^^^^^^^^ + +- Simulates a web server with the capability to also request data from a database +- Allows the emulation of HTTP requests between client (e.g. a web browser) and server + - GET request sends a get all users request to the database server and returns an HTTP 200 status if the database is responsive +- Leverages the Service base class for install/uninstall, status tracking, etc. + +Usage +^^^^^ +- Install on a Node via the ``SoftwareManager`` to start the `WebServer`. +- Service runs on HTTP port 80 by default. (TODO: HTTPS) + +Implementation +^^^^^^^^^^^^^^ + +- HTTP request uses a ``HTTPRequestPacket`` object +- HTTP reaponse uses a ``HTTPResponsePacket`` object +- Extends Service class for integration with ``SoftwareManager``. + +Web Browser (Web Client) +------------------------ + +The ``WebBrowser`` provides a client interface for connecting to the ``WebServer``. + +Key features +^^^^^^^^^^^^ + +- Connects to the ``WebServer`` via the ``SoftwareManager``. +- Simulates HTTP requests and HTTP packet transfer across a network +- Allows the emulation of HTTP requests between client and server: + - Automatically uses ``DNSClient`` to resolve domain names + - GET: performs an HTTP GET request from client to server +- Leverages the Service base class for install/uninstall, status tracking, etc. + +Usage +^^^^^ + +- Install on a Node via the ``SoftwareManager`` to start the ``WebBrowser``. +- Service runs on HTTP port 80 by default. (TODO: HTTPS) +- Execute sending an HTTP GET request with ``get_webpage`` + +Implementation +^^^^^^^^^^^^^^ + +- Leverages ``SoftwareManager`` for sending payloads over the network. +- Provides easy interface for making HTTP requests between an HTTP client and server. +- Extends base Service class. + + +Example Usage +------------- + +Dependencies +^^^^^^^^^^^^ + +.. code-block:: python + + from primaite.simulator.network.container import Network + from primaite.simulator.network.hardware.nodes.computer import Computer + from primaite.simulator.network.hardware.nodes.server import Server + from primaite.simulator.system.applications.web_browser import WebBrowser + from primaite.simulator.system.services.web_server.web_server_service import WebServer + +Example peer to peer network +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: python + + net = Network() + + pc1 = Computer(hostname="pc1", ip_address="192.168.1.50", subnet_mask="255.255.255.0") + srv = Server(hostname="srv", ip_address="192.168.1.10", subnet_mask="255.255.255.0") + pc1.power_on() + srv.power_on() + net.connect(pc1.ethernet_port[1], srv.ethernet_port[1]) + +Install the Web Server +^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: python + + # web browser is automatically installed in computer nodes + # IRL this is usually included with an OS + client: WebBrowser = pc1.software_manager.software['WebBrowser'] + + # install web server + srv.software_manager.install(WebServer) + webserv: WebServer = srv.software_manager.software['WebServer'] + +Open the web page +^^^^^^^^^^^^^^^^^ + +Using a domain name to connect to a website requires setting up DNS Servers. For this example, it is possible to use the IP address directly + +.. code-block:: python + + # check that the get request succeeded + print(client.get_webpage("http://192.168.1.10")) # should be True diff --git a/src/primaite/simulator/network/networks.py b/src/primaite/simulator/network/networks.py index 1ddeb82f..4f9aebdc 100644 --- a/src/primaite/simulator/network/networks.py +++ b/src/primaite/simulator/network/networks.py @@ -14,7 +14,7 @@ from primaite.simulator.system.services.dns.dns_client import DNSClient from primaite.simulator.system.services.dns.dns_server import DNSServer from primaite.simulator.system.services.ftp.ftp_client import FTPClient from primaite.simulator.system.services.red_services.data_manipulation_bot import DataManipulationBot -from primaite.simulator.system.services.web_server.web_server_service import WebServerService +from primaite.simulator.system.services.web_server.web_server_service import WebServer def client_server_routed() -> Network: @@ -260,7 +260,7 @@ def arcd_uc2_network() -> Network: database_client.run() database_client.connect() - web_server.software_manager.install(WebServerService) + web_server.software_manager.install(WebServer) # register the web_server to a domain dns_server_service: DNSServer = domain_controller.software_manager.software["DNSServer"] # noqa diff --git a/src/primaite/simulator/system/applications/web_browser.py b/src/primaite/simulator/system/applications/web_browser.py index 9d2c31b1..69de333e 100644 --- a/src/primaite/simulator/system/applications/web_browser.py +++ b/src/primaite/simulator/system/applications/web_browser.py @@ -74,11 +74,17 @@ class WebBrowser(Application): domain_exists = dns_client.check_domain_exists(target_domain=parsed_url.hostname) # if domain does not exist, the request fails - if not domain_exists: - return False - - # set current domain name IP address - self.domain_name_ip_address = dns_client.dns_cache[parsed_url.hostname] + if domain_exists: + # set current domain name IP address + self.domain_name_ip_address = dns_client.dns_cache[parsed_url.hostname] + else: + # check if url is an ip address + try: + self.domain_name_ip_address = IPv4Address(parsed_url.hostname) + except Exception: + # unable to deal with this request + self.sys_log.error(f"{self.name}: Unable to resolve URL {url}") + return False # create HTTPRequest payload payload = HTTPRequestPacket(request_method=HTTPRequestMethod.GET, request_url=url) diff --git a/src/primaite/simulator/system/services/dns/dns_client.py b/src/primaite/simulator/system/services/dns/dns_client.py index 620a9a32..266ac4f6 100644 --- a/src/primaite/simulator/system/services/dns/dns_client.py +++ b/src/primaite/simulator/system/services/dns/dns_client.py @@ -72,6 +72,11 @@ class DNSClient(Service): :param: session_id: The Session ID the payload is to originate from. Optional. :param: is_reattempt: Checks if the request has been reattempted. Default is False. """ + # check if DNS server is configured + if self.dns_server is None: + self.sys_log.error(f"{self.name}: DNS Server is not configured") + return False + # check if the target domain is in the client's DNS cache payload = DNSPacket(dns_request=DNSRequest(domain_name_request=target_domain)) diff --git a/src/primaite/simulator/system/services/web_server/web_server_service.py b/src/primaite/simulator/system/services/web_server/web_server_service.py index 276cb57f..68624930 100644 --- a/src/primaite/simulator/system/services/web_server/web_server_service.py +++ b/src/primaite/simulator/system/services/web_server/web_server_service.py @@ -1,5 +1,6 @@ from ipaddress import IPv4Address from typing import Any, Optional +from urllib.parse import urlparse from primaite.simulator.network.protocols.http import ( HTTPRequestMethod, @@ -13,7 +14,7 @@ from primaite.simulator.system.applications.database_client import DatabaseClien from primaite.simulator.system.services.service import Service -class WebServerService(Service): +class WebServer(Service): """Class used to represent a Web Server Service in simulation.""" def __init__(self, **kwargs): @@ -76,13 +77,20 @@ class WebServerService(Service): """ response = HTTPResponsePacket(status_code=HTTPStatusCode.BAD_REQUEST, payload=payload) try: - # get data from DatabaseServer - db_client: DatabaseClient = self.software_manager.software["DatabaseClient"] - # get all users - if db_client.query("SELECT * FROM user;"): + parsed_url = urlparse(payload.request_url) + + if parsed_url.path is None or len(parsed_url.path) < 1: # query succeeded response.status_code = HTTPStatusCode.OK + if parsed_url.path.startswith("/users"): + # get data from DatabaseServer + db_client: DatabaseClient = self.software_manager.software["DatabaseClient"] + # get all users + if db_client.query("SELECT * FROM user;"): + # query succeeded + response.status_code = HTTPStatusCode.OK + return response except Exception: # something went wrong on the server diff --git a/tests/integration_tests/system/test_web_client_server.py b/tests/integration_tests/system/test_web_client_server.py index fee51297..8b6f4072 100644 --- a/tests/integration_tests/system/test_web_client_server.py +++ b/tests/integration_tests/system/test_web_client_server.py @@ -1,18 +1,51 @@ from primaite.simulator.network.hardware.nodes.computer import Computer +from primaite.simulator.network.hardware.nodes.server import Server from primaite.simulator.network.protocols.http import HTTPStatusCode from primaite.simulator.system.applications.application import ApplicationOperatingState from primaite.simulator.system.applications.web_browser import WebBrowser from primaite.simulator.system.services.service import ServiceOperatingState -def test_web_page_get_request(uc2_network): - """Test to see if the client retrieves the correct web files.""" +def test_web_page_home_page(uc2_network): + """Test to see if the browser is able to open the main page of the web server.""" client_1: Computer = uc2_network.get_node_by_hostname("client_1") web_client: WebBrowser = client_1.software_manager.software["WebBrowser"] web_client.run() assert web_client.operating_state == ApplicationOperatingState.RUNNING - assert web_client.get_webpage("http://arcd.com/index.html") is True + assert web_client.get_webpage("http://arcd.com/") is True + + # latest reponse should have status code 200 + assert web_client.latest_response is not None + assert web_client.latest_response.status_code == HTTPStatusCode.OK + + +def test_web_page_get_users_page_request_with_domain_name(uc2_network): + """Test to see if the client can handle requests with domain names""" + client_1: Computer = uc2_network.get_node_by_hostname("client_1") + web_client: WebBrowser = client_1.software_manager.software["WebBrowser"] + web_client.run() + assert web_client.operating_state == ApplicationOperatingState.RUNNING + + assert web_client.get_webpage("http://arcd.com/users/") is True + + # latest reponse should have status code 200 + assert web_client.latest_response is not None + assert web_client.latest_response.status_code == HTTPStatusCode.OK + + +def test_web_page_get_users_page_request_with_ip_address(uc2_network): + """Test to see if the client can handle requests that use ip_address.""" + client_1: Computer = uc2_network.get_node_by_hostname("client_1") + web_client: WebBrowser = client_1.software_manager.software["WebBrowser"] + web_client.run() + + web_server: Server = uc2_network.get_node_by_hostname("web_server") + web_server_ip = web_server.nics.get(next(iter(web_server.nics))).ip_address + + assert web_client.operating_state == ApplicationOperatingState.RUNNING + + assert web_client.get_webpage(f"http://{web_server_ip}/users/") is True # latest reponse should have status code 200 assert web_client.latest_response is not None