From 7cfc939004a50d29331f0cbcb674f79cd161f658 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?V=C3=A1clav=20Barto=C5=A1?= <bartos@cesnet.cz>
Date: Tue, 8 Mar 2022 11:10:29 +0100
Subject: [PATCH] Added haas2warden connector

---
 cznichaas2warden/README.md            |  14 +++
 cznichaas2warden/haas2warden.cron     |   2 +
 cznichaas2warden/haas2warden.py       | 139 ++++++++++++++++++++++++++
 cznichaas2warden/warden-filer.service |  18 ++++
 cznichaas2warden/warden_filer.cfg     |  21 ++++
 5 files changed, 194 insertions(+)
 create mode 100644 cznichaas2warden/README.md
 create mode 100644 cznichaas2warden/haas2warden.cron
 create mode 100644 cznichaas2warden/haas2warden.py
 create mode 100644 cznichaas2warden/warden-filer.service
 create mode 100644 cznichaas2warden/warden_filer.cfg

diff --git a/cznichaas2warden/README.md b/cznichaas2warden/README.md
new file mode 100644
index 0000000..42972f5
--- /dev/null
+++ b/cznichaas2warden/README.md
@@ -0,0 +1,14 @@
+# haas2warden
+
+Warden connector for data of [CZ.NIC HaaS project](https://haas.nic.cz/).
+
+It downloads daily [HaaS data dumps](https://haas.nic.cz/stats/export/),
+converts them to IDEA messages and sends them to CESNET's Warden server.
+
+It should be run from `cron` every night when data from previous day are
+available (at 3:30).
+
+The script just writes IDEA messages as files into a "filer" directory.
+A _warden_filer_ daemon must be configured to pick up the messages
+and send them to Warden server.
+There is a systemd file which can be used to run the warden_filer.
diff --git a/cznichaas2warden/haas2warden.cron b/cznichaas2warden/haas2warden.cron
new file mode 100644
index 0000000..dd0dbb6
--- /dev/null
+++ b/cznichaas2warden/haas2warden.cron
@@ -0,0 +1,2 @@
+# Run every day at 03:30
+30 03 * * * haas2warden python3 /data/haas2warden/haas2warden.py -p /data/haas2warden/warden_filer/ -n org.example.ext.cznic_haas -t >> /data/haas2warden/haas2warden.log 2>&1 
diff --git a/cznichaas2warden/haas2warden.py b/cznichaas2warden/haas2warden.py
new file mode 100644
index 0000000..130b82f
--- /dev/null
+++ b/cznichaas2warden/haas2warden.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python3
+from gzip import decompress
+from json import loads
+from datetime import datetime, timedelta
+import argparse
+import logging
+import uuid
+import json
+import os
+import requests
+
+
+data_date = datetime.date(datetime.utcnow()) - timedelta(days=1)
+
+LOGFORMAT = "%(asctime)-15s,%(name)s [%(levelname)s] %(message)s"
+LOGDATEFORMAT = "%Y-%m-%dT%H:%M:%S"
+logging.basicConfig(level=logging.INFO, format=LOGFORMAT, datefmt=LOGDATEFORMAT)
+
+logger = logging.getLogger('haas2warden')
+
+def createIDEAFile(idea_id, idea_msg):
+    """
+    Creates file for IDEA message in .../tmp folder, then move it to .../incoming folder
+    """
+    tmp_dir_path = os.path.join(args.path, "tmp")
+    idea_file_path = os.path.join(tmp_dir_path, idea_id+".idea")
+    os.makedirs(tmp_dir_path, exist_ok=True)
+    idea_file = open(idea_file_path, "w")
+    idea_file.write(idea_msg)
+    idea_file.close()
+
+    incoming_dir_path = os.path.join(args.path, "incoming")
+    incoming_file_path = os.path.join(incoming_dir_path,idea_id+".idea")
+    os.makedirs(incoming_dir_path, exist_ok=True) 
+    os.rename(idea_file_path,incoming_file_path)
+
+
+def createIDEA(time, time_closed, ip, login_successful, commands):
+    """
+    Creates IDEA message 
+    """ 
+    idea_id = str(uuid.uuid4())
+
+    if login_successful:
+        category = "[\"Intrusion.UserCompromise\"]" 
+        description = "SSH login on honeypot (HaaS)"
+        if args.test:
+            category = "[\"Intrusion.UserCompromise\", \"Test\"]"
+        attach = f''',
+   "Attach": [
+        {{
+            "Note": "commands",
+            "Type": ["ShellCode"],
+            "ContentType": "application/json",
+            "Content": {json.dumps(commands)}
+        }}
+    ]''' #              ^-- "commands" is already serialiezed into a json string, we want to include it into a bigger JSON so we must encode it again (to escape quotes and any other special charaters)
+
+    else:
+        category = "[\"Attempt.Login\"]" 
+        description = "Unsuccessful SSH login attempt on honeypot (HaaS)"
+        if args.test:
+            category = "[\"Attempt.Login\", \"Test\"]"
+        attach = ""
+
+    if time_closed: # sometimes time_closed is empty, in such case we must omit CeaseTime completely from IDEA msg
+        cease_time = f'"CeaseTime": "{time_closed}",'
+    else:
+        cease_time = ""
+
+    idea_msg = f"""\
+{{
+    "Format": "IDEA0",
+    "ID": "{idea_id}",
+    "Category": {category},
+    "Description": "{description}",
+    "Note": "Extracted from data of CZ.NIC HaaS project",
+    "DetectTime": "{time}",
+    "EventTime": "{time}",
+    {cease_time}
+    "CreateTime": "{datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')}",
+    "Source": [
+        {{
+            "IP4": ["{ip}"],
+            "Proto": ["tcp", "ssh"]
+        }}
+    ],
+    "Node": [
+        {{
+            "Name": "{args.name}",
+            "SW": ["CZ.NIC HaaS"],
+            "Type": ["Connection", "Auth", "Honeypot"],
+            "Note": "A script converting daily HaaS data dumps from https://haas.nic.cz/stats/export/"
+        }}
+    ]{attach}
+}}
+"""
+    createIDEAFile(idea_id, idea_msg)
+
+    
+def processJSON():
+    """
+    Downloads data from https://haas.nic.cz/stats/export/ and process json files.
+    """
+    date = datetime.strptime(args.date, '%Y-%m-%d').date()
+    # get url
+    url = "https://haas.nic.cz/stats/export/{}/{}/{}.json.gz".format(str(date).split('-')[0],str(date).split('-')[1], str(date))
+    # get data 
+    logger.info("Downloading {}".format(url))
+    response = requests.get(url)
+    if response.status_code == 200:
+        # unzip and read json file
+        json_objects = loads(decompress(response.content))
+        logger.info("Found {} records, converting to IDEA messages".format(len(json_objects)))
+        # go through all json objects
+        for json_object in json_objects:
+            createIDEA(json_object["time"], json_object["time_closed"], json_object["ip"], json_object["login_successful"], json.dumps(json_object["commands"]))
+
+if __name__ == "__main__":
+    
+    # parse arguments
+    parser = argparse.ArgumentParser(
+        prog="haas_receiver.py",
+        description="A script converting daily HaaS data dumps from https://haas.nic.cz/stats/export/"
+    )
+
+    parser.add_argument('-d', '--date', metavar='DATE', default = str(data_date),
+                        help='To download data from date YYYY-MM-DD, use date + 1 day (default: utcnow - 1 day)')
+    parser.add_argument('-p', '--path', metavar='DIRPATH', default = "/data/haas2warden/warden_filer/",
+                        help='Target folder for Idea messages (default: "/data/haas2warden/warden_filer/")')
+    parser.add_argument('-n', '--name', metavar='NODENAME', default = "undefined",
+                        help='Name of the node (default: undefined)')
+    parser.add_argument('-t', '--test', action="store_true",
+                        help='Test category')                                        
+
+    args = parser.parse_args()
+    
+    processJSON()
+    logger.info("Done")
diff --git a/cznichaas2warden/warden-filer.service b/cznichaas2warden/warden-filer.service
new file mode 100644
index 0000000..ca81c2a
--- /dev/null
+++ b/cznichaas2warden/warden-filer.service
@@ -0,0 +1,18 @@
+# Template of Systemd unit for Warden filer daemon
+#
+# TODO: set paths, username and mode (receiver/sender) in the last two lines
+# and uncomment them. Then copy the file to:
+#   /etc/systemd/system/warden-filer.service
+# and run:
+#   systemctl daemon-reload
+
+[Unit]
+Description=Warden filer for haas2warden
+After=syslog.target network.target
+
+[Service]
+Type=forking
+User=haas2warden
+PIDFile=/data/haas2warden/warden_filer.pid
+ExecStart=/opt/warden_filer/warden_filer.py --daemon -c "/data/haas2warden/warden_filer.cfg" --pid_file "/data/haas2warden/warden_filer.pid" sender
+
diff --git a/cznichaas2warden/warden_filer.cfg b/cznichaas2warden/warden_filer.cfg
new file mode 100644
index 0000000..21b46cf
--- /dev/null
+++ b/cznichaas2warden/warden_filer.cfg
@@ -0,0 +1,21 @@
+{
+    // Warden config can be also referenced as:
+    // "warden": "/path/to/warden_client.cfg"
+    "warden": {
+        "url": "https://warden-hub.cesnet.cz/warden3",
+        "cafile": "/etc/pki/tls/certs/ca-bundle.crt",
+        "keyfile": "/data/haas2warden/key.pem",
+        "certfile": "/data/haas2warden/cert.pem",
+        "timeout": 10,
+        "errlog": {"level": "warning"},
+        "filelog": {"level": "info", "file": "/data/haas2warden/warden_filer.log"},
+        "idstore": "/data/haas2warden/warden_filer.id",
+        "name": "org.example.cznic_haas"
+    },
+    "sender": {
+        // Maildir like directory, whose "incoming" subdir will be checked
+        // for Idea events to send out
+        "dir": "/data/haas2warden/warden_filer",
+        "poll_time": 60
+    }
+}
-- 
GitLab