diff --git a/error_monitoring.txt b/error_monitoring.txt new file mode 100644 index 0000000000000000000000000000000000000000..6c7df6929c86ff2734e640e52a22bceedacd5e11 --- /dev/null +++ b/error_monitoring.txt @@ -0,0 +1,23 @@ +[2022-09-30 10:06:58,753] {/var/teraflow/monitoring/service/MonitoringServiceServicerImpl.py:151} INFO - IncludeKpi +INFO:monitoringservice-server:IncludeKpi +[2022-09-30 10:06:58,754] {/var/teraflow/monitoring/service/MonitoringServiceServicerImpl.py:194} INFO - getting Kpi by KpiID +INFO:monitoringservice-server:getting Kpi by KpiID +[2022-09-30 10:06:58,764] {/var/teraflow/monitoring/service/MonitoringServiceServicerImpl.py:215} ERROR - GetKpiDescriptor exception +Traceback (most recent call last): + File "/var/teraflow/monitoring/service/MonitoringServiceServicerImpl.py", line 196, in GetKpiDescriptor + kpi_db = self.sql_db.get_KPI(int(request.kpi_id.uuid)) +ValueError: invalid literal for int() with base 10: 'kpi_id {\n uuid: "17"\n}\n' +ERROR:monitoringservice-server:GetKpiDescriptor exception +Traceback (most recent call last): + File "/var/teraflow/monitoring/service/MonitoringServiceServicerImpl.py", line 196, in GetKpiDescriptor + kpi_db = self.sql_db.get_KPI(int(request.kpi_id.uuid)) +ValueError: invalid literal for int() with base 10: 'kpi_id {\n uuid: "17"\n}\n' +[2022-09-30 10:06:58,780] {/var/teraflow/monitoring/service/MonitoringServiceServicerImpl.py:156} WARNING - Ignoring sample with KPIId(kpi_id { + uuid: "kpi_id {\n uuid: \"17\"\n}\n" +} +): not found in database +WARNING:monitoringservice-server:Ignoring sample with KPIId(kpi_id { + uuid: "kpi_id {\n uuid: \"17\"\n}\n" +} +): not found in database +[2022-09-30 10:06:58,807] {/var/teraflow/monitoring/service/MonitoringServiceServicerImpl.py:151} INFO - IncludeKpi diff --git a/scripts/show_logs_am.sh b/scripts/show_logs_am.sh new file mode 100755 index 0000000000000000000000000000000000000000..1cbd1994d2732f07a46ec8ec0bb1ffbad9b33a28 --- /dev/null +++ b/scripts/show_logs_am.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# 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. + +######################################################################################################################## +# Define your deployment settings here +######################################################################################################################## + +# If not already set, set the name of the Kubernetes namespace to deploy to. +export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"} + +######################################################################################################################## +# Automated steps start here +######################################################################################################################## + +kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/l3-attackmitigatorservice -c server diff --git a/scripts/show_logs_cad.sh b/scripts/show_logs_cad.sh new file mode 100755 index 0000000000000000000000000000000000000000..acfdad79eebf630b8e3d2e4a3f03053fe7c6e660 --- /dev/null +++ b/scripts/show_logs_cad.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# 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. + +######################################################################################################################## +# Define your deployment settings here +######################################################################################################################## + +# If not already set, set the name of the Kubernetes namespace to deploy to. +export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"} + +######################################################################################################################## +# Automated steps start here +######################################################################################################################## + +kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/l3-centralizedattackdetectorservice -c server diff --git a/src/l3_centralizedattackdetector/.gitlab-ci.yml b/src/l3_centralizedattackdetector/.gitlab-ci.yml index 073b7925c7f7c39e1a44766fc21167236875877d..f0e40a383e9077d35c5abf674bbb5ca98d348cfe 100644 --- a/src/l3_centralizedattackdetector/.gitlab-ci.yml +++ b/src/l3_centralizedattackdetector/.gitlab-ci.yml @@ -52,7 +52,7 @@ unit test l3_centralizedattackdetector: - if docker container ls | grep $IMAGE_NAME; then docker rm -f $IMAGE_NAME; else echo "$IMAGE_NAME image is not in the system"; fi script: - docker pull "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG" - - docker run --name $IMAGE_NAME -d -p 10001:10001 -v "$PWD/src/$IMAGE_NAME/tests:/opt/results" --network=teraflowbridge $CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG + - docker run --name $IMAGE_NAME -d -p 10001:10001 --env CAD_CLASSIFICATION_THRESHOLD=0.5 -v "$PWD/src/$IMAGE_NAME/tests:/opt/results" --network=teraflowbridge $CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG - sleep 5 - docker ps -a - docker logs $IMAGE_NAME diff --git a/src/l3_centralizedattackdetector/service/l3_centralizedattackdetectorServiceServicerImpl.py b/src/l3_centralizedattackdetector/service/l3_centralizedattackdetectorServiceServicerImpl.py index 0b55eaa956e46f7473f3b1e6770f779d08f7362f..50151efb5f0909df5c90da2c01799dd1f0688b4d 100644 --- a/src/l3_centralizedattackdetector/service/l3_centralizedattackdetectorServiceServicerImpl.py +++ b/src/l3_centralizedattackdetector/service/l3_centralizedattackdetectorServiceServicerImpl.py @@ -14,22 +14,22 @@ from __future__ import print_function from datetime import datetime + import os import grpc import numpy as np import onnxruntime as rt import logging + from common.proto.l3_centralizedattackdetector_pb2 import Empty from common.proto.l3_centralizedattackdetector_pb2_grpc import L3CentralizedattackdetectorServicer from common.proto.l3_attackmitigator_pb2 import L3AttackmitigatorOutput from common.proto.l3_attackmitigator_pb2_grpc import L3AttackmitigatorStub -# KPIs and Monitoring from common.proto.monitoring_pb2 import KpiDescriptor from common.proto.kpi_sample_types_pb2 import KpiSampleType -# from monitoring.client.MonitoringClient import MonitoringClient from monitoring.client.MonitoringClient import MonitoringClient from common.proto.monitoring_pb2 import Kpi @@ -37,15 +37,16 @@ from common.tools.timestamp.Converters import timestamp_utcnow_to_float from common.proto.context_pb2 import Timestamp LOGGER = logging.getLogger(__name__) -here = os.path.dirname(os.path.abspath(__file__)) -MODEL_FILE = os.path.join(here, "ml_model/crypto_5g_rf_spider_features.onnx") +current_dir = os.path.dirname(os.path.abspath(__file__)) +MODEL_FILE = os.path.join(current_dir, "ml_model/crypto_5g_rf_spider_features.onnx") classification_threshold = os.getenv("CAD_CLASSIFICATION_THRESHOLD", 0.5) class l3_centralizedattackdetectorServiceServicerImpl(L3CentralizedattackdetectorServicer): def __init__(self): - LOGGER.debug("Creating Servicer...") + LOGGER.info("Creating Centralized Attack Detector Service") + self.inference_values = [] self.model = rt.InferenceSession(MODEL_FILE) self.input_name = self.model.get_inputs()[0].name @@ -56,11 +57,9 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto self.class_probability_kpi_id = None def create_predicted_class_kpi(self, client: MonitoringClient, service_id): - # create kpi kpi_description: KpiDescriptor = KpiDescriptor() kpi_description.kpi_description = "L3 security status of service {}".format(service_id) - # kpi_description.service_id.service_uuid.uuid = service_id - kpi_description.service_id.service_uuid.uuid = str(service_id) + kpi_description.service_id.service_uuid.uuid = service_id.service_uuid.uuid kpi_description.kpi_sample_type = KpiSampleType.KPISAMPLETYPE_UNKNOWN new_kpi = client.SetKpi(kpi_description) @@ -69,10 +68,9 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto return new_kpi def create_class_prob_kpi(self, client: MonitoringClient, service_id): - # create kpi kpi_description: KpiDescriptor = KpiDescriptor() kpi_description.kpi_description = "L3 security status of service {}".format(service_id) - kpi_description.service_id.service_uuid.uuid = str(service_id) + kpi_description.service_id.service_uuid.uuid = service_id.service_uuid.uuid kpi_description.kpi_sample_type = KpiSampleType.KPISAMPLETYPE_UNKNOWN new_kpi = client.SetKpi(kpi_description) @@ -81,7 +79,6 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto return new_kpi def make_inference(self, request): - # ML MODEL x_data = np.array( [ [ @@ -100,7 +97,8 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto ) predictions = self.model.run([self.prob_name], {self.input_name: x_data.astype(np.float32)})[0] - # Output format + + # 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"), @@ -117,6 +115,7 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto "time_start": request.time_start, "time_end": request.time_end, } + if predictions[0][1] >= classification_threshold: output_message["confidence"] = predictions[0][1] output_message["tag_name"] = "Crypto" @@ -129,17 +128,15 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto return L3AttackmitigatorOutput(**output_message) def SendInput(self, request, context): - # PERFORM INFERENCE WITH SENT INPUTS - logging.debug("") - print("Inferencing ...", flush=True) - - # STORE VALUES + # Store the data sent in the request self.inference_values.append(request) - # MAKE INFERENCE + # Perform inference with the data sent in the request + logging.info("Performing inference...") output = self.make_inference(request) + logging.info("Inference performed correctly") - # Monitoring + # Include monitored KPIs values service_id = request.service_id if self.predicted_class_kpi_id is None: @@ -148,20 +145,21 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto if self.class_probability_kpi_id is None: self.class_probability_kpi_id = self.create_class_prob_kpi(self.monitoring_client, service_id) - # Packet -> DAD -> CAD -> ML -> (2 Instantaneous Value: higher class probability, predicted class) -> Monitoring + # Packet Aggregation Features -> DAD -> CAD -> ML -> (2 Instantaneous Value: higher class probability, predicted class) -> Monitoring # In addition, two counters: # Counter 1: Total number of crypto attack connections # Counter 2: Rate of crypto attack connections with respect to the total number of connections + # Predicted class KPI kpi_class = Kpi() - kpi_class.kpi_id.kpi_id.uuid = str(self.predicted_class_kpi_id) + kpi_class.kpi_id.kpi_id.CopyFrom(self.predicted_class_kpi_id.kpi_id) kpi_class.kpi_value.int32Val = 1 if output.tag_name == "Crypto" else 0 + # Class probability KPI kpi_prob = Kpi() - kpi_prob.kpi_id.kpi_id.uuid = str(self.class_probability_kpi_id) + kpi_prob.kpi_id.kpi_id.CopyFrom(self.class_probability_kpi_id.kpi_id) kpi_prob.kpi_value.floatVal = output.confidence - # timestamp = timestamp_utcnow_to_float() timestamp = Timestamp() timestamp.timestamp = timestamp_utcnow_to_float() @@ -172,29 +170,37 @@ class l3_centralizedattackdetectorServiceServicerImpl(L3Centralizedattackdetecto self.monitoring_client.IncludeKpi(kpi_prob) if output.tag_name == "Crypto": - # SEND INFO TO MITIGATION SERVER + logging.info("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: with grpc.insecure_channel("192.168.165.78:10002") as channel: stub = L3AttackmitigatorStub(channel) - print("Sending to mitigator...", flush=True) + logging.info("Sending the connection information to the Attack Mitigator component...") response = stub.SendOutput(output) - # print("Response received", response, "Hola", flush=True) - # print("Sent output to mitigator and received: ", response.message) #FIX No message received + logging.info( + "Attack Mitigator notified and received response: ", response.message + ) # FIX No message received - # RETURN "OK" TO THE CALLER return Empty(message="OK, information received and mitigator notified abou the attack") except Exception as e: - print("This is an exception", repr(e), flush=True) - print("Couldnt find l3_attackmitigator", flush=True) - return Empty(message="Mitigator Not found") + 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: - print("No attack detected", flush=True) - return Empty(message="OK, information received (no attack detected)") + logging.info("No attack detected") + + return Empty(message="Ok, information received (no attack detected)") def GetOutput(self, request, context): - logging.debug("") - print("Returing inference output...") + logging.info("Returning inference output...") k = np.multiply(self.inference_values, [2]) k = np.sum(k) + return self.make_inference(k) diff --git a/src/monitoring/service/ManagementDBTools.py b/src/monitoring/service/ManagementDBTools.py new file mode 100644 index 0000000000000000000000000000000000000000..63bdb1893499ff560ff03866f31e3619070d3201 --- /dev/null +++ b/src/monitoring/service/ManagementDBTools.py @@ -0,0 +1,154 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# 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. + +import sqlite3 as sl + +class ManagementDB(): + def __init__(self, database): + self.client = sl.connect(database, check_same_thread=False) + self.create_monitoring_table() + self.create_subscription_table() + self.create_alarm_table() + + def create_monitoring_table(self): + self.client.execute(""" + CREATE TABLE IF NOT EXISTS kpi( + kpi_id INTEGER PRIMARY KEY AUTOINCREMENT, + kpi_description TEXT, + kpi_sample_type INTEGER, + device_id INTEGER, + endpoint_id INTEGER, + service_id INTEGER + ); + """) + + def create_subscription_table(self): + self.client.execute(""" + CREATE TABLE IF NOT EXISTS subscription( + subs_id INTEGER PRIMARY KEY AUTOINCREMENT, + kpi_id INTEGER, + subscriber TEXT, + sampling_duration_s REAL, + sampling_interval_s REAL, + start_timestamp REAL, + end_timestamp REAL + ); + """) + + def create_alarm_table(self): + self.client.execute(""" + CREATE TABLE IF NOT EXISTS alarm( + alarm_id INTEGER PRIMARY KEY AUTOINCREMENT, + alarm_description TEXT, + alarm_name TEXT, + kpi_id INTEGER, + kpi_min_value REAL, + kpi_max_value REAL, + in_range INTEGER, + include_min_value INTEGER, + include_max_value INTEGER + ); + """) + + def insert_KPI(self,kpi_description,kpi_sample_type,device_id,endpoint_id,service_id): + c = self.client.cursor() + c.execute("SELECT kpi_id FROM kpi WHERE device_id is ? AND kpi_sample_type is ? AND endpoint_id is ? AND service_id is ?",(device_id,kpi_sample_type,endpoint_id,service_id)) + data=c.fetchone() + if data is None: + c.execute("INSERT INTO kpi (kpi_description,kpi_sample_type,device_id,endpoint_id,service_id) VALUES (?,?,?,?,?)", (kpi_description,kpi_sample_type,device_id,endpoint_id,service_id)) + self.client.commit() + return c.lastrowid + else: + return data[0] + + def insert_subscription(self,kpi_id,subscriber,sampling_duration_s,sampling_interval_s,start_timestamp, end_timestamp): + c = self.client.cursor() + c.execute("SELECT subs_id FROM subscription WHERE kpi_id is ? AND subscriber is ? AND sampling_duration_s is ? AND sampling_interval_s is ? AND start_timestamp is ? AND end_timestamp is ?",(kpi_id,subscriber,sampling_duration_s,sampling_interval_s,start_timestamp, end_timestamp)) + data=c.fetchone() + if data is None: + c.execute("INSERT INTO subscription (kpi_id,subscriber,sampling_duration_s,sampling_interval_s,start_timestamp, end_timestamp) VALUES (?,?,?,?,?,?)", (kpi_id,subscriber,sampling_duration_s,sampling_interval_s,start_timestamp, end_timestamp)) + self.client.commit() + return c.lastrowid + else: + print("already exists") + return data[0] + + def insert_alarm(self,alarm_description,alarm_name,kpi_id,kpi_min_value,kpi_max_value,in_range,include_min_value,include_max_value): + c = self.client.cursor() + c.execute("SELECT alarm_id FROM alarm WHERE alarm_description is ? AND alarm_name is ? AND kpi_id is ? AND kpi_min_value is ? AND kpi_max_value is ? AND in_range is ? AND include_min_value is ? AND include_max_value is ?",(alarm_description,alarm_name,kpi_id,kpi_min_value,kpi_max_value,in_range,include_min_value,include_max_value)) + data=c.fetchone() + if data is None: + c.execute("INSERT INTO alarm (alarm_description, alarm_name, kpi_id, kpi_min_value, kpi_max_value, in_range, include_min_value, include_max_value) VALUES (?,?,?,?,?,?,?,?)", (alarm_description,alarm_name,kpi_id,kpi_min_value,kpi_max_value,in_range,include_min_value,include_max_value)) + self.client.commit() + return c.lastrowid + else: + print("already exists") + return data[0] + + def delete_KPI(self,kpi_id): + c = self.client.cursor() + c.execute("SELECT * FROM kpi WHERE kpi_id is ?",(kpi_id,)) + data=c.fetchone() + if data is None: + return False + else: + c.execute("DELETE FROM kpi WHERE kpi_id is ?",(kpi_id,)) + self.client.commit() + return True + + def delete_subscription(self,subs_id): + c = self.client.cursor() + c.execute("SELECT * FROM subscription WHERE subs_id is ?",(subs_id,)) + data=c.fetchone() + if data is None: + return False + else: + c.execute("DELETE FROM subscription WHERE subs_id is ?",(subs_id,)) + self.client.commit() + return True + + def delete_alarm(self,alarm_id): + c = self.client.cursor() + c.execute("SELECT * FROM alarm WHERE alarm_id is ?",(alarm_id,)) + data=c.fetchone() + if data is None: + return False + else: + c.execute("DELETE FROM alarm WHERE alarm_id is ?",(alarm_id,)) + self.client.commit() + return True + + def get_KPI(self,kpi_id): + data = self.client.execute("SELECT * FROM kpi WHERE kpi_id is ?",(kpi_id,)) + return data.fetchone() + + def get_subscription(self,subs_id): + data = self.client.execute("SELECT * FROM subscription WHERE subs_id is ?",(subs_id,)) + return data.fetchone() + + def get_alarm(self,alarm_id): + data = self.client.execute("SELECT * FROM alarm WHERE alarm_id is ?",(alarm_id,)) + return data.fetchone() + + def get_KPIS(self): + data = self.client.execute("SELECT * FROM kpi") + return data.fetchall() + + def get_subscriptions(self): + data = self.client.execute("SELECT * FROM subscription") + return data.fetchall() + + def get_alarms(self): + data = self.client.execute("SELECT * FROM alarm") + return data.fetchall()