diff --git a/src/master/client.py b/src/master/client.py index c2ecac03079c66385e90091644f45e63422a2809..08085dba7c06891090dd8badc8bccbc999e1e554 100644 --- a/src/master/client.py +++ b/src/master/client.py @@ -36,7 +36,10 @@ sysrand = random.SystemRandom() challenge_len = 128 # 128 bits of random should be enough for log-in to protect against replay attacks with database.transaction() as t: - # As we just started, there's no plugin active anywhere. Flush whatever is left in the table. + # As we just started, there's no plugin active anywhere. + # Mark anything active as no longer active in the history and + # flush the active ones. + t.execute("INSERT INTO plugin_history (client, name, timestamp, active) SELECT client, name, CURRENT_TIMESTAMP AT TIME ZONE 'UTC', false FROM active_plugins") t.execute("DELETE FROM active_plugins") class ClientConn(twisted.protocols.basic.Int32StringReceiver): @@ -102,8 +105,10 @@ class ClientConn(twisted.protocols.basic.Int32StringReceiver): logger.info("Connection lost from %s", self.cid()) self.__pinger.stop() self.__plugins.unregister_client(self) + now = database.now() def log_plugins(transaction): logger.debug("Dropping plugin list of %s", self.cid()) + transaction.execute("INSERT INTO plugin_history (client, name, timestamp, active) SELECT ap.client, ap.name, %s, false FROM active_plugins AS ap JOIN clients ON ap.client = clients.id WHERE clients.name = %s", (now, self.cid())) transaction.execute('DELETE FROM active_plugins WHERE client IN (SELECT id FROM clients WHERE name = %s)', (self.cid(),)) return True activity.push(log_plugins) @@ -236,8 +241,11 @@ class ClientConn(twisted.protocols.basic.Int32StringReceiver): now = database.now() def log_versions(transaction): logger.debug("Replacing plugin list of %s", self.cid()) + # The current state (override anything previous) transaction.execute('DELETE FROM active_plugins WHERE client IN (SELECT id FROM clients WHERE name = %s)', (self.cid(),)) transaction.executemany("INSERT INTO active_plugins (client, name, updated, version, hash, libname, active) SELECT clients.id, %s, %s, %s, %s, %s, %s FROM clients WHERE clients.name = %s", map(lambda plug: (plug['name'], now, plug['version'], plug['hash'].encode('hex'), plug['lib'], plug['activity'], self.cid()), versions.values())) + # The history, just append (yes, there may be duplicates, but who cares) + transaction.executemany("INSERT INTO plugin_history (client, name, timestamp, version, hash, active) SELECT clients.id, %s, %s, %s, %s, %s FROM clients WHERE clients.name = %s", map(lambda plug: (plug['name'], now, plug['version'], plug['hash'].encode('hex'), plug['activity'], self.cid()), versions.values())) return True activity.push(log_versions) diff --git a/src/master/dbscripts/initdb b/src/master/dbscripts/initdb index fe58e947759026c3c6a0c9adc7de5ecdeee604ff..3bf8b4d48901f362266ba0d01bdbd26fcd7f98ec 100755 --- a/src/master/dbscripts/initdb +++ b/src/master/dbscripts/initdb @@ -22,6 +22,7 @@ DROP TABLE IF EXISTS fake_logs; DROP TABLE IF EXISTS fake_server_names; DROP TABLE IF EXISTS fake_blacklist_limits; DROP TABLE IF EXISTS fake_blacklist_scores; +DROP TABLE IF EXISTS plugin_history; DROP TABLE IF EXISTS known_plugins; DROP TABLE IF EXISTS active_plugins; DROP TABLE IF EXISTS ssh_commands; @@ -110,6 +111,18 @@ CREATE TABLE active_plugins ( UNIQUE(client, name), FOREIGN KEY (client) REFERENCES clients(id) ); +CREATE TABLE plugin_history ( + id BIGINT NOT NULL PRIMARY KEY, + client INT NOT NULL, + name TEXT NOT NULL, + timestamp TIMESTAMP NOT NULL, + version INT, + hash TEXT, + active BOOL NOT NULL, + FOREIGN KEY (client) REFERENCES clients(id) +); +CREATE SEQUENCE plugin_history_id OWNED BY plugin_history.id; +ALTER TABLE plugin_history ALTER COLUMN id SET DEFAULT NEXTVAL('plugin_history_id'); CREATE OR REPLACE VIEW plugin_activity AS SELECT clients.name, MIN(globals.last) AS last, @@ -700,6 +713,8 @@ GRANT SELECT ON known_plugins TO $DBUPDATER; GRANT DELETE ON active_plugins TO $DBUPDATER; GRANT INSERT ON active_plugins TO $DBUPDATER; GRANT SELECT ON active_plugins TO $DBUPDATER; +GRANT INSERT ON plugin_history TO $DBUPDATER; +GRANT ALL ON plugin_history_id TO $DBUPDATER; GRANT SELECT (name, passwd, mechanism, builtin_passwd, slot_id) ON clients TO $DBAUTHENTICATOR; @@ -741,6 +756,10 @@ GRANT DELETE ON refused TO $DBCLEANER; GRANT DELETE ON ssh_sessions TO $DBCLEANER; GRANT DELETE ON ssh_commands TO $DBCLEANER; GRANT SELECT ON fake_logs TO $DBCLEANER; +GRANT SELECT ON plugin_history TO $DBCLEANER; +GRANT INSERT ON plugin_history TO $DBCLEANER; +GRANT DELETE ON plugin_history TO $DBCLEANER; +GRANT ALL ON plugin_history_id TO $DBUPDATER; GRANT SELECT ON groups TO $DBARCHIVIST; GRANT SELECT ON count_types TO $DBARCHIVIST; diff --git a/src/master/dbscripts/purge b/src/master/dbscripts/purge index 9c19b85159bc18ae231ff6840670ef726ecfd00d..a7ff1bf73953ec12138c65faac9b09a18644b70e 100755 --- a/src/master/dbscripts/purge +++ b/src/master/dbscripts/purge @@ -4,12 +4,16 @@ set -ex . ./dbconfig -TABLES='activities anomalies count_snapshots bandwidth bandwidth_stats bandwidth_stats_dbg refused fake_logs' +TABLES='activities anomalies count_snapshots bandwidth bandwidth_stats bandwidth_stats_dbg refused fake_logs plugin_history' BATCH_TABLES='pings certs nats spoof' DATE=$(date -d "$CLEAN_DAYS days ago" "+'%Y-%m-%d'") ( echo "BEGIN;" + # Duplicate the last plugin history event. This way, even if + # the client is connected for a long time, we won't lose all + # the events and we shall know what its state is now or a day ago + echo "INSERT INTO plugin_history (client, name, timestamp, version, hash, active) SELECT DISTINCT ph.client, ph.name, CURRENT_TIMESTAMP AT TIME ZONE 'UTC', ph.version, ph.hash, ph.active FROM plugin_history AS ph JOIN (SELECT client, name, MAX(timestamp) AS timestamp FROM plugin_history GROUP BY client, name) AS latest ON ph.client = latest.client AND ph.name = latest.name AND ph.timestamp = latest.timestamp;" for TABLE in $TABLES ; do echo "DELETE FROM $TABLE WHERE timestamp < $DATE;" done