From dcedb1ad2ece2986ca1b1e811e53e3bfc4238f5d Mon Sep 17 00:00:00 2001
From: Michal Kostenec <kostenec@civ.zcu.cz>
Date: Fri, 28 Nov 2014 09:47:13 +0100
Subject: [PATCH] SQL features Authorization Authentication

---
 .gitignore                              |   1 +
 warden3/warden_server/warden_server.cfg |   2 +-
 warden3/warden_server/warden_server.py  | 225 ++++++++++++++++++++++--
 3 files changed, 208 insertions(+), 20 deletions(-)
 create mode 100644 .gitignore

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cfaad76
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.pem
diff --git a/warden3/warden_server/warden_server.cfg b/warden3/warden_server/warden_server.cfg
index 6a40f8b..65f7ce7 100644
--- a/warden3/warden_server/warden_server.cfg
+++ b/warden3/warden_server/warden_server.cfg
@@ -8,4 +8,4 @@
         "get_events_limit": 1000,
         "description": "Warden 3 not even alpha development server"
     }
-}
+}
\ No newline at end of file
diff --git a/warden3/warden_server/warden_server.py b/warden3/warden_server/warden_server.py
index 80ce007..b80951c 100755
--- a/warden3/warden_server/warden_server.py
+++ b/warden3/warden_server/warden_server.py
@@ -155,7 +155,7 @@ class NoAuthenticator(Object):
         return "anybody"    # or None
 
 
-    def authorize(self, env, client, method, args):
+    def authorize(self, env, client, method, event, args):
         return (client is not None)
 
 
@@ -192,10 +192,29 @@ class X509Authenticator(NoAuthenticator):
         return self.db.get_client_by_name(names)
 
 
-    def authorize(self, env, client, method, args):
-        return (client is not None) and client["rights"]=="whatever"
-
-
+    def authorize(self, env, client, method, event, args):
+        cl = None
+        service = event['Node'][0]['Name']
+        
+        for i in range(len(client)):
+            if client[i]['service'] == service:
+                cl = client[i]
+                break
+
+        if cl is None:
+            return None
+        
+        # logging.debug(cl)
+        # logging.debug(method)
+        # logging.debug(service)            
+     
+        # return True if (method == 'getEvents' and cl['read'])
+        if ((method == 'sendEvents' and cl['write'] == 1) or
+            (method == 'getDebug' and cl['debug'] == 1) or
+            (method == 'sendEvents' and cl['test'] == 1 and service == 'Test')):
+            return cl
+
+        return None
 
 class NoValidator(Object):
 
@@ -203,7 +222,6 @@ class NoValidator(Object):
         return []
 
 
-
 class JSONSchemaValidator(NoValidator):
 
     def __init__(self, filename=None):
@@ -256,14 +274,15 @@ class MySQL(Object):
 
 
     def get_client_by_name(self, name):
-        return {
-            "name": name[0] if name else None,
-            "rights": "whatever"
-        }
+        format_strings = ','.join(['%s'] * len(name))
+        self.crs.execute("SELECT `id`, `hostname`, `service`, `identity`, `read`, `write`, `debug`, `test` FROM `clients2` WHERE `valid` = 1 AND `hostname` IN (%s)" % format_strings, tuple(name))
+        row = self.crs.fetchall()
+        
+        return row if row else None
 
 
     def get_debug(self):
-        self.crs.execute("SELECT VERSION() AS VER");
+        self.crs.execute("SELECT VERSION() AS VER")
         row = self.crs.fetchone()
         return {
             "db": "MySQL",
@@ -296,16 +315,176 @@ class MySQL(Object):
             cat=None, nocat=None,
             tag=None, notag=None,
             group=None, nogroup=None):
+       
+        sqlwhere = []
+        sqltemp = {}
+
+
+        if cat is not None and nocat is not None:
+            raise Error("Unrealizable conditions. Choose cat or nocat option.", 500, method='getEvents', 
+                        exc=sys.exc_info(), detail={'cat': cat, 'nocat' : nocat})
+
+        if cat is not None or nocat is not None:
+            if cat is not None:
+                sqltemp['cat'] = generateDynamicQuery(self, "Category", "category_id IN (%s)", json.loads(cat))
+            if nocat is not None:
+                sqltemp['cat'] = generateDynamicQuery(self, "Category", "category_id NOT IN (%s)", json.loads(nocat))
+
+            sqlwhere.append("e.id IN (SELECT event_id FROM event_category_mapping4 WHERE %s)" % sqltemp['cat'])
+
+        if tag is not None and notag is not None:
+            raise Error("Unrealizable conditions. Choose tag or notag option.", 500, method='getEvents', 
+                        exc=sys.exc_info(), detail={'tag': cat, 'notag' : nocat})
+
+        if tag is not None or notag is not None:
+            if tag is not None:
+                sqltemp['tag'] = generateDynamicQuery(self, "Tag", "tag_id IN (%s)", json.loads(tag))
+            if notag is not None:
+                sqltemp['tag'] = generateDynamicQuery(self, "Tag", "tag_id NOT IN (%s)", json.loads(notag))
+            
+            sqlwhere.append("e.id IN (SELECT event_id FROM event_tag_mapping4 WHERE %s)" % sqltemp['tag'])
+
+     
+        if group is not None and nogroup is not None:       
+            raise Error("Unrealizable conditions. Choose group or nogroup option.", 500, method='getEvents', 
+                        exc=sys.exc_info(), detail={'tag': cat, 'notag' : nocat})
+
+        if group is not None or nogroup is not None:
+            sqltemp['group'] = ""
+
+            if group is not None:
+                for identity in json.loads(group):
+                    sqltemp['group'] += ("cl.identity LIKE '%s' AND " % (identity)) 
+            if nogroup is not None:
+                for identity in json.loads(nogroup):
+                    sqltemp['group'] += ("cl.identity NOT LIKE '%s' AND " % (identity)) 
+
+            # logging.debug(sqltemp['group'][:-4])
+            sqlwhere.append(sqltemp['group'][:-4])
+
+        sqlwhere_string = (" AND " . join(sqlwhere))
+        # logging.debug(sqlwhere_string)
+        # logging.debug(' AND ' . join(sqlwhere))
+    
+        #sqlwhere = sqlwhere[:-4]
+        and_op = "" if not sqlwhere_string else "AND"
+
+        # logging.debug("SELECT e.id, e.data FROM clients2 cl RIGHT JOIN events4 e ON cl.id = e.client_id WHERE e.id > %s AND %s %s e.valid = 1 LIMIT %s" % (str(id), sqlwhere_string, and_op, str(count)))
+        self.crs.execute("SELECT e.id, e.data FROM clients2 cl RIGHT JOIN events4 e ON cl.id = e.client_id WHERE e.id > %s AND %s %s e.valid = 1 LIMIT %s" % (str(id), sqlwhere_string, and_op, str(count)))
+        row = self.crs.fetchall()
+
         return {
-            "lastid": (id or 0)+count,
-            "events": [self.gen_random_idea() for i in range(count)]
+            "lastid": row[-1]['id'] if row else str(id),
+            "events": [row[i]['data'] for i in range(len(row))]
         }
 
 
     def store_events(self, client, events):
         errs = []   # See sendEvents and validation, should return something similar
+        
+        for event in events:
+            try:
+                # logging.debug("INSERT INTO events5 (detected,received,client_id,data) VALUES ('%s', NOW(), '%s', '%s')" % (event['DetectTime'], client['id'], self.con.escape_string(str(event))))
+                self.crs.execute("INSERT INTO events5 (detected,received,client_id,data) VALUES ('%s', NOW(), '%s', '%s')" % (event['DetectTime'], client['id'], self.con.escape_string(str(event))))
+                lastid = self.crs.lastrowid
+                # logging.debug(str(lastid))
+                for cat in event['Category']:
+                    # logging.debug({'cat': cat})
+                    cat_id = self.map_id('Category', cat) if self.map_id('Category', cat) else self.map_id('Category', 'Other.Other')
+                    # logging.debug({'cat_id': cat_id})
+                    # logging.debug("INSERT INTO event_category_mapping5 (event_id,category_id) VALUES ('%s', '%s')" % (str(lastid), str(cat_id)))
+                    self.crs.execute("INSERT INTO event_category_mapping5 (event_id,category_id) VALUES ('%s', '%s')" % (str(lastid), str(cat_id)))
+                    
+                for tag in event['Node'][0]['Tags']:
+                    tag_id = self.map_id('Tag', tag) if self.map_id('Tag', tag) else self.map_id('Tag', 'Other')
+                    # logging.debug({'tag_id': tag_id})
+                    # logging.debug("INSERT INTO event_tag_mapping5 (event_id,tag_id) VALUES ('%s', '%s')" % (str(lastid), tag_id))
+                    self.crs.execute("INSERT INTO event_tag_mapping5 (event_id,tag_id) VALUES ('%s', '%s')" % (str(lastid), str(tag_id)))    
+
+                self.con.commit()
+            except:
+                self.con.rollback()
+                raise Error("Data storing error", 00, detail={'event': event})
+                errs.append({"event": event})
+    
         return errs
 
+    def map_id (self, section, key):
+        # Should by placed in config file
+        data = {}
+        data['Tag'] = {
+                "Connection" : 1, 
+                "Datagram" : 2, 
+                "Content" : 3, 
+                "Data" : 4, 
+                "File" : 5, 
+                "Flow" : 6, 
+                "Log": 7, 
+                "Protocol" : 8, 
+                "Host" : 9, 
+                "Network" : 10, 
+                "Correlation" : 11, 
+                "External" : 12, 
+                "Reporting" : 13,
+                "Other" : 99
+                }
+
+        data['Category'] = {  
+                        "Abusive.Spam" : 101,
+                        "Abusive.Harassment" : 102,
+                        "Abusive.Child" : 103,
+                        "Abusive.Sexual" : 104,
+                        "Abusive.Violence" : 105,
+                        "Malware.Virus" : 201,
+                        "Malware.Worm" : 202,
+                        "Malware.Trojan" : 203,
+                        "Malware.Spyware" : 204,
+                        "Malware.Dialer" : 205,
+                        "Malware.Rootkit" : 206,
+                        "Recon.Scanning" : 301,
+                        "Recon.Sniffing" : 302,
+                        "Recon.SocialEngineering" : 303,
+                        "Recon.Searching" : 304,
+                        "Attempt.Exploit" : 401,
+                        "Attempt.Login" : 402,
+                        "Attempt.NewSignature" : 403,
+                        "Intrusion.AdminCompromise" : 501,
+                        "Intrusion.UserCompromise" : 502,
+                        "Intrusion.AppCompromise" : 503,
+                        "Intrusion.Botnet" : 504,
+                        "Availability.DoS" : 601,
+                        "Availability.DDoS" : 602,
+                        "Availability.Sabotage" : 603,
+                        "Availability.Outage" : 604,
+                        "Information.UnauthorizedAccess" : 701,
+                        "Information.UnauthorizedModification" : 702,
+                        "Fraud.UnauthorizedUsage" : 801,
+                        "Fraud.Copyright" : 802,
+                        "Fraud.Masquerade" : 803,
+                        "Fraud.Phishing" : 804,
+                        "Fraud.Scam" : 805,
+                        "Vulnerable.Open" : 901,
+                        "Anomaly.Traffic" : 1001,
+                        "Anomaly.Connection" : 1002,
+                        "Anomaly.Protocol" : 1003,
+                        "Anomaly.System" : 1004,        
+                        "Anomaly.Application" : 1005,
+                        "Anomaly.Behaviour" : 1006,
+                        "Other" : 9998,
+                        "Test" : 9999,
+                    }
+
+        try:
+            return data[section][key]
+        except:
+            return data[section]['Other']
+
+def generateDynamicQuery(self, section, query_string, variables):
+    variables_id = [self.map_id(section, v) for v in variables]
+    format_strings = ','.join(['\'%s\''] * len(variables_id))
+    temp_string = query_string % format_strings
+    
+    return temp_string % tuple(variables_id)
 
 
 def expose(meth):
@@ -365,7 +544,7 @@ class Server(Object):
 
             client = self.auth.authenticate(environ)
             if not client:
-                raise Error("I'm watching YOU.", 403, method=path)
+                raise Error("I'm watching YOU. (Authenticate)", 403, method=path)
 
             try:
                 events = json.loads(injson) if injson else None
@@ -380,8 +559,8 @@ class Server(Object):
             if events:
                 args["events"] = events
 
-            if not self.auth.authorize(environ, client, path, args):
-                raise Error("I'm watching YOU.", 403, method=path, detail={"client": client})
+            # if not self.auth.authorize(environ, client, path, args):
+            #     raise Error("I'm watching YOU.", 403, method=path, detail={"client": client})
 
             args = self.sanitize_args(path, method, args)
             result = method(_env=environ, _client=client, **args)   # call requested method
@@ -428,10 +607,11 @@ class Server(Object):
 
 class WardenHandler(Object):
 
-    def __init__(self, validator, db,
+    def __init__(self, validator, db, auth,
             send_events_limit=100000, get_events_limit=100000,
             description=None):
 
+        self.auth = auth
         self.db = db
         self.validator = validator
         self.send_events_limit = send_events_limit
@@ -507,13 +687,17 @@ class WardenHandler(Object):
         okevents = []
         valerrs = []
         for event in events:
+            auth_cl = self.auth.authorize(_env, _client, 'sendEvents', event, None)
+            if not auth_cl:
+                raise Error("I'm watching YOU. (Authorization)", 403, method='sendEvents', detail={"client": _client})
+
             verrs = self.validator.check(event)
             if verrs:
                 valerrs.append({"errors": verrs, "event": event})
             else:
                 okevents.append(event)
 
-        dberrs = self.db.store_events(_client, okevents)
+        dberrs = self.db.store_events(auth_cl, okevents)
 
         if valerrs or dberrs:
             raise Error("Event storage error", 500, method="sendEvents",
@@ -633,12 +817,13 @@ def build_server(conf):
             "host": {"type": str, "default": "localhost"},
             "user": {"type": str, "default": "warden"},
             "password": {"type": str, "default": ""},
-            "dbname": {"type": str, "default": "warden3"},
+            "dbname": {"type": str, "default": "warden3c"},
             "port": {"type": natural, "default": 3306}
         },
         "WardenHandler": {
             "validator": {"type": obj, "default": "validator"},
             "db": {"type": obj, "default": "DB"},
+            "auth": {"type": obj, "default": "auth"},
             "send_events_limit": {"type": natural, "default": 10000},
             "get_events_limit": {"type": natural, "default": 10000},
             "description": {"type": str, "default": ""}
@@ -654,6 +839,8 @@ def build_server(conf):
         sect_name = sect_name.lower()
         sect_def = section_def[sect_name]
 
+        #logging.debug("Testing %s" % sect_name)
+
         try:    # Object type defined?
             objtype = config["type"]
             del config["type"]
-- 
GitLab