From a59ba5ede35f494b89dca2ce7e6afc42444cf533 Mon Sep 17 00:00:00 2001
From: Basile Desloges <basile.desloges@cea.fr>
Date: Wed, 2 Feb 2022 15:39:14 +0100
Subject: [PATCH] [eacsl] Add wrapper script to POST E-ACSL alerts to a web API

---
 .../e-acsl/examples/ensuresec/.gitignore      |   2 +
 .../ensuresec/push-alerts/.env-example        |   3 +
 .../ensuresec/push-alerts/receiver.py         |  36 ++++++
 .../examples/ensuresec/push-alerts/wrapper.py | 111 ++++++++++++++++++
 4 files changed, 152 insertions(+)
 create mode 100644 src/plugins/e-acsl/examples/ensuresec/push-alerts/.env-example
 create mode 100644 src/plugins/e-acsl/examples/ensuresec/push-alerts/receiver.py
 create mode 100755 src/plugins/e-acsl/examples/ensuresec/push-alerts/wrapper.py

diff --git a/src/plugins/e-acsl/examples/ensuresec/.gitignore b/src/plugins/e-acsl/examples/ensuresec/.gitignore
index 567609b1234..624a0d90452 100644
--- a/src/plugins/e-acsl/examples/ensuresec/.gitignore
+++ b/src/plugins/e-acsl/examples/ensuresec/.gitignore
@@ -1 +1,3 @@
 build/
+__pycache__/
+.env
\ No newline at end of file
diff --git a/src/plugins/e-acsl/examples/ensuresec/push-alerts/.env-example b/src/plugins/e-acsl/examples/ensuresec/push-alerts/.env-example
new file mode 100644
index 00000000000..8c1f90d4050
--- /dev/null
+++ b/src/plugins/e-acsl/examples/ensuresec/push-alerts/.env-example
@@ -0,0 +1,3 @@
+ENSURESEC_ALERT_URL="http://localhost:5000/alert"
+ENSURESEC_EE_ID="EnsuresecEE"
+ENSURESEC_EE_TOOL_ID="EnsuresecEE_EACSL"
diff --git a/src/plugins/e-acsl/examples/ensuresec/push-alerts/receiver.py b/src/plugins/e-acsl/examples/ensuresec/push-alerts/receiver.py
new file mode 100644
index 00000000000..78ccf80c648
--- /dev/null
+++ b/src/plugins/e-acsl/examples/ensuresec/push-alerts/receiver.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+# Test server that listen on URL /alert for JSON emitted by a program monitored
+# by E-ACSL and print received alerts to the console output.
+# Run with `FLASK_APP=receiver.py FLASK_ENV=development flask run`
+
+from flask import Flask, request
+
+app = Flask(__name__)
+
+
+@app.post("/alert")
+def add_alert():
+    if request.is_json:
+        alert = request.get_json()
+        content = alert['alert']['data'][0]['jsonContent']
+        print(
+"""{file}: In function '{fct}'
+{file}:{line}: {kind} {verdict}:
+\t{name}{post_name}{predicate}"""
+              .format(
+                  file=content['location']['file'],
+                  fct=content['location']['function'],
+                  line=content['location']['line'],
+                  kind=content['kind'],
+                  verdict=content['verdict'],
+                  name=content['name'],
+                  post_name=":\n\t" if content['name'] else "",
+                  predicate=content['predicate']
+              ))
+        if content['values']:
+            print("\tWith values:")
+            for value in content['values']:
+                print("\t- {}: {}".format(value['name'], value['value']))
+        return {}, 201
+    return {"error": "Request must be JSON."}, 415
diff --git a/src/plugins/e-acsl/examples/ensuresec/push-alerts/wrapper.py b/src/plugins/e-acsl/examples/ensuresec/push-alerts/wrapper.py
new file mode 100755
index 00000000000..9116944eca0
--- /dev/null
+++ b/src/plugins/e-acsl/examples/ensuresec/push-alerts/wrapper.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Wrapper for an E-ACSL monitored executable that will POST each raised alert
+# to a specific URL.
+
+import argparse
+import dotenv
+import ijson
+import os
+import requests
+import subprocess
+
+ALERT_URL = 'ENSURESEC_ALERT_URL'
+EE_ID = 'ENSURESEC_EE_ID'
+EE_TOOL_ID = 'ENSURESEC_EE_TOOL_ID'
+
+
+def check_config(value, name):
+    """Check if the given value is empty and exit with an error message if that
+    is the case."""
+    if not value:
+        print("The '{}' option should be provided and not empty.".format(name))
+        exit(1)
+
+
+@ijson.coroutine
+def send_alert(url):
+    """send_alert() is a coroutine that will process E-ACSL alerts as they are
+    emitted by the program. The JSON alerts are POST-ed to the given URL."""
+    while True:
+        alert = (yield)
+        r = requests.post(url, json=alert)
+        if not r.ok:
+            print("Error while sending alert: " + r.text)
+
+
+def main():
+    default = {
+        # Default empty values
+        ALERT_URL: '',
+        EE_ID: '',
+        EE_TOOL_ID: '',
+        # Read configuration from .env file if it exists
+        **dotenv.dotenv_values(".env"),
+        # Overwrite with environment variables
+        **os.environ
+    }
+
+    # Parse script arguments
+    parser = argparse.ArgumentParser(
+        description="Wrapper for an E-ACSL monitored executable that will POST \
+                     each raised alert to a specific URL.")
+    parser.add_argument(
+        'program',
+        help="E-ACSL program to launch and monitor.")
+    parser.add_argument(
+        'args',
+        help="Arguments for the E-ACSL program.",
+        nargs='*')
+    parser.add_argument(
+        '-u', '--alert-url',
+        help="URL where alerts raised by the E-ACSL program must be pushed, \
+              default to environment variable {}.".format(ALERT_URL),
+        metavar='url',
+        default=default[ALERT_URL],
+        dest='alert_url')
+    parser.add_argument(
+        '-i', '--id',
+        help="Ensuresec e-commerce ecosystem id, default to environment \
+              variable {}.".format(EE_ID),
+        metavar='id',
+        default=default[EE_ID],
+        dest='id')
+    parser.add_argument(
+        '-t', '--tool-id',
+        help="Ensuresec e-commerce ecosystem tool id, default to environment \
+              variable {}.".format(EE_TOOL_ID),
+        metavar="id",
+        default=default[EE_TOOL_ID],
+        dest='tool_id')
+    config = parser.parse_args()
+
+    # Check that all necessary configurations have been provided
+    check_config(config.alert_url, 'alert-url')
+    check_config(config.id, 'id')
+    check_config(config.tool_id, 'tool-id')
+
+    # Create an environment for the subprocess with the information either
+    # retrieved from the configuration or the script arguments, and setup
+    # JSON output to stdout
+    env = {'ENSURESEC_EE_ID': config.id,
+           'ENSURESEC_EE_TOOL_ID': config.tool_id,
+           'ENSURESEC_OUTPUT_FILE': '-'}
+    # Launch E-ACSL program as a subprocess
+    p = subprocess.Popen([config.program] + config.args, stdout=subprocess.PIPE,
+                         env=env)
+
+    # Parse output of E-ACSL program as it is produced and use a coroutine to
+    # process JSON objects
+    coro = ijson.items_coro(send_alert(config.alert_url), 'item')
+    for chunk in p.stdout:
+        coro.send(chunk)
+    coro.close()
+
+    # Wait for the end of E-ACSL program
+    p.wait()
+
+
+if __name__ == "__main__":
+    main()
-- 
GitLab