From 869d2d5ce2fb1110ed09febb2a5b4547e34a359a Mon Sep 17 00:00:00 2001 From: Yves Mettier Date: Tue, 28 Apr 2015 11:25:16 +0200 Subject: [PATCH 1/3] Updated collectdout to 0.0.8 with crypto support --- node_modules/collectdout/README.md | 16 ++ node_modules/collectdout/lib/index.js | 207 +++++++++++++++++++++----- node_modules/collectdout/package.json | 18 ++- 3 files changed, 197 insertions(+), 44 deletions(-) diff --git a/node_modules/collectdout/README.md b/node_modules/collectdout/README.md index 38a00a3..b454838 100755 --- a/node_modules/collectdout/README.md +++ b/node_modules/collectdout/README.md @@ -19,6 +19,18 @@ var client = new Collectd(60000, "collectd_server", 25826, "my_server"); ``` Fourth argument is optional, default is os.hostname() +To transmit data securely with username 'alice' and password '12345', you can +select authentication (HMAC) by setting the security level to 1. +```javascript +var client = new Collectd(60000, "collectd_server", 25826, "my_server", + 1, 'alice', '12345'); +``` +You can also encrypt the trasmitted data by setting the security level to 2. +```javascript +var client = new Collectd(60000, "collectd_server", 25826, "my_server", + 2, 'alice', '12345'); +``` + Create your plugin instance: ```javascript var plugin = client.plugin('myapp', 'worker13'); @@ -37,6 +49,10 @@ plugin.addCounter('uptime', '0', 1); ``` # Change log +- v0.0.8 + * Adding crypto support +- v0.0.7 + * Fix counter bug - v0.0.6 * Possibility to send notification - v0.0.5 diff --git a/node_modules/collectdout/lib/index.js b/node_modules/collectdout/lib/index.js index 867dbc1..21b9f2e 100755 --- a/node_modules/collectdout/lib/index.js +++ b/node_modules/collectdout/lib/index.js @@ -1,6 +1,7 @@ var dgram = require('dgram'); var net = require('net'); var os = require('os'); +var crypto = require('crypto'); /** * Generic helper because all Collectd plugins and PluginInstance @@ -92,8 +93,8 @@ PluginInstance.prototype = { * * Syntax : * 1/ var client = Collectd(); - * 2/ var client = Collectd(int, string, int, string); - * 3/ var client = Collectd(int, object, ignored, string); + * 2/ var client = Collectd(int, string, int, string, int, string, string); + * 3/ var client = Collectd(int, object, ignored, string, int, string, string); * * All arguments are optional (like in syntax 1) * Syntax 1 @@ -103,16 +104,30 @@ PluginInstance.prototype = { * arg2 is the Collectd server name * arg3 is the Collectd server port * arg4 is the host name used in Collectd values + * arg5 is the security level (0: No security, 1: Sign, 2: Encrypt) + * optional, defaults to 0 (No security) + * arg6 is the username used for signing/encrypting + * required only if security level is 1 (Sign) or 2 (Encrypt) + * arg7 is the password used for signing/encrypting + * required only if security level is 1 (Sign) or 2 (Encrypt) * Syntax 3 * arg1 is the interval between 2 sending data to Collectd servers * arg2 is an array like this : [ ['host1', port], ['host2', port], ...] * arg3 is ignored. * arg4 is the host name used in Collectd values + * arg5 is the security level (0: No security, 1: Sign, 2: Encrypt) + * optional, defaults to 0 (No security) + * arg6 is the username used for signing/encrypting + * required only if security level is 1 (Sign) or 2 (Encrypt) + * arg7 is the password used for signing/encrypting + * required only if security level is 1 (Sign) or 2 (Encrypt) */ -function Collectd(interval, host, port, hostname) { +function Collectd(interval, host, port, hostname, securityLevel, username, password) { this.interval = interval || 10000; this.serverHosts = []; - + this.securityLevel = securityLevel; + this.username = username; + this.password = password; this.plugins = {}; this.hostname = typeof hostname !== 'undefined' ? hostname : os.hostname(); @@ -156,8 +171,12 @@ Collectd.prototype.plugin = function(name, instance) { */ Collectd.prototype.send = function() { var prevHostname, prevPlugin, prevInstance, prevType, prevTypeInstance, prevTime, prevInterval; - - var pkt = new Packet(this.write.bind(this)); + var pkt = new Packet({ + sendCb: this.write.bind(this), + securityLevel: this.securityLevel, + username: this.username, + password: this.password, + }); var hostname = this.hostname; var interval = this.interval; var time = Math.floor(new Date().getTime() / 1000); @@ -245,8 +264,9 @@ Collectd.prototype.NOTIF_OKAY=4; * Note : if notif.h is defined and its value is false, the default hostname will be sent */ Collectd.prototype.sendNotif = function(notif) { - - var pkt = new Packet(this.write.bind(this)); + var pkt = new Packet({ + sendCb: this.write.bind(this) + }); var hostname = this.hostname; var time = Math.floor(new Date().getTime() / 1000); @@ -279,20 +299,135 @@ Collectd.prototype.write = function(buf) { } }; - var MAX_PACKET_SIZE = 1024; -function Packet(sendCb) { - this.sendCb = sendCb; - this.buf = new Buffer(MAX_PACKET_SIZE); +var SECURITY_LEVEL = { + NONE: 0, + SIGN: 1, + ENCRYPT: 2, +}; +var SIGNATURE_OVERHEAD = 36; +var ENCRYPTION_OVERHEAD = 42; + +function Packet(args) { + + this.sendCb = args.sendCb; + this.username = args.username; + this.password = args.password; + this.securityLevel = args.securityLevel !== undefined ? args.securityLevel : 0; this.pos = 0; + + switch (this.securityLevel) { + case SECURITY_LEVEL.NONE: + this.overhead = 0; + break; + case SECURITY_LEVEL.SIGN: + this.overhead = SIGNATURE_OVERHEAD + this.username.length; + break; + case SECURITY_LEVEL.ENCRYPT: + this.overhead = ENCRYPTION_OVERHEAD + this.username.length; + break; + default: + throw 'Packet: Invalid Security Level'; + break; + } + + this.buf = new Buffer(MAX_PACKET_SIZE - this.overhead); } Packet.prototype = { + send: function() { - if (this.pos > 0) - this.sendCb(this.buf.slice(0, this.pos)); - this.pos = 0; + if (this.pos > 0) { + this.encapsulate(); + this.sendCb(this.buf); + } + this.pos = 0; + }, + + encapsulate: function () { + switch (this.securityLevel) { + case SECURITY_LEVEL.NONE: + break; + case SECURITY_LEVEL.SIGN: + this.sign(); + break; + case SECURITY_LEVEL.ENCRYPT: + this.encrypt(); + break; + default: + throw new Error('Invalid Security Level: ' + + this.securityLevel); + } + }, + + sign: function () { + + // Signed Packet format: + // [header][data] + // + // Header format: + // [packet_type][header_length][ mac ][ username ] + // [ 2 bytes ][ 2 bytes ][ 32 bytes ][ dynamic ] + + // original (unencapsulated) packet + var original = this.buf.slice(0, this.pos); + + // Calculate HMAC on concatenation of username and payload + var hmac_sha256 = crypto.createHmac('sha256', this.password); + hmac_sha256.write(this.username); + hmac_sha256.write(original); + var mac = hmac_sha256.digest(); + + // Create a new buffer for the signed packet + var buffer = new Buffer(this.overhead + original.length); + buffer.writeUInt16BE('0x0200', 0); + buffer.writeUInt16BE(this.overhead, 2); + mac.copy(buffer, 4); + buffer.write(this.username, 36); + original.copy(buffer, this.overhead); + this.buf = buffer; + }, + + encrypt: function () { + // Encrypted Packet format: + // [header][encr(data)] + // + // Header format: + // [packet_type][total_packet_length][username_length][username][ IV ][ encr(checksum) ] + // [ 2 bytes ][ 2 bytes ][ 2 bytes ][dynamic ][ 16 bytes ][ 20 bytes ] + + // original (unencapsulated) packet + var original = this.buf.slice(0, this.pos); + + // Calculate SHA1 checksum of original packet + var sha1 = crypto.createHash('sha1'); + sha1.write(original); + var checksum = sha1.digest(); + + // Create encryption key by hashing password + var sha256 = crypto.createHash('sha256'); + sha256.write(this.password); + var key = sha256.digest(); + + // Generate an initialization vector + var iv = crypto.randomBytes(16); + + // Encrypt concatenation of checksum and original + var aes256_ofb = crypto.createCipheriv('aes-256-ofb', key, iv); + aes256_ofb.write(checksum); + aes256_ofb.write(original); + var ciphertext = aes256_ofb.read() + + // Create a new buffer for the encrypted packet + var buffer = new Buffer(this.overhead + original.length); + buffer.writeUInt16BE('0x0210', 0); + buffer.writeUInt16BE(buffer.length, 2); + buffer.writeUInt16BE(this.username.length, 4); + buffer.write(this.username, 6); + iv.copy(buffer, 6 + this.username.length); + ciphertext.copy(buffer, 6 + this.username.length + 16); + this.buf = buffer; }, addStringPart: function(id, str) { @@ -370,27 +505,27 @@ Packet.prototype = { /* Tries to make it fit in 1024 bytes or starts a new packet */ catchOverflow: function(cb, resetCb) { - var tries = 2; - while(tries > 0) { - tries--; - - var oldPos = this.pos; - try { - /* On success return */ - return cb(); - } catch (e) { - if (e.constructor === PacketOverflow) { - /* Flush packet so far */ - this.pos = oldPos; - this.send(); - this.buf = new Buffer(MAX_PACKET_SIZE); - /* Clear state */ - resetCb(); - /* And retry... */ - } else - throw e; - } - } + var tries = 2; + while(tries > 0) { + tries--; + + var oldPos = this.pos; + try { + /* On success return */ + return cb(); + } catch (e) { + if (e.constructor === PacketOverflow) { + /* Flush packet so far */ + this.pos = oldPos; + this.send(); + this.buf = new Buffer(MAX_PACKET_SIZE - this.overhead); + /* Clear state */ + resetCb(); + /* And retry... */ + } else + throw e; + } + } } }; diff --git a/node_modules/collectdout/package.json b/node_modules/collectdout/package.json index 16d9c33..7d6b0ba 100755 --- a/node_modules/collectdout/package.json +++ b/node_modules/collectdout/package.json @@ -1,7 +1,7 @@ { "name": "collectdout", "description": "Periodically send values out to a Collectd server for statistics", - "version": "0.0.7", + "version": "0.0.8", "main": "./lib/index.js", "author": { "name": "Astro" @@ -25,20 +25,22 @@ "type": "MIT" } ], - "gitHead": "eb033e62b95cf754d985742cd9a3460a856399b9", - "_id": "collectdout@0.0.7", + "gitHead": "ff256ddbddfcfc9af0d7a4e4f008141370a2b1df", + "_id": "collectdout@0.0.8", "scripts": {}, - "_shasum": "84d31b4a9de71762d8efd3b7611881f4de3afafb", + "_shasum": "25e9ef3f04a65bcff9eb9fb524b4446482ae4d02", "_from": "collectdout@*", - "_npmVersion": "1.4.28", + "_npmVersion": "2.1.5", + "_nodeVersion": "0.10.25", "_npmUser": { "name": "feraudet", "email": "cyril@feraudet.com" }, "dist": { - "shasum": "84d31b4a9de71762d8efd3b7611881f4de3afafb", - "tarball": "http://registry.npmjs.org/collectdout/-/collectdout-0.0.7.tgz" + "shasum": "25e9ef3f04a65bcff9eb9fb524b4446482ae4d02", + "tarball": "http://registry.npmjs.org/collectdout/-/collectdout-0.0.8.tgz" }, "directories": {}, - "_resolved": "https://registry.npmjs.org/collectdout/-/collectdout-0.0.7.tgz" + "_resolved": "https://registry.npmjs.org/collectdout/-/collectdout-0.0.8.tgz", + "readme": "ERROR: No README data found!" } From c0bb3735c2e22e2023dae15d9904ba359c577337 Mon Sep 17 00:00:00 2001 From: Dimitris Rozakis Date: Fri, 24 Apr 2015 13:33:20 +0300 Subject: [PATCH 2/3] Add crypto support in collectd networking protocol This requires node-collectdout version >= 0.0.8 --- config/default.json | 7 +++++++ src/collectm.js | 39 ++++++++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/config/default.json b/config/default.json index 345a333..58b8b90 100644 --- a/config/default.json +++ b/config/default.json @@ -10,6 +10,13 @@ "Interval": 60, +// Cryptography/Security settings + "Crypto": { + "SecurityLevel": 0, + "Username": "alice", + "Password": "12345678" + }, + // Time To Live : will stop Collectm after this value (86400sec = 1 day). // This is useful if you think there is a memory leak and want to reboot periodically // Collectm. Stop Collectm after 86400sec and Windows will restart the service. diff --git a/src/collectm.js b/src/collectm.js index 47fced9..6550d60 100755 --- a/src/collectm.js +++ b/src/collectm.js @@ -93,6 +93,34 @@ function get_interval() { return(cfg.has('Interval') ? (cfg.get('Interval') * 1000) : 60000); } +function get_security_level() { + var securityLevel = 0; + if (cfg.has('Crypto') && cfg.get('Crypto').SecurityLevel !== undefined) { + securityLevel = cfg.get('Crypto').SecurityLevel; + if (securityLevel !== 0 && securityLevel !== 1 && securityLevel !== 2) { + throw new Error('Security level must be in (0, 1, 2).'); + } + } + if ((get_username() === '' || get_password() === '') && securityLevel > 0) { + throw new Error('Security level set greater to 0 but username or password left empty.'); + } + return securityLevel; +} + +function get_username() { + if (cfg.has('Crypto') && cfg.get('Crypto').Username !== undefined) { + return cfg.get('Crypto').Username; + } + return ''; +} + +function get_password() { + if (cfg.has('Crypto') && cfg.get('Crypto').Password !== undefined) { + return cfg.get('Crypto').Password; + } + return ''; +} + function get_collectm_ttl() { return(cfg.has('CollectmTimeToLive') ? (cfg.get('CollectmTimeToLive') * 1000) : 0); } @@ -104,7 +132,7 @@ function get_log_deletion_days() { function remove_old_logs(days) { var now = new Date(); now = now.getTime(); - + fs.readdir(path.join(prefix, 'logs'), function(err, files) { var filenames; if(err) { @@ -114,7 +142,7 @@ function remove_old_logs(days) { filenames = files.map(function (f) { return path.join(prefix,'logs',f); }); - + each(filenames, function(i,f) { if(/collectm\.log/.test(f)) { fs.stat(f, function(err, stat) { @@ -141,8 +169,9 @@ function remove_old_logs(days) { collectmHostname = get_hostname_with_case(); logger.log('info', 'Sending metrics to Collectd with hostname '+collectmHostname+' (case sensitive).'); -client = new Collectd(get_interval(), get_collectd_servers_and_ports(), 0, collectmHostname); - +client = new Collectd(get_interval(), get_collectd_servers_and_ports(), 0, collectmHostname, + get_security_level(), get_username(), get_password()); + /* Load the plugins */ pluginsCfg = cfg.has('Plugin') ? cfg.get('Plugin') : []; plugin = {}; @@ -226,7 +255,7 @@ if(cfg.get('HttpConfig.enable')) { collectmTimeToLive = get_collectm_ttl(); if(collectmTimeToLive > 60) { logger.info('TTL configured : will gracefully stop after '+parseInt(collectmTimeToLive/1000)+' seconds'); - setTimeout(function() { + setTimeout(function() { logger.error('Gracefully stopped after configured TTL'); process.exit(); }, collectmTimeToLive); From 082e586108590afb211ce05428be607461732d91 Mon Sep 17 00:00:00 2001 From: Yves Mettier Date: Thu, 7 May 2015 11:41:11 +0200 Subject: [PATCH 3/3] Updated version/Changelog --- ChangeLog | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index acff52b..57b7a99 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,5 @@ +- PR #14 : Added crypto support (Thanks to Dimitris Rozakis) + Version 1.5.1 - PR #8 : fix syntax/parsing errors in default.json. Thanks to Dimitris Rozakis. diff --git a/package.json b/package.json index 3dedf47..042e139 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "CollectM", - "version": "1.5.1", + "version": "1.5.1cryptosupport", "author": "Cyril Feraudet", "license": "GPLv2", "dependancies": {