diff --git a/manifests/l3_centralizedattackdetectorservice.yaml b/manifests/l3_centralizedattackdetectorservice.yaml index 594e21f4dbb1b10e1d859053c33785e2e59e4b46..95c6d8176ca86c98c1e26d88267c864247ae8b5b 100644 --- a/manifests/l3_centralizedattackdetectorservice.yaml +++ b/manifests/l3_centralizedattackdetectorservice.yaml @@ -36,6 +36,12 @@ spec: env: - name: LOG_LEVEL value: "DEBUG" + - name: BATCH_SIZE + value: "256" + - name: CAD_CLASSIFICATION_THRESHOLD + value: "0.5" + - name: MONITORED_KPIS_TIME_INTERVAL_AGG + value: "60" readinessProbe: exec: command: ["/bin/grpc_health_probe", "-addr=:10001"] diff --git a/src/l3_attackmitigator/service/l3_attackmitigatorServiceServicerImpl.py b/src/l3_attackmitigator/service/l3_attackmitigatorServiceServicerImpl.py index 34cfcd5d081a431f165461564eeb5a3390a3bda5..f3613b377a86f61ba0a76665eb3001f5d9721a2a 100644 --- a/src/l3_attackmitigator/service/l3_attackmitigatorServiceServicerImpl.py +++ b/src/l3_attackmitigator/service/l3_attackmitigatorServiceServicerImpl.py @@ -65,9 +65,6 @@ class l3_attackmitigatorServiceServicerImpl(L3AttackmitigatorServicer): service_id.context_id.context_uuid.uuid = context_uuid service_id.service_uuid.uuid = service_uuid - # Get service form Context - # context_client = ContextClient() - try: _service: Service = self.context_client.GetService(service_id) except: @@ -88,11 +85,9 @@ class l3_attackmitigatorServiceServicerImpl(L3AttackmitigatorServicer): # Set RuleSet for this ACL ConfigRule acl_rule_set = acl_config_rule.acl.rule_set - # TODO: update the following parameters; for instance, add them as parameters of the method configure_acl_rule - # acl_rule_set.name = "DROP-HTTPS" + acl_rule_set.name = "DROP-TCP" acl_rule_set.type = AclRuleTypeEnum.ACLRULETYPE_IPV4 - # acl_rule_set.description = "DROP undesired HTTPS traffic" acl_rule_set.description = "DROP undesired TCP traffic" # Add ACLEntry to the ACLRuleSet @@ -108,26 +103,24 @@ class l3_attackmitigatorServiceServicerImpl(L3AttackmitigatorServicer): acl_entry.match.dst_address = "{}/32".format(dst_ip) acl_entry.match.src_port = int(src_port) acl_entry.match.dst_port = int(dst_port) - # TODO: update the following parameters; for instance, add them as parameters of the method configure_acl_rule + acl_entry.action.forward_action = AclForwardActionEnum.ACLFORWARDINGACTION_DROP acl_entry.action.log_action = AclLogActionEnum.ACLLOGACTION_NOLOG - LOGGER.info("ACL Rule Set: %s", acl_rule_set) - LOGGER.info("ACL Config Rule: %s", acl_config_rule) + LOGGER.info("ACL Rule Set: %s", grpc_message_to_json_string(acl_rule_set)) + LOGGER.info("ACL Config Rule: %s", grpc_message_to_json_string(acl_config_rule)) # Add the ACLRuleSet to the list of configured ACLRuleSets self.configured_acl_config_rules.append(acl_config_rule) # Update the Service with the new ACL RuleSet - # service_client = ServiceClient() service_reply: ServiceId = self.service_client.UpdateService(service_request) - # TODO: Log the service_reply details + LOGGER.info("Service reply: %s", grpc_message_to_json_string(service_reply)) if service_reply != service_request.service_id: # pylint: disable=no-member raise Exception("Service update failed. Wrong ServiceId was returned") - @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def PerformMitigation(self, request, context): last_value = request.confidence @@ -148,7 +141,7 @@ class l3_attackmitigatorServiceServicerImpl(L3AttackmitigatorServicer): counter = 0 service_id = request.service_id - LOGGER.info("Service Id.:\n{}".format(service_id)) + LOGGER.info("Service Id.:\n{}".format(grpc_message_to_json_string(service_id))) LOGGER.info("Retrieving service from Context") while sentinel: @@ -160,7 +153,7 @@ class l3_attackmitigatorServiceServicerImpl(L3AttackmitigatorServicer): LOGGER.debug("Waiting 2 seconds", counter, e) time.sleep(2) - LOGGER.info(f"Service with Service Id.: {service_id}\n{service}") + LOGGER.info(f"Service with Service Id.: {grpc_message_to_json_string(service_id)}\n{grpc_message_to_json_string(service)}") LOGGER.info("Adding new rule to the service to block the attack") self.configure_acl_rule( @@ -173,20 +166,20 @@ class l3_attackmitigatorServiceServicerImpl(L3AttackmitigatorServicer): src_port=port_o, dst_port=port_d, ) - LOGGER.info("Service with new rule:\n{}".format(service)) + LOGGER.info("Service with new rule:\n{}".format(grpc_message_to_json_string(service))) LOGGER.info("Updating service with the new rule") self.service_client.UpdateService(service) + service = self.context_client.GetService(service_id) LOGGER.info( "Service obtained from Context after updating with the new rule:\n{}".format( - self.context_client.GetService(service_id) + grpc_message_to_json_string(service) ) ) return Empty(message=f"OK, received values: {last_tag} with confidence {last_value}.") - @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetConfiguredACLRules(self, request, context): acl_rules = ACLRules() diff --git a/src/l3_centralizedattackdetector/service/l3_centralizedattackdetectorServiceServicerImpl.py b/src/l3_centralizedattackdetector/service/l3_centralizedattackdetectorServiceServicerImpl.py index 8f59c81150345f15faaf69824d1036b87f5bd80d..3bfd6fd2ff09ef471d94b6c66470ed5668704827 100644 --- a/src/l3_centralizedattackdetector/service/l3_centralizedattackdetectorServiceServicerImpl.py +++ b/src/l3_centralizedattackdetector/service/l3_centralizedattackdetectorServiceServicerImpl.py @@ -13,46 +13,36 @@ # limitations under the License. from __future__ import print_function -from datetime import datetime -from datetime import timedelta +from datetime import datetime, timedelta +import csv import os import numpy as np import onnxruntime as rt import logging import time +import uuid +from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method +from common.proto.context_pb2 import Timestamp, SliceId, ConnectionId +from common.proto.kpi_sample_types_pb2 import KpiSampleType +from common.proto.l3_attackmitigator_pb2 import L3AttackmitigatorOutput from common.proto.l3_centralizedattackdetector_pb2 import Empty, AutoFeatures from common.proto.l3_centralizedattackdetector_pb2_grpc import L3CentralizedattackdetectorServicer - -from common.proto.l3_attackmitigator_pb2 import L3AttackmitigatorOutput - -from common.proto.monitoring_pb2 import KpiDescriptor -from common.proto.kpi_sample_types_pb2 import KpiSampleType - -from monitoring.client.MonitoringClient import MonitoringClient -from common.proto.monitoring_pb2 import Kpi - +from common.proto.monitoring_pb2 import Kpi, KpiDescriptor from common.tools.timestamp.Converters import timestamp_utcnow_to_float -from common.proto.context_pb2 import Timestamp, SliceId, ConnectionId - +from monitoring.client.MonitoringClient import MonitoringClient from l3_attackmitigator.client.l3_attackmitigatorClient import l3_attackmitigatorClient -import uuid - -from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method - LOGGER = logging.getLogger(__name__) current_dir = os.path.dirname(os.path.abspath(__file__)) -# Demo constants +# Constants DEMO_MODE = False ATTACK_IPS = ["37.187.95.110", "91.121.140.167", "94.23.23.52", "94.23.247.226", "149.202.83.171"] - -BATCH_SIZE= 10 - -METRICS_POOL = MetricsPool('l3_centralizedattackdetector', 'RPC') +BATCH_SIZE = int(os.getenv("BATCH_SIZE", 10)) +METRICS_POOL = MetricsPool("l3_centralizedattackdetector", "RPC") class ConnectionInfo: @@ -99,15 +89,15 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto self.cryptomining_detector_features_metadata = [float(x) for x in self.cryptomining_detector_features_metadata] self.cryptomining_detector_features_metadata.sort() LOGGER.info("Cryptomining Detector Features: " + str(self.cryptomining_detector_features_metadata)) - - LOGGER.info("Batch size: " + str(BATCH_SIZE)) + + LOGGER.info(f"Batch size: {BATCH_SIZE}") self.input_name = self.cryptomining_detector_model.get_inputs()[0].name self.label_name = self.cryptomining_detector_model.get_outputs()[0].name self.prob_name = self.cryptomining_detector_model.get_outputs()[1].name - # Kpi values - self.l3_security_status = 0 # unnecessary + # KPI values + self.l3_security_status = 0 self.l3_ml_model_confidence = 0 self.l3_inferences_in_interval_counter = 0 @@ -123,7 +113,7 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto self.l3_unique_attackers = 0 self.l3_non_empty_time_interval = False - + self.active_requests = [] self.monitoring_client = MonitoringClient() @@ -163,8 +153,8 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto self.attackmitigator_client = l3_attackmitigatorClient() # Environment variables - self.CLASSIFICATION_THRESHOLD = os.getenv("CAD_CLASSIFICATION_THRESHOLD", 0.5) - self.MONITORED_KPIS_TIME_INTERVAL_AGG = os.getenv("MONITORED_KPIS_TIME_INTERVAL_AGG", 60) + self.CLASSIFICATION_THRESHOLD = float(os.getenv("CAD_CLASSIFICATION_THRESHOLD", 0.5)) + self.MONITORED_KPIS_TIME_INTERVAL_AGG = int(os.getenv("MONITORED_KPIS_TIME_INTERVAL_AGG", 60)) # Constants self.NORMAL_CLASS = 0 @@ -190,6 +180,20 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto self.false_positives = 0 self.false_negatives = 0 + self.replica_uuid = uuid.uuid4() + + self.first_batch_request_time = 0 + self.last_batch_request_time = 0 + + LOGGER.info("This replica's identifier is: " + str(self.replica_uuid)) + + self.response_times_csv_file_path = "response_times.csv" + col_names = ["timestamp_first_req", "timestamp_last_req", "total_time", "batch_size"] + + with open(self.response_times_csv_file_path, "w", newline="") as file: + writer = csv.writer(file) + writer.writerow(col_names) + """ Create a monitored KPI for a specific service and add it to the Monitoring Client -input: @@ -224,19 +228,11 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto -output: None """ - def create_kpis(self, service_id, device_id, endpoint_id): + def create_kpis(self, service_id): LOGGER.info("Creating KPIs for service {}".format(service_id)) - # for now, all the KPIs are created for all the services from which requests are received + # all the KPIs are created for all the services from which requests are received for kpi in self.monitored_kpis: - # generate random slice_id - slice_id = SliceId() - slice_id.slice_uuid.uuid = str(uuid.uuid4()) - - # generate random connection_id - connection_id = ConnectionId() - connection_id.connection_uuid.uuid = str(uuid.uuid4()) - created_kpi = self.create_kpi( service_id, kpi, @@ -259,17 +255,9 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto if non_empty_time_interval: for service_id in monitor_service_ids: LOGGER.debug("service_id: {}".format(service_id)) - + self.monitor_compute_l3_kpi(service_id, monitor_inference_results) - - # Demo mode inference results are erased - """if DEMO_MODE: - # Delete fist half of the inference results - LOGGER.debug("inference_results len: {}".format(len(self.inference_results))) - self.inference_results = self.inference_results[len(self.inference_results)//2:] - LOGGER.debug("inference_results len after erase: {}".format(len(self.inference_results)))""" - # end = time.time() - # LOGGER.debug("Time to process inference results with erase: {}".format(end - start)) + LOGGER.debug("KPIs sent to monitoring server") else: LOGGER.debug("No KPIs sent to monitoring server") @@ -314,7 +302,7 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto LOGGER.debug("time_interval_start: {}".format(self.time_interval_start)) LOGGER.debug("time_interval_end: {}".format(self.time_interval_end)) - def monitor_compute_l3_kpi(self, service_id, monitor_inference_results): + def monitor_compute_l3_kpi(self,): # L3 security status kpi_security_status = Kpi() kpi_security_status.kpi_id.kpi_id.CopyFrom(self.monitored_kpis["l3_security_status"]["kpi_id"]) @@ -389,19 +377,14 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto # Get batch size batch_size = x_data.shape[0] - - # Print batch size - LOGGER.debug("batch_size: {}".format(batch_size)) - LOGGER.debug("x_data.shape: {}".format(x_data.shape)) - - inference_time_start = time.perf_counter() + inference_time_start = time.time() # Perform inference predictions = self.cryptomining_detector_model.run( [self.prob_name], {self.input_name: x_data.astype(np.float32)} )[0] - inference_time_end = time.perf_counter() + inference_time_end = time.time() # Measure inference time inference_time = inference_time_end - inference_time_start @@ -459,7 +442,7 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto output_message["tag"] = self.NORMAL_CLASS return output_message - + """ Classify connection as standard traffic or cryptomining attack and return results -input: @@ -480,14 +463,14 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto # Print input data shape LOGGER.debug("x_data.shape: {}".format(x_data.shape)) - inference_time_start = time.perf_counter() + inference_time_start = time.time() # Perform inference predictions = self.cryptomining_detector_model.run( [self.prob_name], {self.input_name: x_data.astype(np.float32)} )[0] - inference_time_end = time.perf_counter() + inference_time_end = time.time() # Measure inference time inference_time = inference_time_end - inference_time_start @@ -519,23 +502,25 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto # Gather the predicted class, the probability of that class and other relevant information required to block the attack output_messages = [] for i, request in enumerate(requests): - output_messages.append({ - "confidence": None, - "timestamp": datetime.now().strftime("%d/%m/%Y %H:%M:%S"), - "ip_o": request.connection_metadata.ip_o, - "ip_d": request.connection_metadata.ip_d, - "tag_name": None, - "tag": None, - "flow_id": request.connection_metadata.flow_id, - "protocol": request.connection_metadata.protocol, - "port_o": request.connection_metadata.port_o, - "port_d": request.connection_metadata.port_d, - "ml_id": self.cryptomining_detector_file_name, - "service_id": request.connection_metadata.service_id, - "endpoint_id": request.connection_metadata.endpoint_id, - "time_start": request.connection_metadata.time_start, - "time_end": request.connection_metadata.time_end, - }) + output_messages.append( + { + "confidence": None, + "timestamp": datetime.now().strftime("%d/%m/%Y %H:%M:%S"), + "ip_o": request.connection_metadata.ip_o, + "ip_d": request.connection_metadata.ip_d, + "tag_name": None, + "tag": None, + "flow_id": request.connection_metadata.flow_id, + "protocol": request.connection_metadata.protocol, + "port_o": request.connection_metadata.port_o, + "port_d": request.connection_metadata.port_d, + "ml_id": self.cryptomining_detector_file_name, + "service_id": request.connection_metadata.service_id, + "endpoint_id": request.connection_metadata.endpoint_id, + "time_start": request.connection_metadata.time_start, + "time_end": request.connection_metadata.time_end, + } + ) if predictions[i][1] >= self.CLASSIFICATION_THRESHOLD: output_messages[i]["confidence"] = predictions[i][1] @@ -554,18 +539,22 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto + request: L3CentralizedattackdetectorMetrics object with connection features information -output: Empty object with a message about the execution of the function """ + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def AnalyzeConnectionStatistics(self, request, context): # Perform inference with the data sent in the request + if len(self.active_requests) == 0: + self.first_batch_request_time = time.time() + self.active_requests.append(request) - - if len(self.active_requests) == BATCH_SIZE: - logging.info("Performing inference...") - + + if len(self.active_requests) >= BATCH_SIZE: + LOGGER.debug("Performing inference... {}".format(self.replica_uuid)) + inference_time_start = time.time() cryptomining_detector_output = self.perform_distributed_inference(self.active_requests) inference_time_end = time.time() - + LOGGER.debug("Inference performed in {} seconds".format(inference_time_end - inference_time_start)) logging.info("Inference performed correctly") @@ -574,12 +563,10 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto for i, req in enumerate(self.active_requests): service_id = req.connection_metadata.service_id - device_id = req.connection_metadata.endpoint_id.device_id - endpoint_id = req.connection_metadata.endpoint_id # Check if a request of a new service has been received and, if so, create the monitored KPIs for that service if service_id not in self.service_ids: - self.create_kpis(service_id, device_id, endpoint_id) + self.create_kpis(service_id) self.service_ids.append(service_id) monitor_kpis_start = time.time() @@ -637,9 +624,7 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto self.false_positives += 1 self.total_predictions += 1 - - # if False: - notification_time_start = time.perf_counter() + notification_time_start = time.time() LOGGER.debug("Crypto attack detected") @@ -651,8 +636,11 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto try: logging.info("Sending the connection information to the Attack Mitigator component...") message = L3AttackmitigatorOutput(**cryptomining_detector_output[i]) - response = self.attackmitigator_client.PerformMitigation(message) - notification_time_end = time.perf_counter() + + am_response = self.attackmitigator_client.PerformMitigation(message) + LOGGER.debug("AM response: {}".format(am_response)) + + notification_time_end = time.time() self.am_notification_times.append(notification_time_end - notification_time_start) @@ -682,11 +670,8 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto f.write("Std notification time: {}\n".format(std_notification_time)) f.write("Median notification time: {}\n".format(median_notification_time)) - # logging.info("Attack Mitigator notified and received response: ", response.message) # FIX No message received logging.info("Attack Mitigator notified") - #return Empty(message="OK, information received and mitigator notified abou the attack") - except Exception as e: logging.error("Error notifying the Attack Mitigator component about the attack: ", e) logging.error("Couldn't find l3_attackmitigator") @@ -704,11 +689,24 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto self.total_predictions += 1 - # return Empty(message="Ok, information received (no attack detected)") - self.active_requests = [] + self.last_batch_request_time = time.time() + + col_values = [ + self.first_batch_request_time, + self.last_batch_request_time, + self.last_batch_request_time - self.first_batch_request_time, + BATCH_SIZE, + ] + + LOGGER.debug("col_values: {}".format(col_values)) + + with open(self.response_times_csv_file_path, "a", newline="") as file: + writer = csv.writer(file) + writer.writerow(col_values) + return Empty(message="Ok, metrics processed") - + return Empty(message="Ok, information received") def analyze_prediction_accuracy(self, confidence): @@ -766,6 +764,7 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto Send features allocated in the metadata of the onnx file to the DAD -output: ONNX metadata as a list of integers """ + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetFeaturesIds(self, request: Empty, context): features = AutoFeatures() diff --git a/src/l3_centralizedattackdetector/service/l3_centralizedattackdetectorServiceServicerImpl_old.py b/src/l3_centralizedattackdetector/service/l3_centralizedattackdetectorServiceServicerImpl_old.py deleted file mode 100644 index 1fdc955557f189d2f5aded162052743b3e762036..0000000000000000000000000000000000000000 --- a/src/l3_centralizedattackdetector/service/l3_centralizedattackdetectorServiceServicerImpl_old.py +++ /dev/null @@ -1,791 +0,0 @@ -# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import print_function -from datetime import datetime -from datetime import timedelta - -import os -import numpy as np -import onnxruntime as rt -import logging -import time - -from common.proto.l3_centralizedattackdetector_pb2 import Empty, AutoFeatures -from common.proto.l3_centralizedattackdetector_pb2_grpc import L3CentralizedattackdetectorServicer - -from common.proto.l3_attackmitigator_pb2 import L3AttackmitigatorOutput - -from common.proto.monitoring_pb2 import KpiDescriptor -from common.proto.kpi_sample_types_pb2 import KpiSampleType - -from monitoring.client.MonitoringClient import MonitoringClient -from common.proto.monitoring_pb2 import Kpi - -from common.tools.timestamp.Converters import timestamp_utcnow_to_float -from common.proto.context_pb2 import Timestamp, SliceId, ConnectionId - -from l3_attackmitigator.client.l3_attackmitigatorClient import l3_attackmitigatorClient - -import uuid - - -LOGGER = logging.getLogger(__name__) -# ML directory (ml_model/cryptomining_detector/cryptomining_detector.onnx) -current_dir = os.path.dirname(os.path.abspath(__file__)) - -# Demo constants -DEMO_MODE = True -ATTACK_IPS = ["37.187.95.110", "91.121.140.167", "94.23.23.52", "94.23.247.226", "149.202.83.171"] - - -class ConnectionInfo: - def __init__(self, ip_o, port_o, ip_d, port_d): - self.ip_o = ip_o - self.port_o = port_o - self.ip_d = ip_d - self.port_d = port_d - - def __eq__(self, other): - return ( - self.ip_o == other.ip_o - and self.port_o == other.port_o - and self.ip_d == other.ip_d - and self.port_d == other.port_d - ) - - def __str__(self): - return "ip_o: " + self.ip_o + "\nport_o: " + self.port_o + "\nip_d: " + self.ip_d + "\nport_d: " + self.port_d - - -class l3_centralizedattackdetectorServiceServicerImpl(L3CentralizedattackdetectorServicer): - - """ - Initialize variables, prediction model and clients of components used by CAD - """ - - def __init__(self): - LOGGER.info("Creating Centralized Attack Detector Service") - - self.inference_values = [] - self.inference_results = [] - self.cryptomining_detector_path = os.path.join(current_dir, "ml_model/cryptomining_detector/") - self.cryptomining_detector_file_name = os.listdir(self.cryptomining_detector_path)[0] - self.cryptomining_detector_model_path = os.path.join( - self.cryptomining_detector_path, self.cryptomining_detector_file_name - ) - self.cryptomining_detector_model = rt.InferenceSession(self.cryptomining_detector_model_path) - - # Load cryptomining detector features metadata from ONNX file - self.cryptomining_detector_features_metadata = list( - self.cryptomining_detector_model.get_modelmeta().custom_metadata_map.values() - ) - self.cryptomining_detector_features_metadata = [float(x) for x in self.cryptomining_detector_features_metadata] - self.cryptomining_detector_features_metadata.sort() - LOGGER.info("Cryptomining Detector Features: " + str(self.cryptomining_detector_features_metadata)) - - self.input_name = self.cryptomining_detector_model.get_inputs()[0].name - self.label_name = self.cryptomining_detector_model.get_outputs()[0].name - self.prob_name = self.cryptomining_detector_model.get_outputs()[1].name - - self.monitoring_client = MonitoringClient() - self.service_ids = [] - self.monitored_kpis = { - "l3_security_status": { - "kpi_id": None, - "description": "L3 - Confidence of the cryptomining detector in the security status in the last time interval of the service {service_id}", - "kpi_sample_type": KpiSampleType.KPISAMPLETYPE_L3_SECURITY_STATUS_CRYPTO, - "service_ids": [], - }, - "l3_ml_model_confidence": { - "kpi_id": None, - "description": "L3 - Security status of the service in a time interval of the service {service_id} (“0” if no attack has been detected on the service and “1” if a cryptomining attack has been detected)", - "kpi_sample_type": KpiSampleType.KPISAMPLETYPE_ML_CONFIDENCE, - "service_ids": [], - }, - "l3_unique_attack_conns": { - "kpi_id": None, - "description": "L3 - Number of attack connections detected in a time interval of the service {service_id} (attacks of the same connection [origin IP, origin port, destination IP and destination port] are only considered once)", - "kpi_sample_type": KpiSampleType.KPISAMPLETYPE_L3_UNIQUE_ATTACK_CONNS, - "service_ids": [], - }, - "l3_unique_compromised_clients": { - "kpi_id": None, - "description": "L3 - Number of unique compromised clients of the service in a time interval of the service {service_id} (attacks from the same origin IP are only considered once)", - "kpi_sample_type": KpiSampleType.KPISAMPLETYPE_L3_UNIQUE_COMPROMISED_CLIENTS, - "service_ids": [], - }, - "l3_unique_attackers": { - "kpi_id": None, - "description": "L3 - number of unique attackers of the service in a time interval of the service {service_id} (attacks from the same destination IP are only considered once)", - "kpi_sample_type": KpiSampleType.KPISAMPLETYPE_L3_UNIQUE_ATTACKERS, - "service_ids": [], - }, - } - self.attackmitigator_client = l3_attackmitigatorClient() - - # Environment variables - self.CLASSIFICATION_THRESHOLD = os.getenv("CAD_CLASSIFICATION_THRESHOLD", 0.5) - self.MONITORED_KPIS_TIME_INTERVAL_AGG = os.getenv("MONITORED_KPIS_TIME_INTERVAL_AGG", 60) - - # Constants - self.NORMAL_CLASS = 0 - self.CRYPTO_CLASS = 1 - - self.kpi_test = None - self.time_interval_start = None - self.time_interval_end = None - - # CAD evaluation tests - self.cad_inference_times = [] - self.cad_num_inference_measurements = 100 - - # AM evaluation tests - self.am_notification_times = [] - - # List of attack connections - self.attack_connections = [] - - self.correct_attack_conns = 0 - self.correct_predictions = 0 - self.total_predictions = 0 - self.false_positives = 0 - self.false_negatives = 0 - - """ - Create a monitored KPI for a specific service and add it to the Monitoring Client - -input: - + service_id: service ID where the KPI will be monitored - + kpi_name: name of the KPI - + kpi_description: description of the KPI - + kpi_sample_type: KPI sample type of the KPI (it must be defined in the kpi_sample_types.proto file) - -output: KPI identifier representing the KPI - """ - - def create_kpi( - self, - service_id, - kpi_name, - kpi_description, - kpi_sample_type, - ): - kpidescriptor = KpiDescriptor() - kpidescriptor.kpi_description = kpi_description - kpidescriptor.service_id.service_uuid.uuid = service_id.service_uuid.uuid - kpidescriptor.kpi_sample_type = kpi_sample_type - new_kpi = self.monitoring_client.SetKpi(kpidescriptor) - - LOGGER.info("Created KPI {}".format(kpi_name)) - - return new_kpi - - """ - Create the monitored KPIs for a specific service, add them to the Monitoring Client and store their identifiers in the monitored_kpis dictionary - -input: - + service_id: service ID where the KPIs will be monitored - -output: None - """ - - def create_kpis(self, service_id, device_id, endpoint_id): - LOGGER.info("Creating KPIs for service {}".format(service_id)) - - # for now, all the KPIs are created for all the services from which requests are received - for kpi in self.monitored_kpis: - # generate random slice_id - slice_id = SliceId() - slice_id.slice_uuid.uuid = str(uuid.uuid4()) - - # generate random connection_id - connection_id = ConnectionId() - connection_id.connection_uuid.uuid = str(uuid.uuid4()) - - created_kpi = self.create_kpi( - service_id, - kpi, - self.monitored_kpis[kpi]["description"].format(service_id=service_id.service_uuid.uuid), - self.monitored_kpis[kpi]["kpi_sample_type"], - ) - self.monitored_kpis[kpi]["kpi_id"] = created_kpi.kpi_id - self.monitored_kpis[kpi]["service_ids"].append(service_id.service_uuid.uuid) - - LOGGER.info("Created KPIs for service {}".format(service_id)) - - def monitor_kpis(self): - monitor_inference_results = self.inference_results - monitor_service_ids = self.service_ids - - LOGGER.debug("monitor_inference_results: {}".format(len(monitor_inference_results))) - LOGGER.debug("monitor_service_ids: {}".format(len(monitor_service_ids))) - - self.assign_timestamp(monitor_inference_results) - - self.delete_older_inference_results(monitor_inference_results) - - non_empty_time_interval = self.check_inference_time_interval(monitor_inference_results) - - if non_empty_time_interval: - # start = time.time() - for service_id in monitor_service_ids: - LOGGER.debug("service_id: {}".format(service_id)) - - self.monitor_compute_l3_kpi(service_id, monitor_inference_results) - - # Demo mode inference results are erased - """if DEMO_MODE: - # Delete fist half of the inference results - LOGGER.debug("inference_results len: {}".format(len(self.inference_results))) - self.inference_results = self.inference_results[len(self.inference_results)//2:] - LOGGER.debug("inference_results len after erase: {}".format(len(self.inference_results)))""" - # end = time.time() - # LOGGER.debug("Time to process inference results with erase: {}".format(end - start)) - LOGGER.debug("KPIs sent to monitoring server") - else: - LOGGER.debug("No KPIs sent to monitoring server") - - def assign_timestamp(self, monitor_inference_results): - time_interval = self.MONITORED_KPIS_TIME_INTERVAL_AGG - - # assign the timestamp of the first inference result to the time_interval_start - if self.time_interval_start is None: - self.time_interval_start = monitor_inference_results[0]["timestamp"] - LOGGER.debug("self.time_interval_start: {}".format(self.time_interval_start)) - - # add time_interval to the current time to get the time interval end - LOGGER.debug("time_interval: {}".format(time_interval)) - LOGGER.debug(timedelta(seconds=time_interval)) - self.time_interval_end = self.time_interval_start + timedelta(seconds=time_interval) - - current_time = datetime.utcnow() - - LOGGER.debug("current_time: {}".format(current_time)) - - if current_time >= self.time_interval_end: - self.time_interval_start = self.time_interval_end - self.time_interval_end = self.time_interval_start + timedelta(seconds=time_interval) - - LOGGER.debug("time_interval_start: {}".format(self.time_interval_start)) - LOGGER.debug("time_interval_end: {}".format(self.time_interval_end)) - - def delete_older_inference_results(self, monitor_inference_results): - # delete all inference results that are older than the time_interval_start - delete_inference_results = [] - - for i in range(len(monitor_inference_results)): - inference_result_timestamp = monitor_inference_results[i]["timestamp"] - - if inference_result_timestamp < self.time_interval_start: - delete_inference_results.append(monitor_inference_results[i]) - - if len(delete_inference_results) > 0: - monitor_inference_results = [ - inference_result - for inference_result in monitor_inference_results - if inference_result not in delete_inference_results - ] - if DEMO_MODE: - LOGGER.debug("inference_results len: {}".format(len(self.inference_results))) - self.inference_results = monitor_inference_results - LOGGER.debug("inference_results len after erase: {}".format(len(self.inference_results))) - LOGGER.debug(f"Cleaned inference results. {len(delete_inference_results)} inference results deleted") - - def check_inference_time_interval(self, monitor_inference_results): - # check if there is at least one inference result in monitor_inference_results in the current time_interval - num_inference_results_in_time_interval = 0 - for i in range(len(monitor_inference_results)): - inference_result_timestamp = monitor_inference_results[i]["timestamp"] - - if ( - inference_result_timestamp >= self.time_interval_start - and inference_result_timestamp < self.time_interval_end - ): - num_inference_results_in_time_interval += 1 - - if num_inference_results_in_time_interval > 0: - non_empty_time_interval = True - LOGGER.debug( - f"Current time interval is not empty (there are {num_inference_results_in_time_interval} inference results" - ) - else: - non_empty_time_interval = False - LOGGER.debug("Current time interval is empty. No KPIs will be reported.") - - return non_empty_time_interval - - def monitor_compute_l3_kpi(self, service_id, monitor_inference_results): - # L3 security status - kpi_security_status = Kpi() - kpi_security_status.kpi_id.kpi_id.CopyFrom(self.monitored_kpis["l3_security_status"]["kpi_id"]) - kpi_security_status.kpi_value.int32Val = self.monitor_security_status(service_id, monitor_inference_results) - - # L3 ML model confidence - kpi_conf = Kpi() - kpi_conf.kpi_id.kpi_id.CopyFrom(self.monitored_kpis["l3_ml_model_confidence"]["kpi_id"]) - kpi_conf.kpi_value.floatVal = self.monitor_ml_model_confidence( - service_id, monitor_inference_results, kpi_security_status - ) - - # L3 unique attack connections - kpi_unique_attack_conns = Kpi() - kpi_unique_attack_conns.kpi_id.kpi_id.CopyFrom(self.monitored_kpis["l3_unique_attack_conns"]["kpi_id"]) - kpi_unique_attack_conns.kpi_value.int32Val = self.monitor_unique_attack_conns( - service_id, monitor_inference_results - ) - - # L3 unique compromised clients - kpi_unique_compromised_clients = Kpi() - kpi_unique_compromised_clients.kpi_id.kpi_id.CopyFrom( - self.monitored_kpis["l3_unique_compromised_clients"]["kpi_id"] - ) - kpi_unique_compromised_clients.kpi_value.int32Val = self.monitor_unique_compromised_clients( - service_id, monitor_inference_results - ) - - # L3 unique attackers - kpi_unique_attackers = Kpi() - kpi_unique_attackers.kpi_id.kpi_id.CopyFrom(self.monitored_kpis["l3_unique_attackers"]["kpi_id"]) - kpi_unique_attackers.kpi_value.int32Val = self.monitor_unique_attackers(service_id, monitor_inference_results) - - timestamp = Timestamp() - timestamp.timestamp = timestamp_utcnow_to_float() - - kpi_security_status.timestamp.CopyFrom(timestamp) - kpi_conf.timestamp.CopyFrom(timestamp) - kpi_unique_attack_conns.timestamp.CopyFrom(timestamp) - kpi_unique_compromised_clients.timestamp.CopyFrom(timestamp) - kpi_unique_attackers.timestamp.CopyFrom(timestamp) - - LOGGER.debug("Sending KPIs to monitoring server") - - LOGGER.debug("kpi_security_status: {}".format(kpi_security_status)) - LOGGER.debug("kpi_conf: {}".format(kpi_conf)) - LOGGER.debug("kpi_unique_attack_conns: {}".format(kpi_unique_attack_conns)) - LOGGER.debug("kpi_unique_compromised_clients: {}".format(kpi_unique_compromised_clients)) - LOGGER.debug("kpi_unique_attackers: {}".format(kpi_unique_attackers)) - - try: - self.monitoring_client.IncludeKpi(kpi_security_status) - self.monitoring_client.IncludeKpi(kpi_conf) - self.monitoring_client.IncludeKpi(kpi_unique_attack_conns) - self.monitoring_client.IncludeKpi(kpi_unique_compromised_clients) - self.monitoring_client.IncludeKpi(kpi_unique_attackers) - except Exception as e: - LOGGER.debug("Error sending KPIs to monitoring server: {}".format(e)) - - def monitor_security_status(self, service_id, monitor_inference_results): - # get the output.tag of the ML model of the last aggregation time interval as indicated by the self.MONITORED_KPIS_TIME_INTERVAL_AGG variable - outputs_last_time_interval = [] - - for i in range(len(monitor_inference_results)): - if ( - monitor_inference_results[i]["timestamp"] >= self.time_interval_start - and monitor_inference_results[i]["timestamp"] < self.time_interval_end - and monitor_inference_results[i]["output"]["service_id"] == service_id - and service_id.service_uuid.uuid in self.monitored_kpis["l3_security_status"]["service_ids"] - ): - outputs_last_time_interval.append(monitor_inference_results[i]["output"]["tag"]) - - LOGGER.debug("outputs_last_time_interval: {}".format(outputs_last_time_interval)) - - # check if all outputs are 0 - all_outputs_zero = True - for output in outputs_last_time_interval: - if output != self.NORMAL_CLASS: - all_outputs_zero = False - break - - if all_outputs_zero: - return 0 - return 1 - - def monitor_ml_model_confidence(self, service_id, monitor_inference_results, kpi_security_status): - # get the output.confidence of the ML model of the last aggregation time interval as indicated by the self.MONITORED_KPIS_TIME_INTERVAL_AGG variable - confidences_normal_last_time_interval = [] - confidences_crypto_last_time_interval = [] - - for i in range(len(monitor_inference_results)): - LOGGER.debug("monitor_inference_results[i]: {}".format(monitor_inference_results[i])) - - if ( - monitor_inference_results[i]["timestamp"] >= self.time_interval_start - and monitor_inference_results[i]["timestamp"] < self.time_interval_end - and monitor_inference_results[i]["output"]["service_id"] == service_id - and service_id.service_uuid.uuid in self.monitored_kpis["l3_ml_model_confidence"]["service_ids"] - ): - if monitor_inference_results[i]["output"]["tag"] == self.NORMAL_CLASS: - confidences_normal_last_time_interval.append(monitor_inference_results[i]["output"]["confidence"]) - elif monitor_inference_results[i]["output"]["tag"] == self.CRYPTO_CLASS: - confidences_crypto_last_time_interval.append(monitor_inference_results[i]["output"]["confidence"]) - else: - LOGGER.debug("Unknown tag: {}".format(monitor_inference_results[i]["output"]["tag"])) - - LOGGER.debug("confidences_normal_last_time_interval: {}".format(confidences_normal_last_time_interval)) - LOGGER.debug("confidences_crypto_last_time_interval: {}".format(confidences_crypto_last_time_interval)) - - if kpi_security_status.kpi_value.int32Val == 0: - return np.mean(confidences_normal_last_time_interval) - - return np.mean(confidences_crypto_last_time_interval) - - def monitor_unique_attack_conns(self, service_id, monitor_inference_results): - # get the number of unique attack connections (grouping by origin IP, origin port, destination IP, destination port) of the last aggregation time interval as indicated by the self.MONITORED_KPIS_TIME_INTERVAL_AGG variable - num_unique_attack_conns_last_time_interval = 0 - unique_attack_conns_last_time_interval = [] - - for i in range(len(monitor_inference_results)): - if ( - monitor_inference_results[i]["timestamp"] >= self.time_interval_start - and monitor_inference_results[i]["timestamp"] < self.time_interval_end - and monitor_inference_results[i]["output"]["service_id"] == service_id - and service_id.service_uuid.uuid in self.monitored_kpis["l3_unique_attack_conns"]["service_ids"] - ): - if monitor_inference_results[i]["output"]["tag"] == self.CRYPTO_CLASS: - current_attack_conn = { - "ip_o": monitor_inference_results[i]["output"]["ip_o"], - "port_o": monitor_inference_results[i]["output"]["port_o"], - "ip_d": monitor_inference_results[i]["output"]["ip_d"], - "port_d": monitor_inference_results[i]["output"]["port_d"], - } - - is_unique_attack_conn = True - - for j in range(len(unique_attack_conns_last_time_interval)): - if current_attack_conn == unique_attack_conns_last_time_interval[j]: - is_unique_attack_conn = False - - if is_unique_attack_conn: - num_unique_attack_conns_last_time_interval += 1 - unique_attack_conns_last_time_interval.append(current_attack_conn) - - return num_unique_attack_conns_last_time_interval - - def monitor_unique_compromised_clients(self, service_id, monitor_inference_results): - # get the number of unique compromised clients (grouping by origin IP) of the last aggregation time interval as indicated by the self.MONITORED_KPIS_TIME_INTERVAL_AGG variable - num_unique_compromised_clients_last_time_interval = 0 - unique_compromised_clients_last_time_interval = [] - - for i in range(len(monitor_inference_results)): - if ( - monitor_inference_results[i]["timestamp"] >= self.time_interval_start - and monitor_inference_results[i]["timestamp"] < self.time_interval_end - and monitor_inference_results[i]["output"]["service_id"] == service_id - and service_id.service_uuid.uuid in self.monitored_kpis["l3_unique_compromised_clients"]["service_ids"] - ): - if monitor_inference_results[i]["output"]["tag"] == self.CRYPTO_CLASS: - if ( - monitor_inference_results[i]["output"]["ip_o"] - not in unique_compromised_clients_last_time_interval - ): - unique_compromised_clients_last_time_interval.append( - monitor_inference_results[i]["output"]["ip_o"] - ) - num_unique_compromised_clients_last_time_interval += 1 - - return num_unique_compromised_clients_last_time_interval - - def monitor_unique_attackers(self, service_id, monitor_inference_results): - # get the number of unique attackers (grouping by destination ip) of the last aggregation time interval as indicated by the self.MONITORED_KPIS_TIME_INTERVAL_AGG variable - num_unique_attackers_last_time_interval = 0 - unique_attackers_last_time_interval = [] - - for i in range(len(monitor_inference_results)): - if ( - monitor_inference_results[i]["timestamp"] >= self.time_interval_start - and monitor_inference_results[i]["timestamp"] < self.time_interval_end - and monitor_inference_results[i]["output"]["service_id"] == service_id - and service_id.service_uuid.uuid in self.monitored_kpis["l3_unique_attackers"]["service_ids"] - ): - if monitor_inference_results[i]["output"]["tag"] == self.CRYPTO_CLASS: - if monitor_inference_results[i]["output"]["ip_d"] not in unique_attackers_last_time_interval: - unique_attackers_last_time_interval.append(monitor_inference_results[i]["output"]["ip_d"]) - num_unique_attackers_last_time_interval += 1 - - return num_unique_attackers_last_time_interval - - """ - Classify connection as standard traffic or cryptomining attack and return results - -input: - + request: L3CentralizedattackdetectorMetrics object with connection features information - -output: L3AttackmitigatorOutput object with information about the assigned class and prediction confidence - """ - - def perform_inference(self, request): - x_data = np.array([[feature.feature for feature in request.features]]) - - # Print input data shape - LOGGER.debug("x_data.shape: {}".format(x_data.shape)) - - # Get batch size - batch_size = x_data.shape[0] - - # Print batch size - LOGGER.debug("batch_size: {}".format(batch_size)) - LOGGER.debug("x_data.shape: {}".format(x_data.shape)) - - inference_time_start = time.perf_counter() - - # Perform inference - predictions = self.cryptomining_detector_model.run( - [self.prob_name], {self.input_name: x_data.astype(np.float32)} - )[0] - - inference_time_end = time.perf_counter() - - # Measure inference time - inference_time = inference_time_end - inference_time_start - self.cad_inference_times.append(inference_time) - - if len(self.cad_inference_times) > self.cad_num_inference_measurements: - inference_times_np_array = np.array(self.cad_inference_times) - np.save(f"inference_times_{batch_size}.npy", inference_times_np_array) - - avg_inference_time = np.mean(inference_times_np_array) - max_inference_time = np.max(inference_times_np_array) - min_inference_time = np.min(inference_times_np_array) - std_inference_time = np.std(inference_times_np_array) - median_inference_time = np.median(inference_times_np_array) - - LOGGER.debug("Average inference time: {}".format(avg_inference_time)) - LOGGER.debug("Max inference time: {}".format(max_inference_time)) - LOGGER.debug("Min inference time: {}".format(min_inference_time)) - LOGGER.debug("Standard deviation inference time: {}".format(std_inference_time)) - LOGGER.debug("Median inference time: {}".format(median_inference_time)) - - with open(f"inference_times_stats_{batch_size}.txt", "w") as f: - f.write("Average inference time: {}\n".format(avg_inference_time)) - f.write("Max inference time: {}\n".format(max_inference_time)) - f.write("Min inference time: {}\n".format(min_inference_time)) - f.write("Standard deviation inference time: {}\n".format(std_inference_time)) - f.write("Median inference time: {}\n".format(median_inference_time)) - - # Gather the predicted class, the probability of that class and other relevant information required to block the attack - output_message = { - "confidence": None, - "timestamp": datetime.now().strftime("%d/%m/%Y %H:%M:%S"), - "ip_o": request.connection_metadata.ip_o, - "ip_d": request.connection_metadata.ip_d, - "tag_name": None, - "tag": None, - "flow_id": request.connection_metadata.flow_id, - "protocol": request.connection_metadata.protocol, - "port_o": request.connection_metadata.port_o, - "port_d": request.connection_metadata.port_d, - "ml_id": self.cryptomining_detector_file_name, - "service_id": request.connection_metadata.service_id, - "endpoint_id": request.connection_metadata.endpoint_id, - "time_start": request.connection_metadata.time_start, - "time_end": request.connection_metadata.time_end, - } - - if predictions[0][1] >= self.CLASSIFICATION_THRESHOLD: - output_message["confidence"] = predictions[0][1] - output_message["tag_name"] = "Crypto" - output_message["tag"] = self.CRYPTO_CLASS - else: - output_message["confidence"] = predictions[0][0] - output_message["tag_name"] = "Normal" - output_message["tag"] = self.NORMAL_CLASS - - return output_message - - """ - Receive features from Attack Mitigator, predict attack and communicate with Attack Mitigator - -input: - + request: L3CentralizedattackdetectorMetrics object with connection features information - -output: Empty object with a message about the execution of the function - """ - - def AnalyzeConnectionStatistics(self, request, context): - # Perform inference with the data sent in the request - logging.info("Performing inference...") - start = time.time() - cryptomining_detector_output = self.perform_inference(request) - end = time.time() - LOGGER.debug("Inference performed in {} seconds".format(end - start)) - logging.info("Inference performed correctly") - - self.inference_results.append({"output": cryptomining_detector_output, "timestamp": datetime.now()}) - LOGGER.debug("inference_results length: {}".format(len(self.inference_results))) - - service_id = request.connection_metadata.service_id - device_id = request.connection_metadata.endpoint_id.device_id - endpoint_id = request.connection_metadata.endpoint_id - - # Check if a request of a new service has been received and, if so, create the monitored KPIs for that service - if service_id not in self.service_ids: - self.create_kpis(service_id, device_id, endpoint_id) - self.service_ids.append(service_id) - - start = time.time() - self.monitor_kpis() - end = time.time() - - LOGGER.debug("Monitoring KPIs performed in {} seconds".format(end - start)) - LOGGER.debug("cryptomining_detector_output: {}".format(cryptomining_detector_output)) - - if DEMO_MODE: - self.analyze_prediction_accuracy(cryptomining_detector_output["confidence"]) - - connection_info = ConnectionInfo( - request.connection_metadata.ip_o, - request.connection_metadata.port_o, - request.connection_metadata.ip_d, - request.connection_metadata.port_d, - ) - - if cryptomining_detector_output["tag_name"] == "Crypto": - LOGGER.debug("Crypto found") - LOGGER.debug(connection_info) - - # Only notify Attack Mitigator when a cryptomining connection has been detected - if cryptomining_detector_output["tag_name"] == "Crypto" and connection_info not in self.attack_connections: - self.attack_connections.append(connection_info) - - if connection_info.ip_o in ATTACK_IPS or connection_info.ip_d in ATTACK_IPS: - self.correct_attack_conns += 1 - self.correct_predictions += 1 - else: - LOGGER.debug("False positive: {}".format(connection_info)) - self.false_positives += 1 - - self.total_predictions += 1 - - # if False: - notification_time_start = time.perf_counter() - - LOGGER.debug("Crypto attack detected") - - # Notify the Attack Mitigator component about the attack - logging.info( - "Notifying the Attack Mitigator component about the attack in order to block the connection..." - ) - - try: - logging.info("Sending the connection information to the Attack Mitigator component...") - message = L3AttackmitigatorOutput(**cryptomining_detector_output) - response = self.attackmitigator_client.PerformMitigation(message) - notification_time_end = time.perf_counter() - - self.am_notification_times.append(notification_time_end - notification_time_start) - - LOGGER.debug(f"am_notification_times length: {len(self.am_notification_times)}") - LOGGER.debug(f"last am_notification_time: {self.am_notification_times[-1]}") - - if len(self.am_notification_times) > 100: - am_notification_times_np_array = np.array(self.am_notification_times) - np.save("am_notification_times.npy", am_notification_times_np_array) - - avg_notification_time = np.mean(am_notification_times_np_array) - max_notification_time = np.max(am_notification_times_np_array) - min_notification_time = np.min(am_notification_times_np_array) - std_notification_time = np.std(am_notification_times_np_array) - median_notification_time = np.median(am_notification_times_np_array) - - LOGGER.debug("Average notification time: {}".format(avg_notification_time)) - LOGGER.debug("Max notification time: {}".format(max_notification_time)) - LOGGER.debug("Min notification time: {}".format(min_notification_time)) - LOGGER.debug("Std notification time: {}".format(std_notification_time)) - LOGGER.debug("Median notification time: {}".format(median_notification_time)) - - with open("am_notification_times_stats.txt", "w") as f: - f.write("Average notification time: {}\n".format(avg_notification_time)) - f.write("Max notification time: {}\n".format(max_notification_time)) - f.write("Min notification time: {}\n".format(min_notification_time)) - f.write("Std notification time: {}\n".format(std_notification_time)) - f.write("Median notification time: {}\n".format(median_notification_time)) - - # logging.info("Attack Mitigator notified and received response: ", response.message) # FIX No message received - logging.info("Attack Mitigator notified") - - return Empty(message="OK, information received and mitigator notified abou the attack") - except Exception as e: - logging.error("Error notifying the Attack Mitigator component about the attack: ", e) - logging.error("Couldn't find l3_attackmitigator") - - return Empty(message="Attack Mitigator not found") - else: - logging.info("No attack detected") - - if cryptomining_detector_output["tag_name"] != "Crypto": - if connection_info.ip_o not in ATTACK_IPS and connection_info.ip_d not in ATTACK_IPS: - self.correct_predictions += 1 - else: - LOGGER.debug("False negative: {}".format(connection_info)) - self.false_negatives += 1 - - self.total_predictions += 1 - - return Empty(message="Ok, information received (no attack detected)") - - def analyze_prediction_accuracy(self, confidence): - LOGGER.info("Number of Attack Connections Correctly Classified: {}".format(self.correct_attack_conns)) - LOGGER.info("Number of Attack Connections: {}".format(len(self.attack_connections))) - - if self.total_predictions > 0: - overall_detection_acc = self.correct_predictions / self.total_predictions - else: - overall_detection_acc = 0 - - LOGGER.info("Overall Detection Accuracy: {}\n".format(overall_detection_acc)) - - if len(self.attack_connections) > 0: - cryptomining_attack_detection_acc = self.correct_attack_conns / len(self.attack_connections) - else: - cryptomining_attack_detection_acc = 0 - - LOGGER.info("Cryptomining Attack Detection Accuracy: {}".format(cryptomining_attack_detection_acc)) - LOGGER.info("Cryptomining Detector Confidence: {}".format(confidence)) - - with open("prediction_accuracy.txt", "a") as f: - LOGGER.debug("Exporting prediction accuracy and confidence") - - f.write("Overall Detection Accuracy: {}\n".format(overall_detection_acc)) - f.write("Cryptomining Attack Detection Accuracy: {}\n".format(cryptomining_attack_detection_acc)) - f.write("Total Predictions: {}\n".format(self.total_predictions)) - f.write("Total Positives: {}\n".format(len(self.attack_connections))) - f.write("False Positives: {}\n".format(self.false_positives)) - f.write("True Negatives: {}\n".format(self.total_predictions - len(self.attack_connections))) - f.write("False Negatives: {}\n".format(self.false_negatives)) - f.write("Cryptomining Detector Confidence: {}\n\n".format(confidence)) - f.write("Timestamp: {}\n".format(datetime.now().strftime("%d/%m/%Y %H:%M:%S"))) - f.close() - - def AnalyzeBatchConnectionStatistics(self, request, context): - start = time.time() - - for metric in request.metrics: - self.AnalyzeConnectionStatistics(metric, context) - end = time.time() - - with open("batch_time.txt", "a") as f: - f.write(str(len(request.metrics)) + "\n") - f.write(str(end - start) + "\n\n") - f.close() - - logging.debug("Metrics: " + str(len(request.metrics))) - logging.debug("Batch time: " + str(end - start)) - - return Empty(message="OK, information received.") - - """ - Send features allocated in the metadata of the onnx file to the DAD - -output: ONNX metadata as a list of integers - """ - - def GetFeaturesIds(self, request: Empty, context): - features = AutoFeatures() - - for feature in self.cryptomining_detector_features_metadata: - features.auto_features.append(feature) - - return features diff --git a/src/tests/scenario3/l3/README.md b/src/tests/scenario3/l3/README.md index f66d8e351033d2762a77269243b6d3bb2a1d7022..2e243997d29b70436d1a1b88e2a3177951bfe970 100644 --- a/src/tests/scenario3/l3/README.md +++ b/src/tests/scenario3/l3/README.md @@ -1,3 +1,9 @@ -# Scripts to automatically run the "Attack Detection & Mitigation at the L3 Layer" workflow (Scenario 3). -"launch_l3_attack_detection_and_mitigation.sh" launches the TeraFlow OS components, which includes the CentralizedAttackDetector and AttackMitigator componentes necessary to perform this workflow. -"launch_l3_attack_detection_and_mitigation_complete.sh" also launches the DistributedAttackDetector, which monitors the network data plane and passively collects traffic packets and aggregates them in network flows, which are then provided to the CentralizedAttackDetector to detect attacks that may be occurring in the network. +# Demonstration of a L3 Cybersecurity Components for Attack Detection and Mitigation + +__Authors__: Partners of Universidad Politécnica de Madrid and Telefónica I+D + +## Executing + +```bash +python src/tests/scenario3/l3/run.sh +``` \ No newline at end of file diff --git a/src/tests/scenario3/l3/complete_deploy.sh b/src/tests/scenario3/l3/complete_deploy.sh deleted file mode 100755 index 5e8a2772c61ac2e96e5cf675468d27be2b940fe6..0000000000000000000000000000000000000000 --- a/src/tests/scenario3/l3/complete_deploy.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -./src/tests/ofc22/run_test_03_delete_service.sh -./src/tests/ofc22/run_test_04_cleanup.sh -source src/tests/ofc22/deploy_specs.sh -source my_deploy.sh -./deploy/all.sh -source tfs_runtime_env_vars.sh -ofc22/run_test_01_bootstrap.sh -ofc22/run_test_02_create_service.sh diff --git a/src/tests/scenario3/l3/copy_protos_to_dad.sh b/src/tests/scenario3/l3/copy_protos_to_dad.sh deleted file mode 100755 index 6735d9cf95d2243e6f87b5508c2e3f7b9756c474..0000000000000000000000000000000000000000 --- a/src/tests/scenario3/l3/copy_protos_to_dad.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Set the variables for the remote host and destination directory -REMOTE_HOST="192.168.165.73" -DEST_DIR="/home/ubuntu/TeraflowDockerDistributed/l3_distributedattackdetector/proto" - -# Copy the files to the remote host -sshpass -p "ubuntu" scp /home/ubuntu/tfs-ctrl-new/proto/src/python/l3_centralizedattackdetector_pb2.py "$REMOTE_HOST:$DEST_DIR" -sshpass -p "ubuntu" scp /home/ubuntu/tfs-ctrl-new/proto/src/python/l3_centralizedattackdetector_pb2_grpc.py "$REMOTE_HOST:$DEST_DIR" - -sshpass -p "ubuntu" scp /home/ubuntu/tfs-ctrl-new/proto/src/python/l3_attackmitigator_pb2.py "$REMOTE_HOST:$DEST_DIR" -sshpass -p "ubuntu" scp /home/ubuntu/tfs-ctrl-new/proto/src/python/l3_attackmitigator_pb2_grpc.py "$REMOTE_HOST:$DEST_DIR" diff --git a/src/tests/scenario3/l3/launch_l3_attack_detection_and_mitigation.sh b/src/tests/scenario3/l3/deploy.sh old mode 100644 new mode 100755 similarity index 90% rename from src/tests/scenario3/l3/launch_l3_attack_detection_and_mitigation.sh rename to src/tests/scenario3/l3/deploy.sh index a22d98bad6c203c825d3343c44e3d31674a41ec0..e8e02b026d42ea16d5df29da8a15c291f421e52c --- a/src/tests/scenario3/l3/launch_l3_attack_detection_and_mitigation.sh +++ b/src/tests/scenario3/l3/deploy.sh @@ -13,12 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -cd /home/ubuntu/tfs-ctrl -source my_deploy.sh -./deploy.sh -./show_deploy.sh - +source deploy_specs.sh +./deploy/all.sh source tfs_runtime_env_vars.sh - ofc22/run_test_01_bootstrap.sh ofc22/run_test_02_create_service.sh diff --git a/src/tests/scenario3/l3/deploy_l3_component.sh b/src/tests/scenario3/l3/deploy_l3_component.sh deleted file mode 100755 index 8e468c9067c93a06c6716ac618f5c9fdba860d34..0000000000000000000000000000000000000000 --- a/src/tests/scenario3/l3/deploy_l3_component.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash -# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -component=$1 - -source "my_deploy.sh" - -echo "Deploying $component..." - -# check if component == "CAD" -if [ $component == "CAD" ]; then - # find kubernetes pod that contains "centralizedattackdetectorservice" - pod=$(kubectl --namespace $TFS_K8S_NAMESPACE get pods | grep l3-centralizedattackdetectorservice | awk '{print $1}') - - # delete pod - kubectl --namespace $TFS_K8S_NAMESPACE delete pod $pod --force --grace-period=0 - - # # wait for pod to be deleted - # while [ $(kubectl --namespace $TFS_K8S_NAMESPACE get pods | grep l3-centralizedattackdetectorservice | wc -l) -gt 0 ]; do - # sleep 1 - # done - - # deploy l3_centralizedattackdetector component - ./deploy_component.sh "l3_centralizedattackdetector" -fi - -# check if component == "AM" -if [ $component == "AM" ]; then - # find kubernetes pod that contains "l3-attackmitigatorservice" - pod=$(kubectl --namespace $TFS_K8S_NAMESPACE get pods | grep l3-attackmitigatorservice | awk '{print $1}') - - # delete pod - kubectl --namespace $TFS_K8S_NAMESPACE delete pod $pod --force --grace-period=0 - - # # wait for pod to be deleted - # while [ $(kubectl --namespace $TFS_K8S_NAMESPACE get pods | grep l3-attackmitigatorservice | wc -l) -gt 0 ]; do - # sleep 1 - # done - - # deploy l3_attackmitigator component - ./deploy_component.sh "l3_attackmitigator" -fi - -echo "Component $component deployed" - -echo "Restarting DAD..." -sshpass -p "ubuntu" ssh -o StrictHostKeyChecking=no -n -f ubuntu@192.168.165.73 "sh -c 'nohup /home/ubuntu/TeraflowDockerDistributed/restart.sh > /dev/null 2>&1 &'" -echo "DAD restarted" diff --git a/src/tests/scenario3/l3/deploy_specs.sh b/src/tests/scenario3/l3/deploy_specs.sh new file mode 100644 index 0000000000000000000000000000000000000000..c3c9122b8594908c9d9f7d9a56daa4f8d0d8cf52 --- /dev/null +++ b/src/tests/scenario3/l3/deploy_specs.sh @@ -0,0 +1,126 @@ +#!/bin/bash +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# ----- TeraFlowSDN ------------------------------------------------------------ + +# Set the URL of the internal MicroK8s Docker registry where the images will be uploaded to. +export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/" + +# Set the list of components, separated by spaces, you want to build images for, and deploy. +export TFS_COMPONENTS="context device pathcomp service slice compute webui load_generator monitoring automation l3_attackmitigator l3_centralizedattackdetector" + +# Set the tag you want to use for your images. +export TFS_IMAGE_TAG="dev" + +# Set the name of the Kubernetes namespace to deploy TFS to. +export TFS_K8S_NAMESPACE="tfs" + +# Set additional manifest files to be applied after the deployment +export TFS_EXTRA_MANIFESTS="manifests/nginx_ingress_http.yaml manifests/servicemonitors.yaml" + +# Set the new Grafana admin password +export TFS_GRAFANA_PASSWORD="admin123+" + +# Disable skip-build flag to rebuild the Docker images. +export TFS_SKIP_BUILD="" + + +# ----- CockroachDB ------------------------------------------------------------ + +# Set the namespace where CockroackDB will be deployed. +export CRDB_NAMESPACE="crdb" + +# Set the external port CockroackDB Postgre SQL interface will be exposed to. +export CRDB_EXT_PORT_SQL="26257" + +# Set the external port CockroackDB HTTP Mgmt GUI interface will be exposed to. +export CRDB_EXT_PORT_HTTP="8081" + +# Set the database username to be used by Context. +export CRDB_USERNAME="tfs" + +# Set the database user's password to be used by Context. +export CRDB_PASSWORD="tfs123" + +# Set the database name to be used by Context. +export CRDB_DATABASE="tfs" + +# Set CockroachDB installation mode to 'single'. This option is convenient for development and testing. +# See ./deploy/all.sh or ./deploy/crdb.sh for additional details +export CRDB_DEPLOY_MODE="single" + +# Disable flag for dropping database, if it exists. +export CRDB_DROP_DATABASE_IF_EXISTS="YES" + +# Disable flag for re-deploying CockroachDB from scratch. +export CRDB_REDEPLOY="YES" + + +# ----- NATS ------------------------------------------------------------------- + +# Set the namespace where NATS will be deployed. +export NATS_NAMESPACE="nats" + +# Set the external port NATS Client interface will be exposed to. +export NATS_EXT_PORT_CLIENT="4222" + +# Set the external port NATS HTTP Mgmt GUI interface will be exposed to. +export NATS_EXT_PORT_HTTP="8222" + +# Disable flag for re-deploying NATS from scratch. +export NATS_REDEPLOY="YES" + + +# ----- QuestDB ---------------------------------------------------------------- + +# Set the namespace where QuestDB will be deployed. +export QDB_NAMESPACE="qdb" + +# Set the external port QuestDB Postgre SQL interface will be exposed to. +export QDB_EXT_PORT_SQL="8812" + +# Set the external port QuestDB Influx Line Protocol interface will be exposed to. +export QDB_EXT_PORT_ILP="9009" + +# Set the external port QuestDB HTTP Mgmt GUI interface will be exposed to. +export QDB_EXT_PORT_HTTP="9000" + +# Set the database username to be used for QuestDB. +export QDB_USERNAME="admin" + +# Set the database user's password to be used for QuestDB. +export QDB_PASSWORD="quest" + +# Set the table name to be used by Monitoring for KPIs. +export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis" + +# Set the table name to be used by Slice for plotting groups. +export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups" + +# Disable flag for dropping tables if they exist. +export QDB_DROP_TABLES_IF_EXIST="YES" + +# Disable flag for re-deploying QuestDB from scratch. +export QDB_REDEPLOY="YES" + + +# ----- K8s Observability ------------------------------------------------------ + +# Set the external port Prometheus Mgmt HTTP GUI interface will be exposed to. +export PROM_EXT_PORT_HTTP="9090" + +# Set the external port Grafana HTTP Dashboards will be exposed to. +export GRAF_EXT_PORT_HTTP="3000" diff --git a/src/tests/scenario3/l3/get_ml_model_info.sh b/src/tests/scenario3/l3/get_ml_model_info.sh deleted file mode 100755 index 19fb1177a23c13e4cdffd2e8c75df3aad3502c04..0000000000000000000000000000000000000000 --- a/src/tests/scenario3/l3/get_ml_model_info.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -pod=$(kubectl get pods -n "tfs" -l app=l3-centralizedattackdetectorservice | sed -n '2p' | cut -d " " -f1) -while true; do - kubectl -n "tfs" cp $pod:prediction_accuracy.txt ./prediction_accuracy.txt - clear - cat prediction_accuracy.txt | tail -n 10 - sleep 1 -done diff --git a/src/tests/scenario3/l3/grafana_dashboard.json b/src/tests/scenario3/l3/grafana_dashboard.json new file mode 100644 index 0000000000000000000000000000000000000000..3376931290b3a63ca9ee70ca8ad558b21e732f6a --- /dev/null +++ b/src/tests/scenario3/l3/grafana_dashboard.json @@ -0,0 +1,1211 @@ +{ + "overwrite": true, + "folderId": 0, + "dashboard": { + "id": null, + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "iteration": 1675103296430, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "postgres", + "uid": "questdb" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": ".*PACKETS_.*" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "unit", + "value": "pps" + }, + { + "id": "custom.axisLabel", + "value": "Packets / sec" + }, + { + "id": "custom.axisSoftMin", + "value": 0 + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": ".*BYTES_.*" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "unit", + "value": "Bps" + }, + { + "id": "custom.axisLabel", + "value": "Bytes / sec" + }, + { + "id": "custom.axisSoftMin", + "value": 0 + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "first", + "min", + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "questdb" + }, + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "kpi_value", + "rawQuery": true, + "rawSql": "SELECT\r\n $__time(timestamp), kpi_value AS metric, service_id, kpi_sample_type\r\nFROM\r\n tfs_monitoring\r\nWHERE\r\n $__timeFilter(timestamp) AND device_name IN (${device_name}) AND endpoint_name IN (${endpoint_name}) AND kpi_sample_type IN (${kpi_sample_type}) AND kpi_sample_type LIKE 'L3_SECURITY_STATUS_CRYPTO'\r\nGROUP BY\r\n device_name, endpoint_name, kpi_sample_type\r\nORDER BY\r\n timestamp", + "refId": "A", + "select": [ + [ + { + "params": [ + "kpi_value" + ], + "type": "column" + } + ] + ], + "table": "monitoring", + "timeColumn": "timestamp", + "where": [ + { + "name": "", + "params": [ + "device_id", + "IN", + "$device_id" + ], + "type": "expression" + } + ] + } + ], + "title": "L3 Security Status Crypto", + "transformations": [ + { + "id": "renameByRegex", + "options": { + "regex": "metric {kpi_sample_type=\\\"([^\\\"]+)\\\", service_id=\\\"([^\\\"]+)\\\"}", + "renamePattern": "Security Status Crypto - L3 (Service Id: $2)" + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "postgres", + "uid": "questdb" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#2635d4", + "mode": "fixed" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": ".*PACKETS_.*" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "unit", + "value": "pps" + }, + { + "id": "custom.axisLabel", + "value": "Packets / sec" + }, + { + "id": "custom.axisSoftMin", + "value": 0 + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": ".*BYTES_.*" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "unit", + "value": "Bps" + }, + { + "id": "custom.axisLabel", + "value": "Bytes / sec" + }, + { + "id": "custom.axisSoftMin", + "value": 0 + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 8, + "y": 0 + }, + "id": 3, + "options": { + "legend": { + "calcs": [ + "first", + "min", + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "questdb" + }, + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "kpi_value", + "rawQuery": true, + "rawSql": "SELECT\r\n $__time(timestamp), kpi_value AS metric, service_id, kpi_sample_type\r\nFROM\r\n tfs_monitoring\r\nWHERE\r\n $__timeFilter(timestamp) AND device_name IN (${device_name}) AND endpoint_name IN (${endpoint_name}) AND kpi_sample_type IN (${kpi_sample_type}) AND kpi_sample_type LIKE 'ML_CONFIDENCE'\r\nGROUP BY\r\n device_name, endpoint_name, kpi_sample_type\r\nORDER BY\r\n timestamp", + "refId": "A", + "select": [ + [ + { + "params": [ + "kpi_value" + ], + "type": "column" + } + ] + ], + "table": "monitoring", + "timeColumn": "timestamp", + "where": [ + { + "name": "", + "params": [ + "device_id", + "IN", + "$device_id" + ], + "type": "expression" + } + ] + } + ], + "title": "L3 ML Confidence", + "transformations": [ + { + "id": "renameByRegex", + "options": { + "regex": "metric {kpi_sample_type=\\\"([^\\\"]+)\\\", service_id=\\\"([^\\\"]+)\\\"}", + "renamePattern": "ML Confidence - L3 (Service Id: $2)" + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "postgres", + "uid": "questdb" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "dark-orange", + "mode": "fixed" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": ".*PACKETS_.*" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "unit", + "value": "pps" + }, + { + "id": "custom.axisLabel", + "value": "Packets / sec" + }, + { + "id": "custom.axisSoftMin", + "value": 0 + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": ".*BYTES_.*" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "unit", + "value": "Bps" + }, + { + "id": "custom.axisLabel", + "value": "Bytes / sec" + }, + { + "id": "custom.axisSoftMin", + "value": 0 + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "calcs": [ + "first", + "min", + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "questdb" + }, + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "kpi_value", + "rawQuery": true, + "rawSql": "SELECT\r\n $__time(timestamp), kpi_value AS metric, service_id, kpi_sample_type\r\nFROM\r\n tfs_monitoring\r\nWHERE\r\n $__timeFilter(timestamp) AND device_name IN (${device_name}) AND endpoint_name IN (${endpoint_name}) AND kpi_sample_type IN (${kpi_sample_type}) AND kpi_sample_type LIKE 'L3_UNIQUE_ATTACK_CONNS'\r\nGROUP BY\r\n device_name, endpoint_name, kpi_sample_type\r\nORDER BY\r\n timestamp", + "refId": "A", + "select": [ + [ + { + "params": [ + "kpi_value" + ], + "type": "column" + } + ] + ], + "table": "monitoring", + "timeColumn": "timestamp", + "where": [ + { + "name": "", + "params": [ + "device_id", + "IN", + "$device_id" + ], + "type": "expression" + } + ] + } + ], + "title": "L3 Unique Attack Connections", + "transformations": [ + { + "id": "renameByRegex", + "options": { + "regex": "metric {kpi_sample_type=\\\"([^\\\"]+)\\\", service_id=\\\"([^\\\"]+)\\\"}", + "renamePattern": "Unique Attack Connections - L3 (Service Id: $2)" + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "postgres", + "uid": "questdb" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#ad3fe3", + "mode": "fixed" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": ".*PACKETS_.*" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "unit", + "value": "pps" + }, + { + "id": "custom.axisLabel", + "value": "Packets / sec" + }, + { + "id": "custom.axisSoftMin", + "value": 0 + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": ".*BYTES_.*" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "unit", + "value": "Bps" + }, + { + "id": "custom.axisLabel", + "value": "Bytes / sec" + }, + { + "id": "custom.axisSoftMin", + "value": 0 + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 5, + "options": { + "legend": { + "calcs": [ + "first", + "min", + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "questdb" + }, + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "kpi_value", + "rawQuery": true, + "rawSql": "SELECT\r\n $__time(timestamp), kpi_value AS metric, service_id, kpi_sample_type\r\nFROM\r\n tfs_monitoring\r\nWHERE\r\n $__timeFilter(timestamp) AND device_name IN (${device_name}) AND endpoint_name IN (${endpoint_name}) AND kpi_sample_type IN (${kpi_sample_type}) AND kpi_sample_type LIKE 'L3_UNIQUE_COMPROMISED_CLIENTS'\r\nGROUP BY\r\n device_name, endpoint_name, kpi_sample_type\r\nORDER BY\r\n timestamp", + "refId": "A", + "select": [ + [ + { + "params": [ + "kpi_value" + ], + "type": "column" + } + ] + ], + "table": "monitoring", + "timeColumn": "timestamp", + "where": [ + { + "name": "", + "params": [ + "device_id", + "IN", + "$device_id" + ], + "type": "expression" + } + ] + } + ], + "title": "L3 Unique Compromised Clients", + "transformations": [ + { + "id": "renameByRegex", + "options": { + "regex": "metric {kpi_sample_type=\\\"([^\\\"]+)\\\", service_id=\\\"([^\\\"]+)\\\"}", + "renamePattern": "Unique Compromised Clients - L3 (Service Id: $2)" + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "postgres", + "uid": "questdb" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#f6ca1b", + "mode": "fixed" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": ".*PACKETS_.*" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "unit", + "value": "pps" + }, + { + "id": "custom.axisLabel", + "value": "Packets / sec" + }, + { + "id": "custom.axisSoftMin", + "value": 0 + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": ".*BYTES_.*" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "unit", + "value": "Bps" + }, + { + "id": "custom.axisLabel", + "value": "Bytes / sec" + }, + { + "id": "custom.axisSoftMin", + "value": 0 + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 6, + "options": { + "legend": { + "calcs": [ + "first", + "min", + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "questdb" + }, + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "kpi_value", + "rawQuery": true, + "rawSql": "SELECT\r\n $__time(timestamp), kpi_value AS metric, service_id, kpi_sample_type\r\nFROM\r\n tfs_monitoring\r\nWHERE\r\n $__timeFilter(timestamp) AND device_name IN (${device_name}) AND endpoint_name IN (${endpoint_name}) AND kpi_sample_type IN (${kpi_sample_type}) AND kpi_sample_type LIKE 'L3_UNIQUE_ATTACKERS'\r\nGROUP BY\r\n device_name, endpoint_name, kpi_sample_type\r\nORDER BY\r\n timestamp", + "refId": "A", + "select": [ + [ + { + "params": [ + "kpi_value" + ], + "type": "column" + } + ] + ], + "table": "monitoring", + "timeColumn": "timestamp", + "where": [ + { + "name": "", + "params": [ + "device_id", + "IN", + "$device_id" + ], + "type": "expression" + } + ] + } + ], + "title": "L3 Unique Attackers", + "transformations": [ + { + "id": "renameByRegex", + "options": { + "regex": "metric {kpi_sample_type=\\\"([^\\\"]+)\\\", service_id=\\\"([^\\\"]+)\\\"}", + "renamePattern": "Unique Attackers - L3 (Service Id: $2)" + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "postgres", + "uid": "questdb" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": ".*PACKETS_.*" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "unit", + "value": "pps" + }, + { + "id": "custom.axisLabel", + "value": "Packets / sec" + }, + { + "id": "custom.axisSoftMin", + "value": 0 + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": ".*BYTES_.*" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "unit", + "value": "Bps" + }, + { + "id": "custom.axisLabel", + "value": "Bytes / sec" + }, + { + "id": "custom.axisSoftMin", + "value": 0 + } + ] + } + ] + }, + "gridPos": { + "h": 19, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 7, + "options": { + "legend": { + "calcs": [ + "first", + "min", + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "questdb" + }, + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "kpi_value", + "rawQuery": true, + "rawSql": "SELECT\r\n $__time(timestamp), kpi_value AS metric, device_name, endpoint_name, kpi_sample_type\r\nFROM\r\n tfs_monitoring\r\nWHERE\r\n $__timeFilter(timestamp) AND device_name IN (${device_name}) AND endpoint_name IN (${endpoint_name}) AND kpi_sample_type IN (${kpi_sample_type})\r\nGROUP BY\r\n device_name, endpoint_name, kpi_sample_type\r\nORDER BY\r\n timestamp", + "refId": "A", + "select": [ + [ + { + "params": [ + "kpi_value" + ], + "type": "column" + } + ] + ], + "table": "monitoring", + "timeColumn": "timestamp", + "where": [ + { + "name": "", + "params": [ + "device_id", + "IN", + "$device_id" + ], + "type": "expression" + } + ] + } + ], + "title": "L3 Monitoring Packets/Bytes Received/Sent", + "transformations": [ + { + "id": "renameByRegex", + "options": { + "regex": "metric {device_name=\\\"([^\\\"]+)\\\", endpoint_name=\\\"([^\\\"]+)\\\", kpi_sample_type=\\\"([^\\\"]+)\\\"}", + "renamePattern": "$3 ($1 : $2)" + } + } + ], + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 36, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "postgres", + "uid": "questdb" + }, + "definition": "SELECT DISTINCT device_name FROM tfs_monitoring;", + "hide": 0, + "includeAll": true, + "label": "Device", + "multi": true, + "name": "device_name", + "options": [], + "query": "SELECT DISTINCT device_name FROM tfs_monitoring;", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "postgres", + "uid": "questdb" + }, + "definition": "SELECT DISTINCT endpoint_name FROM tfs_monitoring WHERE device_name IN (${device_name})", + "hide": 0, + "includeAll": true, + "label": "EndPoint", + "multi": true, + "name": "endpoint_name", + "options": [], + "query": "SELECT DISTINCT endpoint_name FROM tfs_monitoring WHERE device_name IN (${device_name})", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "postgres", + "uid": "questdb" + }, + "definition": "SELECT DISTINCT kpi_sample_type FROM tfs_monitoring;", + "hide": 0, + "includeAll": true, + "label": "Kpi Sample Type", + "multi": true, + "name": "kpi_sample_type", + "options": [], + "query": "SELECT DISTINCT kpi_sample_type FROM tfs_monitoring;", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "L3 Monitoring", + "uid": "tf-l3-monit", + "version": 6, + "weekStart": "" + } +} \ No newline at end of file diff --git a/src/tests/scenario3/l3/launch_l3_attack_detection_and_mitigation_complete.sh b/src/tests/scenario3/l3/launch_l3_attack_detection_and_mitigation_complete.sh deleted file mode 100644 index 05b20077eb951102ab11fc90aaab53463c41f94f..0000000000000000000000000000000000000000 --- a/src/tests/scenario3/l3/launch_l3_attack_detection_and_mitigation_complete.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -cd /home/ubuntu/tfs-ctrl -source my_deploy.sh -./deploy.sh -./show_deploy.sh - -source tfs_runtime_env_vars.sh - -ofc22/run_test_01_bootstrap.sh -ofc22/run_test_02_create_service.sh - -sshpass -p "ubuntu" ssh -o StrictHostKeyChecking=no -n -f ubuntu@192.168.165.73 "sh -c 'nohup /home/ubuntu/TeraflowDockerDistributed/restart.sh > /dev/null 2>&1 &'" diff --git a/src/tests/scenario3/l3/launch_webui.sh b/src/tests/scenario3/l3/launch_webui.sh deleted file mode 100755 index bf1867eb108331647f3ad343b0ab23d098617aff..0000000000000000000000000000000000000000 --- a/src/tests/scenario3/l3/launch_webui.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -ssh -L 12345:localhost:80 ubuntu@192.168.165.78 diff --git a/src/tests/scenario3/l3/run.sh b/src/tests/scenario3/l3/run.sh new file mode 100644 index 0000000000000000000000000000000000000000..c62ea89ea7e9bfd15e387af56ec19f23206df945 --- /dev/null +++ b/src/tests/scenario3/l3/run.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Deploy TeraFlowSDN with L3 Cybersecurity Components for Attack Detection and Mitigation +echo "Deploying TFS with L3 Cybersecurity Components for Attack Detection and Mitigation..." +./deploy.sh +echo "TFS deployed." + +# Deploy Distributed Attack Detector +if $DAD_NODE_PASSWORD == "" || $DAD_NODE_IP == ""; then + echo "Please set the DAD_NODE_PASSWORD and DAD_NODE_IP environment variables." + exit 1 +fi + +echo "Deploying Distributed Attack Detector..." +sshpass -p $DAD_NODE_PASSWORD ssh -o StrictHostKeyChecking=no -n -f ubuntu@$DAD_NODE_IP "sh -c 'nohup /home/ubuntu/TeraflowDockerDistributed/restart.sh > /dev/null 2>&1 &'" +echo "Distributed Attack Detector deployed." \ No newline at end of file