diff --git a/warden3/warden_client/warden_client.py b/warden3/warden_client/warden_client.py index e2ed6bb80972f5a57335fba4971d54c8040d0605..25e9e73a13680f3bad100d7778edae6cb6f37f45 100644 --- a/warden3/warden_client/warden_client.py +++ b/warden3/warden_client/warden_client.py @@ -211,7 +211,7 @@ class Client(object): Error("Unable to setup syslog logging", self.logger, exc=exc_info()) if not (errlog or filelog or syslog): - # User wants explicitly no logging, so let him shoot its socks off. + # User wants explicitly no logging, so let him shoot his socks off. # This silences complaining of logging module about no suitable # handler. self.logger.addHandler(logging.NullHandler()) @@ -252,6 +252,8 @@ class Client(object): def sendRequest(self, func="", payload=None, **kwargs): + kwargs["client"] = self.name + if kwargs: for k in kwargs.keys(): if kwargs[k] is None: diff --git a/warden3/warden_client/warden_curl_test.sh b/warden3/warden_client/warden_curl_test.sh index 95af2e514ca1f58ac8f206dab761d8fe13c06185..bab94acacd3b05bc47f8a584b25b56ba51034594 100755 --- a/warden3/warden_client/warden_curl_test.sh +++ b/warden3/warden_client/warden_curl_test.sh @@ -6,7 +6,8 @@ keyfile='key.pem' certfile='cert.pem' cafile='tcs-ca-bundle.pem' -url='https://warden.example.com/warden3' +url="$1" +client="$2" # --fail \ # --show-error \ @@ -19,7 +20,7 @@ curl \ --cacert $cafile \ --connect-timeout 3 \ --request POST \ - $url/blefub + "$url/blefub?client=$2" echo echo "Test 404" @@ -29,7 +30,27 @@ curl \ --cacert $cafile \ --connect-timeout 3 \ --request POST \ - $url/ + "$url/?client=$2" +echo + +echo "Test 403 - no client" +curl \ + --key $keyfile \ + --cert $certfile \ + --cacert $cafile \ + --connect-timeout 3 \ + --request POST \ + "$url/getEvents" +echo + +echo "Test 403 - wrong client" +curl \ + --key $keyfile \ + --cert $certfile \ + --cacert $cafile \ + --connect-timeout 3 \ + --request POST \ + "$url/getEvents?client=asdf.blefub" echo echo "Test Deserialization" @@ -40,7 +61,7 @@ curl \ --connect-timeout 3 \ --request POST \ --data '{#$%^' \ - $url/getEvents + "$url/getEvents?client=$2" echo echo "Test Called with unknown category" @@ -50,7 +71,7 @@ curl \ --cacert $cafile \ --connect-timeout 3 \ --request POST \ - $url/getEvents?cat=bflm + "$url/getEvents?client=$2&cat=bflm" echo echo "Test Called with both cat and nocat" @@ -60,7 +81,7 @@ curl \ --cacert $cafile \ --connect-timeout 3 \ --request POST \ - "$url/getEvents?cat=Other&nocat=Test" + "$url/getEvents?client=$2&cat=Other&nocat=Test" echo echo "Test Invalid data for getEvents - silently discarded" @@ -71,7 +92,7 @@ curl \ --connect-timeout 3 \ --request POST \ --data '[1]' \ - $url/getEvents + "$url/getEvents?client=$2" echo echo "Test Called with internal args - just in log" @@ -81,7 +102,7 @@ curl \ --cacert $cafile \ --connect-timeout 3 \ --request POST \ - $url/getEvents?self=test + "$url/getEvents?client=$2&self=test" echo echo "Test Called with superfluous args - just in log" @@ -91,7 +112,7 @@ curl \ --cacert $cafile \ --connect-timeout 3 \ --request POST \ - $url/getEvents?bad=guy + "$url/getEvents?client=$2&bad=guy" echo echo "Test getEvents with no args - should be OK" @@ -101,7 +122,7 @@ curl \ --cacert $cafile \ --connect-timeout 3 \ --request POST \ - $url/getEvents + "$url/getEvents?client=$2" echo echo "Test getEvents - should be OK" @@ -111,7 +132,7 @@ curl \ --cacert $cafile \ --connect-timeout 3 \ --request POST \ - "$url/getEvents?count=3&id=10" + "$url/getEvents?client=$2&count=3&id=10" echo echo "Test getDebug" @@ -121,7 +142,7 @@ curl \ --cacert $cafile \ --connect-timeout 3 \ --request POST \ - $url/getDebug + "$url/getDebug?client=$2" echo echo "Test getInfo" @@ -131,7 +152,7 @@ curl \ --cacert $cafile \ --connect-timeout 3 \ --request POST \ - $url/getInfo + "$url/getInfo?client=$2" echo #curl \ diff --git a/warden3/warden_server/warden3.0-alpha.sql b/warden3/warden_server/warden3.0-alpha.sql index 14ed3ea7fbcf0828d65c76566392147e0206d7d4..0f66f01553aef3c97c64c076341ff37d9a84455c 100644 --- a/warden3/warden_server/warden3.0-alpha.sql +++ b/warden3/warden_server/warden3.0-alpha.sql @@ -32,7 +32,7 @@ CREATE TABLE IF NOT EXISTS `categories` ( `subcategory` varchar(64) DEFAULT NULL, `cat_subcat` varchar(129) NOT NULL, KEY `cat_sub` (`cat_subcat`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; -- -- Dumping data for table `categories` @@ -90,37 +90,56 @@ INSERT INTO `categories` (`id`, `category`, `subcategory`, `cat_subcat`) VALUES CREATE TABLE IF NOT EXISTS `clients` ( `id` int(11) NOT NULL AUTO_INCREMENT, - `hostname` varchar(256) NOT NULL, `registered` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', `requestor` varchar(256) NOT NULL, + `hostname` varchar(256) NOT NULL, + `service` varchar(256) NOT NULL, `note` text NOT NULL, `valid` tinyint(1) NOT NULL DEFAULT '1', - `read` tinyint(1) NOT NULL DEFAULT '0', + `identity` varchar(64) NOT NULL, + `read` tinyint(1) NOT NULL DEFAULT '1', `debug` tinyint(1) NOT NULL DEFAULT '0', + `write` tinyint(1) NOT NULL DEFAULT '0', + `test` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=29 ; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci AUTO_INCREMENT=31 ; -- -- Dumping data for table `clients` -- -INSERT INTO `clients` (`id`, `hostname`, `registered`, `requestor`, `note`, `valid`, `read`, `debug`) VALUES -(1, 'afrodita.civ.zcu.cz', '0000-00-00 00:00:00', '', '', 1, 1, 1), -(3, 'au1.cesnet.cz', '0000-00-00 00:00:00', '', '', 1, 1, 0), -(12, 'au2.cesnet.cz', '0000-00-00 00:00:00', '', '', 1, 1, 0), -(13, 'bee.net.vutbr.cz', '0000-00-00 00:00:00', '', '', 1, 1, 0), -(14, 'buldog.vsb.cz', '0000-00-00 00:00:00', '', '', 1, 1, 0), -(15, 'collector-nemea.liberouter.org', '0000-00-00 00:00:00', '', '', 1, 1, 0), -(17, 'collector.liberouter.org', '0000-00-00 00:00:00', '', '', 1, 1, 0), -(18, 'holly.cesnet.cz', '0000-00-00 00:00:00', '', '', 1, 1, 0), -(19, 'kryten.cesnet.cz', '0000-00-00 00:00:00', '', '', 1, 1, 0), -(20, 'mentat.cesnet.cz', '0000-00-00 00:00:00', '', '', 1, 1, 0), -(21, 'miel.opf.slu.cz', '0000-00-00 00:00:00', '', '', 1, 1, 0), -(23, 'nfsen.ics.muni.cz', '0000-00-00 00:00:00', '', '', 1, 1, 0), -(25, 'vinovago.cesnet.cz', '0000-00-00 00:00:00', '', '', 1, 1, 0), -(26, 'ward.tul.cz', '0000-00-00 00:00:00', '', '', 1, 1, 0), -(28, 'kostik.zcu.cz', '0000-00-00 00:00:00', 'kostenec@civ.zcu.cz', '', 1, 1, 1), -(29, 'grey.cesnet.cz', '2014-12-11 13:51:14', 'ph@cesnet.cz', '', 1, 1, 1); +INSERT INTO `clients` (`id`, `registered`, `requestor`, `hostname`, `service`, `note`, `valid`, `identity`, `read`, `debug`, `write`, `test`) VALUES +(1, '0000-00-00 00:00:00', '', 'afrodita.civ.zcu.cz', 'hihat', '', 1, 'cz.zcu.civ.afrodita.hihat', 1, 0, 1, 0), +(2, '0000-00-00 00:00:00', '', 'afrodita.civ.zcu.cz', 'labrea', '', 1, 'cz.zcu.civ.afrodita.labrea', 1, 0, 1, 0), +(3, '0000-00-00 00:00:00', '', 'au1.cesnet.cz', 'cesnet_ids', '', 1, 'cz.cesnet.au1.cesnet_ids', 1, 0, 1, 0), +(4, '0000-00-00 00:00:00', '', 'au1.cesnet.cz', 'cesnet_sserv', '', 1, 'cz.cesnet.au1.cesnet_sserv', 1, 0, 1, 0), +(5, '0000-00-00 00:00:00', '', 'au1.cesnet.cz', 'n6d-dorkbot', '', 1, 'cz.cesnet.au1.n6d-dorkbot', 1, 0, 1, 0), +(6, '0000-00-00 00:00:00', '', 'au1.cesnet.cz', 'n6e-certplsinkhole', '', 1, 'cz.cesnet.au1.n6e-certplsinkhole', 1, 0, 1, 0), +(7, '0000-00-00 00:00:00', '', 'au1.cesnet.cz', 'n6i-citadelsinkhole', '', 1, 'cz.cesnet.au1.n6i-citadelsinkhole', 1, 0, 1, 0), +(8, '0000-00-00 00:00:00', '', 'au1.cesnet.cz', 'n6i-citadelsinkholeqd', '', 1, 'cz.cesnet.au1.n6i-citadelsinkholeqd', 1, 0, 1, 0), +(9, '0000-00-00 00:00:00', '', 'au1.cesnet.cz', 'n6n-openntp', '', 1, 'cz.cesnet.au1.n6n-openntp', 1, 0, 1, 0), +(10, '0000-00-00 00:00:00', '', 'au1.cesnet.cz', 'n6o-botszeroaccess', '', 1, 'cz.cesnet.au1.n6o-botszeroaccess', 1, 0, 1, 0), +(11, '0000-00-00 00:00:00', '', 'au1.cesnet.cz', 'report_n6v-virut', '', 1, 'cz.cesnet.au1.report_n6v-virut', 1, 0, 1, 0), +(12, '0000-00-00 00:00:00', '', 'au2.cesnet.cz', 'ids-cz', '', 1, 'cz.cesnet.au2.ids-cz', 1, 0, 1, 0), +(13, '0000-00-00 00:00:00', '', 'bee.net.vutbr.cz', 'hpscan', '', 1, 'cz.vutbr.net.bee.hpscan', 1, 0, 1, 0), +(14, '0000-00-00 00:00:00', '', 'buldog.vsb.cz', 'kippo', '', 1, 'cz.vsb.buldog.kippo', 1, 0, 1, 0), +(15, '0000-00-00 00:00:00', '', 'collector-nemea.liberouter.org', 'nemea', '', 1, 'org.liberouter.collector-nemea.nemea', 1, 0, 1, 0), +(16, '0000-00-00 00:00:00', '', 'collector.liberouter.org', 'hoststats', '', 1, 'org.liberouter.collector.hoststats', 1, 0, 1, 0), +(17, '0000-00-00 00:00:00', '', 'collector.liberouter.org', 'synscandetector_1_0', '', 1, 'org.liberouter.collector.synscandetector_1_0', 1, 0, 1, 0), +(18, '0000-00-00 00:00:00', '', 'holly.cesnet.cz', 'kippohoneypot', '', 1, 'cz.cesnet.holly.kippohoneypot', 1, 0, 1, 0), +(19, '0000-00-00 00:00:00', '', 'kryten.cesnet.cz', 'dionaeahoneypot', '', 1, 'cz.cesnet.kryten.dionaeahoneypot', 1, 0, 1, 0), +(20, '0000-00-00 00:00:00', '', 'mentat.cesnet.cz', 'mentat', '', 1, 'cz.cesnet.mentat.mentat', 1, 0, 1, 0), +(21, '0000-00-00 00:00:00', '', 'miel.opf.slu.cz', 'kippo', '', 1, 'cz.slu.opf.miel.kippo', 1, 0, 1, 0), +(22, '0000-00-00 00:00:00', '', 'nfsen.ics.muni.cz', 'honeyscan', '', 1, 'cz.muni.ics.nfsen.honeyscan', 1, 0, 1, 0), +(23, '0000-00-00 00:00:00', '', 'nfsen.ics.muni.cz', 'scandetector_1_0', '', 1, 'cz.muni.ics.nfsen.scandetector_1_0', 1, 0, 1, 0), +(24, '0000-00-00 00:00:00', '', 'nfsen.ics.muni.cz', 'sshbruteforce-1_n', '', 1, 'cz.muni.ics.nfsen.sshbruteforce-1_n', 1, 0, 1, 0), +(25, '0000-00-00 00:00:00', '', 'vinovago.cesnet.cz', 'fail2ban', '', 1, 'cz.cesnet.vinovago.fail2ban', 1, 0, 1, 0), +(26, '0000-00-00 00:00:00', '', 'ward.tul.cz', 'dionaeatul', '', 1, 'cz.tul.ward.dionaeatul', 1, 0, 1, 0), +(27, '0000-00-00 00:00:00', '', 'ward.tul.cz', 'kippo', '', 1, 'cz.tul.ward.kippo', 1, 0, 1, 0), +(28, '0000-00-00 00:00:00', 'kostenec@civ.zcu.cz', 'kostik.zcu.cz', 'com.example.test-node', '', 1, 'com.example.test-node', 1, 0, 0, 0), +(29, '0000-00-00 00:00:00', 'kostenec@civ.zcu.cz', 'kostik.zcu.cz', 'com.example.test-node2', '', 1, 'com.example.test-node2', 1, 0, 0, 0), +(30, '0000-00-00 00:00:00', 'kostenec@civ.zcu.cz', 'kostik.zcu.cz', 'Test', '', 1, 'com.example.test-node3', 1, 0, 0, 0), +(31, '2014-12-11 13:51:18', 'ph@cesnet.cz', 'grey.cesnet.cz', 'Test', '', 1, 'cz.cesnet.grey.test', 1, 1, 1, 0); -- -------------------------------------------------------- @@ -131,13 +150,12 @@ INSERT INTO `clients` (`id`, `hostname`, `registered`, `requestor`, `note`, `val CREATE TABLE IF NOT EXISTS `events` ( `id` int(11) NOT NULL AUTO_INCREMENT, `received` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - `service_id` int(11) NOT NULL, + `client_id` int(11) NOT NULL, `data` text NOT NULL, `valid` tinyint(1) NOT NULL DEFAULT '1', PRIMARY KEY (`id`), - KEY `detected` (`detected`), - KEY `id` (`id`,`service_id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; + KEY `id` (`id`,`client_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci AUTO_INCREMENT=1 ; -- -------------------------------------------------------- @@ -149,7 +167,7 @@ CREATE TABLE IF NOT EXISTS `event_category_mapping` ( `event_id` int(11) NOT NULL, `category_id` int(11) NOT NULL, KEY `event_id_2` (`event_id`,`category_id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; -- -------------------------------------------------------- @@ -161,7 +179,7 @@ CREATE TABLE IF NOT EXISTS `event_tag_mapping` ( `event_id` int(11) NOT NULL, `tag_id` int(11) NOT NULL, KEY `event_id_2` (`event_id`,`tag_id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; -- -------------------------------------------------------- @@ -176,64 +194,7 @@ CREATE TABLE IF NOT EXISTS `last_events` ( `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `client_id` (`client_id`,`event_id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; - --- -------------------------------------------------------- - --- --- Table structure for table `services` --- - -CREATE TABLE IF NOT EXISTS `services` ( - `service_id` int(11) NOT NULL AUTO_INCREMENT, - `client_id` int(11) NOT NULL, - `registered` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', - `requestor` varchar(256) NOT NULL, - `service` varchar(256) NOT NULL, - `note` text NOT NULL, - `valid` tinyint(1) NOT NULL DEFAULT '1', - `identity` varchar(64) NOT NULL, - `write` tinyint(1) NOT NULL DEFAULT '0', - `test` int(11) NOT NULL DEFAULT '0', - PRIMARY KEY (`service_id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=31 ; - --- --- Dumping data for table `services` --- - -INSERT INTO `services` (`service_id`, `client_id`, `registered`, `requestor`, `service`, `note`, `valid`, `identity`, `write`, `test`) VALUES -(1, 1, '0000-00-00 00:00:00', '', 'hihat', '', 1, 'cz.zcu.civ.afrodita.hihat', 1, 0), -(2, 1, '0000-00-00 00:00:00', '', 'labrea', '', 1, 'cz.zcu.civ.afrodita.labrea', 1, 0), -(3, 3, '0000-00-00 00:00:00', '', 'cesnet_ids', '', 1, 'cz.cesnet.au1.cesnet_ids', 1, 0), -(4, 3, '0000-00-00 00:00:00', '', 'cesnet_sserv', '', 1, 'cz.cesnet.au1.cesnet_sserv', 1, 0), -(5, 3, '0000-00-00 00:00:00', '', 'n6d-dorkbot', '', 1, 'cz.cesnet.au1.n6d-dorkbot', 1, 0), -(6, 3, '0000-00-00 00:00:00', '', 'n6e-certplsinkhole', '', 1, 'cz.cesnet.au1.n6e-certplsinkhole', 1, 0), -(7, 3, '0000-00-00 00:00:00', '', 'n6i-citadelsinkhole', '', 1, 'cz.cesnet.au1.n6i-citadelsinkhole', 1, 0), -(8, 3, '0000-00-00 00:00:00', '', 'n6i-citadelsinkholeqd', '', 1, 'cz.cesnet.au1.n6i-citadelsinkholeqd', 1, 0), -(9, 3, '0000-00-00 00:00:00', '', 'n6n-openntp', '', 1, 'cz.cesnet.au1.n6n-openntp', 1, 0), -(10, 3, '0000-00-00 00:00:00', '', 'n6o-botszeroaccess', '', 1, 'cz.cesnet.au1.n6o-botszeroaccess', 1, 0), -(11, 3, '0000-00-00 00:00:00', '', 'report_n6v-virut', '', 1, 'cz.cesnet.au1.report_n6v-virut', 1, 0), -(12, 12, '0000-00-00 00:00:00', '', 'ids-cz', '', 1, 'cz.cesnet.au2.ids-cz', 1, 0), -(13, 13, '0000-00-00 00:00:00', '', 'hpscan', '', 1, 'cz.vutbr.net.bee.hpscan', 1, 0), -(14, 14, '0000-00-00 00:00:00', '', 'kippo', '', 1, 'cz.vsb.buldog.kippo', 1, 0), -(15, 15, '0000-00-00 00:00:00', '', 'nemea', '', 1, 'org.liberouter.collector-nemea.nemea', 1, 0), -(16, 16, '0000-00-00 00:00:00', '', 'hoststats', '', 1, 'org.liberouter.collector.hoststats', 1, 0), -(17, 17, '0000-00-00 00:00:00', '', 'synscandetector_1_0', '', 1, 'org.liberouter.collector.synscandetector_1_0', 1, 0), -(18, 18, '0000-00-00 00:00:00', '', 'kippohoneypot', '', 1, 'cz.cesnet.holly.kippohoneypot', 1, 0), -(19, 19, '0000-00-00 00:00:00', '', 'dionaeahoneypot', '', 1, 'cz.cesnet.kryten.dionaeahoneypot', 1, 0), -(20, 20, '0000-00-00 00:00:00', '', 'mentat', '', 1, 'cz.cesnet.mentat.mentat', 1, 0), -(21, 21, '0000-00-00 00:00:00', '', 'kippo', '', 1, 'cz.slu.opf.miel.kippo', 1, 0), -(22, 23, '0000-00-00 00:00:00', '', 'honeyscan', '', 1, 'cz.muni.ics.nfsen.honeyscan', 1, 0), -(23, 23, '0000-00-00 00:00:00', '', 'scandetector_1_0', '', 1, 'cz.muni.ics.nfsen.scandetector_1_0', 1, 0), -(24, 23, '0000-00-00 00:00:00', '', 'sshbruteforce-1_n', '', 1, 'cz.muni.ics.nfsen.sshbruteforce-1_n', 1, 0), -(25, 25, '0000-00-00 00:00:00', '', 'fail2ban', '', 1, 'cz.cesnet.vinovago.fail2ban', 1, 0), -(26, 26, '0000-00-00 00:00:00', '', 'dionaeatul', '', 1, 'cz.tul.ward.dionaeatul', 1, 0), -(27, 26, '0000-00-00 00:00:00', '', 'kippo', '', 1, 'cz.tul.ward.kippo', 1, 0), -(28, 28, '0000-00-00 00:00:00', 'kostenec@civ.zcu.cz', 'com.example.test-node', '', 1, 'com.example.test-node', 0, 0), -(29, 28, '0000-00-00 00:00:00', 'kostenec@civ.zcu.cz', 'com.example.test-node2', '', 1, 'com.example.test-node2', 0, 0), -(30, 28, '0000-00-00 00:00:00', 'kostenec@civ.zcu.cz', 'Test', '', 1, 'com.example.test-node3', 0, 0), -(31, 29, '2014-12-11 13:51:18', 'ph@cesnet.cz', 'Test', '', 1, 'cz.cesnet.grey.test', 1, 0); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci AUTO_INCREMENT=1 ; -- -------------------------------------------------------- @@ -246,7 +207,7 @@ CREATE TABLE IF NOT EXISTS `tags` ( `tag` varchar(64) NOT NULL, KEY `id_tag_name` (`id`,`tag`), KEY `tag_name` (`tag`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; -- -- Dumping data for table `tags` diff --git a/warden3/warden_server/warden_server.py b/warden3/warden_server/warden_server.py index 453199a8c20f8140462090ae0b8a3b7a9dd3fb47..b7e214dbe6d96e4cb7cb66abd2987bcad6ac80f6 100755 --- a/warden3/warden_server/warden_server.py +++ b/warden3/warden_server/warden_server.py @@ -14,6 +14,7 @@ import M2Crypto.X509 import json import MySQLdb as my import MySQLdb.cursors as mycursors +from collections import namedtuple from uuid import uuid4 from time import time, gmtime from math import trunc @@ -57,7 +58,7 @@ class Error(Exception): def info_str(self): - return ("Detail: %s" % self.detail) or "" + return ("Detail: %s" % self.detail) if self.detail else "" def debug_str(self): @@ -173,6 +174,13 @@ class Object(object): +# Simple container class definition shortcut +Client = namedtuple("Client", + ["id", "registered", "requestor", "hostname", "service", "note", + "identity", "read", "debug", "write", "test"]) + + + class Request(Object): """ Simple container for info about ongoing request. One instance gets created before server startup, and all other @@ -200,9 +208,9 @@ class Request(Object): def reset(self, env=None, client=None, path=None, req_id=None): - self.env = {} if env is None else env - self.client = {} if client is None else client - self.path = "" if path is None else path + self.env = env + self.client = client + self.path = path or "" if req_id is not None: self.req_id = req_id else: @@ -214,7 +222,6 @@ class Request(Object): - class ObjectReq(Object): def __init__(self, req): @@ -226,17 +233,18 @@ class ObjectReq(Object): return "%s(req=%s)" % (type(self).__name__, type(self.req).__name__) + class NoAuthenticator(ObjectReq): def __init__(self, req): ObjectReq.__init__(self, req) - def authenticate (self, env): + def authenticate (self, env, args): return "anybody" # or None - def authorize(self, env, client, path, event): + def authorize(self, env, client, path, method): return (client is not None) @@ -268,51 +276,46 @@ class X509Authenticator(NoAuthenticator): return [firstcommon] + list(set(altnames+commons) - set([firstcommon])) - def authenticate (self, env): - names = self.get_cert_dns_names(env["SSL_CLIENT_CERT"]) - return self.db.get_client_by_name(names) - - - def authorize(self, env, client, path, event): - # Authorize for debug - if (path == 'getDebug'): - if not client["debug"]: - logging.info("Auth failed: client does not have debug enabled") - return None - return client - - if path in ['getInfo', 'getEvents']: - return client - + def authenticate (self, env, args): try: - identity = event['Node'][0]['Name'].lower() - except (KeyError, TypeError): - # Event does not bear valid Node attribute - logging.info("Auth failed: event does not bear valid Node attribute") + identity = args["client"][0] + except KeyError: + logging.info("authenticate: bad or missing client argument") return None try: - service = client["services"][identity] - except KeyError: - # We are unable to pair service in message to service in db - logging.info("authorize: failed, '%s' from event not found in services for client %i" % (identity, client["id"])) + cert_names = self.get_cert_dns_names(env["SSL_CLIENT_CERT"]) + except: + logging.info("authenticate: cannot get or parse certificate from env") return None + + client = self.db.get_client_by_name(identity, cert_names) - client["service"] = service + if not client: + logging.info("authenticate: client not found") - # Authorize for sending events - if (path == "sendEvents"): - if not (service["write"] or service["test"]): - logging.info("authorize: failed, service %i (%s) is not allowed to write or test" % (service["service_id"], identity)) + logging.info("authenticate: ok, client=%s" % str(client)) + return client + + + def authorize(self, env, client, path, method): + if method.debug: + if not client.debug: + logging.info("authorize: failed, client does not have debug enabled") return None + return client - test = 'Test' in event.get('Category', []) - # if not test: - # logging.info("authorize: failed, service %i (%s) does not send Test category in event" % (service["service_id"], identity)) - if test and not service['test']: - logging.info("authorize: failed, service %i (%s) is not allowed to send Test category in event" % (service["service_id"], identity)) + if method.read: + if not client.read: + logging.info("authorize: failed, client does not have read enabled") return None - + return client + + if method.write: + if not (client.write or client.test): + logging.info("authorize: failed, client is not allowed to write or test") + return None + return client @@ -393,27 +396,18 @@ class MySQL(ObjectReq): type(self).__name__, type(self.req).__name__, self.host, self.user, self.dbname, self.port, self.catmap_filename, self.tagmap_filename) - def get_client_by_name(self, name): - format_strings = ','.join(['%s'] * len(name)) - self.crs.execute("SELECT cl.`id`, cl.`hostname`, s.`service`, s.`service_id`, s.`identity`, cl.`read`, s.`write`, s.`test`, cl.`debug` FROM `clients` cl LEFT JOIN `services` s ON cl.`id` = s.`client_id` WHERE cl.`valid` = 1 AND s.`valid` = 1 AND `hostname` IN (%s)" % format_strings, tuple(name)) + def get_client_by_name(self, identity, cert_names): + format_strings = ','.join(['%s'] * len(cert_names)) + query = "SELECT id, registered, requestor, hostname, service, note, identity, `read`, debug, `write`, test FROM clients WHERE valid = 1 AND identity = %%s AND hostname IN (%s)" % format_strings + self.crs.execute(query, [identity] + cert_names) rows = self.crs.fetchall() - if not rows: - return None - - client = {} - for n in ["id", "hostname", "read", "debug"]: - client[n] = rows[0][n] - services = {} - for row in rows: - service = {} - for n in ["service", "service_id", "identity", "write", "test"]: - service[n] = row[n] - services[row["identity"]] = service + if len(rows)>1: + logging.warn("get_client_by_name: query returned more than one result: %s" % str(rows)) + return None - client["services"] = services + client = Client(**rows[0]) if rows else None - logging.debug("get_client_by_name: %s", str(client)) return client @@ -475,7 +469,7 @@ class MySQL(ObjectReq): sqlwhere = [] sqlparams = [] - sqlwhere.append("SELECT e.id, e.data FROM services s RIGHT JOIN events e ON s.service_id = e.service_id WHERE e.id > %s") + sqlwhere.append("SELECT e.id, e.data FROM clients c RIGHT JOIN events e ON c.id = e.client_id WHERE e.id > %s") sqlparams.append(id or 0) if cat or nocat: @@ -497,7 +491,7 @@ class MySQL(ObjectReq): not_op = "" if group else "NOT" for identity in (group or nogroup): - sqlwhere.append(" AND s.identity %s LIKE %%s" % not_op) + sqlwhere.append(" AND c.identity %s LIKE %%s" % not_op) sqlparams.append(identity + "%") sqlwhere.append(" AND e.valid = 1 LIMIT %s") @@ -525,7 +519,7 @@ class MySQL(ObjectReq): def store_event(self, client, event): try: - self.crs.execute("INSERT INTO events (received,service_id,data) VALUES (NOW(), %s, %s)", (client["service"]["service_id"], json.dumps(event))) + self.crs.execute("INSERT INTO events (received,client_id,data) VALUES (NOW(), %s, %s)", (client.id, json.dumps(event))) lastid = self.crs.lastrowid logging.debug("store_event: Last ID in events - %i" % lastid) @@ -552,8 +546,8 @@ class MySQL(ObjectReq): def insertLastReceivedId(self, client, id): - logging.debug("insertLastReceivedId: id %i for client %i(%s)" % (id, client["id"], client["hostname"])) - self.crs.execute("INSERT INTO last_events(client_id, event_id, timestamp) VALUES(%s, %s, NOW())", (client["id"], id)) + logging.debug("insertLastReceivedId: id %i for client %i(%s)" % (id, client.id, client.hostname)) + self.crs.execute("INSERT INTO last_events(client_id, event_id, timestamp) VALUES(%s, %s, NOW())", (client.id, id)) def getLastEventId(self): self.crs.execute("SELECT MAX(id) as id FROM events") @@ -562,19 +556,26 @@ class MySQL(ObjectReq): return row['id'] if row['id'] is not None else 0 def getLastReceivedId(self, client): - self.crs.execute("SELECT MAX(event_id) as id FROM last_events WHERE client_id = %s", client["id"]) + self.crs.execute("SELECT MAX(event_id) as id FROM last_events WHERE client_id = %s", client.id) row = self.crs.fetchone() id = row['id'] if row is not None else 0 - logging.debug("getLastReceivedId: id %i for client %i(%s)" % (id, client["id"], client["hostname"])) + logging.debug("getLastReceivedId: id %i for client %i(%s)" % (id, client.id, client.hostname)) return id -def expose(meth): - meth.exposed = True - return meth +def expose(read=1, write=0, debug=0): + + def expose_deco(meth): + meth.exposed = True + meth.read = read + meth.write = write + meth.debug = debug + return meth + + return expose_deco class Server(ObjectReq): @@ -600,7 +601,7 @@ class Server(ObjectReq): # silently remove surplus arguments - potential forward # compatibility (unknown args will get ignored) - badargs = set(args)-set(func.func_code.co_varnames[0:func.func_code.co_argcount]) + badargs = set(args) - set(func.func_code.co_varnames[0:func.func_code.co_argcount]) for a in badargs: del args[a] if badargs: @@ -629,21 +630,26 @@ class Server(ObjectReq): except Exception: raise self.req.error("You've fallen of the cliff.", 404) - self.req.client = client = self.auth.authenticate(environ) + self.req.args = args = parse_qs(environ.get('QUERY_STRING', "")) + logging.debug("arguments: %s" % str(args)) + + self.req.client = client = self.auth.authenticate(environ, args) if not client: raise self.req.error("I'm watching. Authenticate.", 403) try: events = json.loads(injson) if injson else None except Exception as e: - raise self.req.error("Deserialization error", 400, + raise self.req.error("Deserialization error.", 400, sys.exc_info(), detail={"args": injson, "parser": str(e)}) - - args = parse_qs(environ.get('QUERY_STRING', "")) - logging.debug("calling handler with %s" % str(args)) if events: args["events"] = events + auth = self.auth.authorize(self.req.env, self.req.client, self.req.path, method) + if not auth: + raise self.req.error("I'm watching. Not authorized.", 403, detail={"client": client.identity}) + + args.pop("client", None) args = self.sanitize_args(path, method, args) result = method(**args) # call requested method @@ -709,15 +715,11 @@ class WardenHandler(ObjectReq): self.get_events_limit, self.send_events_limit, self.description) - @expose + @expose(read=1, debug=1) def getDebug(self): - auth = self.auth.authorize(self.req.env, self.req.client, self.req.path, None) - if not auth: - raise self.req.error("I'm watching. Authorize.", 403, detail={"client": self.req.client["hostname"]}) - return { "environment": self.req.env, - "client": self.req.client, + "client": self.req.client.__dict__, "database": self.db.get_debug(), "system": { "uname": os.uname() @@ -736,12 +738,8 @@ class WardenHandler(ObjectReq): } - @expose + @expose(read=1) def getInfo(self): - auth = self.auth.authorize(self.req.env, self.req.client, self.req.path, None) - if not auth: - raise self.req.error("I'm watching. Authorize.", 403, detail={"client": self.req.client["hostname"]}) - info = { "version": VERSION, "send_events_limit": self.send_events_limit, @@ -752,16 +750,12 @@ class WardenHandler(ObjectReq): return info - @expose + @expose(read=1) def getEvents(self, id=None, count=None, cat=None, nocat=None, tag=None, notag=None, group=None, nogroup=None): - auth = self.auth.authorize(self.req.env, self.req.client, self.req.path, None) - if not auth: - raise self.req.error("I'm watching. Authorize.", 403, detail={"client": self.req.client["hostname"]}) - try: id = int(id[0]) except (ValueError, TypeError, IndexError): @@ -799,7 +793,18 @@ class WardenHandler(ObjectReq): return res - @expose + def checkNode(self, event, identity): + try: + ev_id = event['Node'][0]['Name'].lower() + except (KeyError, TypeError): + # Event does not bear valid Node attribute + return ["Event does not bear valid Node attribute"] + if ev_id != identity: + return ["Node does not correspond with saving client"] + return [] + + + @expose(write=1) def sendEvents(self, events=[]): if not isinstance(events, list): raise self.req.error("List of events expected.", 400) @@ -811,19 +816,20 @@ class WardenHandler(ObjectReq): saved = 0 errs = {} for i, event in enumerate(events): - ev_errs = [] - - auth_cl = self.auth.authorize(self.req.env, self.req.client, self.req.path, event) - if not auth_cl: - errs[i] = ["Client %i(%s) does not correspond with event Node info or is not allowed to write" % (self.req.client["service"]["service_id"], self.req.client["service"]["identity"])] - continue - v_errs = self.validator.check(event) if v_errs: errs[i] = v_errs continue - db_errs = self.db.store_event(auth_cl, event) + node_errs = self.checkNode(event, self.req.client.identity) + if node_errs: + errs[i] = node_errs + continue + + if self.req.client.test and not 'Test' in event.get('Category', []): + errs[i] = ["You're allowed to send only messages, containing \"Test\" among categories."] + + db_errs = self.db.store_event(self.req.client, event) if db_errs: errs[i] = db_errs continue