From fddca0cdb4b684e7f7bc801572421ce6ee644601 Mon Sep 17 00:00:00 2001 From: Chris Saylor Date: Wed, 26 Jun 2013 13:55:50 -0400 Subject: [PATCH] Added SSL support Includes: * SSL verification if the logstash is configured to verify the sender. This is tested by passing the public key of the client as a CA to the SSL TPC server. --- .travis.yml | 3 +- lib/winston-logstash.js | 35 ++++++++++++---- test/client.key | 28 +++++++++++++ test/client.pub | 21 ++++++++++ test/server.key | 28 +++++++++++++ test/server.pub | 22 ++++++++++ test/winston-logstash_test.js | 75 ++++++++++++++++++++++++++++++++++- 7 files changed, 202 insertions(+), 10 deletions(-) create mode 100644 test/client.key create mode 100644 test/client.pub create mode 100644 test/server.key create mode 100644 test/server.pub diff --git a/.travis.yml b/.travis.yml index 2ca91f2..7636c1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: node_js node_js: - "0.10" - - "0.8" \ No newline at end of file + - "0.8" +env: NODE_TLS_REJECT_UNAUTHORIZED=0 diff --git a/lib/winston-logstash.js b/lib/winston-logstash.js index 10278a5..8b5de6f 100644 --- a/lib/winston-logstash.js +++ b/lib/winston-logstash.js @@ -8,6 +8,8 @@ var net = require('net'), util = require('util'), os = require('os'), + tls = require('tls'), + fs = require('fs'), winston = require('winston'), common = require('winston/lib/winston/common'); @@ -22,6 +24,10 @@ var Logstash = exports.Logstash = function (options) { this.node_name = options.node_name || process.title; this.pid = options.pid || process.pid; this.max_connect_retries = options.max_connect_retries || 4; + this.ssl_enable = options.ssl_enable || false; + this.ssl_key = options.ssl_key || ''; + this.ssl_cert = options.ssl_cert || ''; + this.ssl_passphrase = options.ssl_passphrase || ''; // Connection state this.log_queue = []; @@ -78,10 +84,24 @@ Logstash.prototype.log = function (level, msg, meta, callback) { }; Logstash.prototype.connect = function () { + var options = {}; + var self = this; this.retries++; this.connecting = true; - this.socket = new net.Socket(); - var self = this; + if (this.ssl_enable) { + options = { + key: this.ssl_key ? fs.readFileSync(this.ssl_key) : null, + cert: this.ssl_cert ? fs.readFileSync(this.ssl_cert) : null, + passphrase: this.ssl_passphrase ? this.ssl_passphrase : null + } + this.socket = new tls.connect(this.port, this.host, options, function() { + self.socket.setEncoding('UTF-8'); + self.announce(); + self.connecting = false; + }); + } else { + this.socket = new net.Socket(); + } this.socket.on('error', function (err) { self.connecting = false; @@ -111,11 +131,12 @@ Logstash.prototype.connect = function () { } }); - - this.socket.connect(self.port, self.host, function () { - self.announce(); - self.connecting = false; - }); + if (!this.ssl_enable) { + this.socket.connect(self.port, self.host, function () { + self.announce(); + self.connecting = false; + }); + } }; diff --git a/test/client.key b/test/client.key new file mode 100644 index 0000000..d31c2b3 --- /dev/null +++ b/test/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCy6hmYDJurpjsg +kwUrb4GjThAswWkcwOWf8A0OH5mMZ5cqutbuWRvH8n8lY9ImHSr+JqaB3++soItS +yOBjwcDGy08x+/r4Kv2SFx0bneMh8fbbYL2jh5qBAZkp9c1pKvlEZGqLV855dfbD +/Ax9lGjcBLUh/ptXNsFrXwHzQgQkQ85B/93L/px0DmPkR2/JiS1X9eLjfCzdIO3F +JQ2Jb1UAJo6svHpctv7QLFqSUzOjCgBwAafmwzOD9gro59ZLp2s87lzDQcKyG2F6 +v+hlsI0Cs6tT5GR2zEny5MBryZveuMAxVgWk/2ue0brq3WGQag8OWGoKbTmqFX9V +qYKL69jxAgMBAAECggEAZrW7wa0jRPQmJ3ofUqMt4gkzge36oNYgPKP7aqs7Y/4o +KxCdyLEBINUWpulaQCUohES9+/sJxalYskq9fqdBOx6GNWS52/QaI97/B9apCNi2 +ZZRw6cs0KHE5c3w30vMnrFqVS685ykKtSi+NIB6t3/kX3w8iMHwvp6sgHH0+kW4h +b3q1rIZsppqiUAlRdZ78aUHxT4dgkq7M1vJPd+LnH3JSkxRbItKhVkFEtZ2nqGTa +gJZkCXFyZ9z3Ot+lPA2SZ9fNi+xBzaJG99GR7gbkNRW9RcFOBE/fuIB5WXz562aX +n3BnvadhYb6sJEnbgopDRO1jamVi/WaVfx8uwkQq/QKBgQDjlUmkFe/eKkgqLkB6 +tM0OWs4Jv0TreMd0THp3G2YVJqA4i6sgn8RxXMULUDmfoxpUdi/kRZB7uMYIY9SU +u6tH9GYEZu3K5LhGxd9KVLpyw5PIQBZhnlmPmfLmY7AUj8CjTX8Vu6oH4ESB419g +Nep9vueTt6x9geLO/sBPX39fDwKBgQDJQR0ZbgMx9IHQqTQYEcIhCsoGpw1OUeFF +qi46gHI4VBogcmoLM3lYeRFE0/LaS45ngeuytxdaFQTc4tVLgy/vq2X7BT2QKMFf +3YQgra/mhSREPzSrfLE4BW1FHKdH4DKd0Y5eGAftvhMxwXR+go6bRUVF/2hie+T2 +pN7brGFH/wKBgQDQHomFgHom1MvBpyJYFFchNXEOANfp7j2X4QhtO6US2ovSqgcI +gpKGiKu5ONg5YFKHwrzxJ5yFR+fxbDnO+gAcgSd6BxaWYKsULYeQ1gLbSHcwvUq6 +YrDMXaB/h9JSO1OTAx+gpElNgo2jqmZh+WxDKf1kXKdk7w6W9OiwrUgxiQKBgBpE +n3G0REJ+8Xq0uxHxL290ow3fPANe+JJJNiN4pG/pukdF58VygIIluzVKnMYVWo70 +DDmVYDznxWy3RrPGeblEm9j5xCFO0H6cwSQqx2zFMAigX1WKvu7a9now1TyybKjg +sz5g0JgdWRADehsRG1/02KfcQFmqZiYzOstdYo0tAoGBAKxlxuV1SbfXxRgOh5HG +EE/oyh41VzeuVWtPMCRd1sPbpW/3iHkrvra7RTSqei8DjTWlVmNFnq6b/6laNyXL +9yLRUJ+jwIXUzCotyKDm5CTkGgaiXX9umGX9m+GThRXu4UuE8ZeO3wBkf3CuyG7s +hwlPvwysf1z7VMjWVhVc/MaT +-----END PRIVATE KEY----- diff --git a/test/client.pub b/test/client.pub new file mode 100644 index 0000000..b64f971 --- /dev/null +++ b/test/client.pub @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgIJAMV5n38IYrTaMA0GCSqGSIb3DQEBBQUAMFExCzAJBgNV +BAYTAlVTMRAwDgYDVQQIDAdGbG9yaWRhMRowGAYDVQQKDBFadW1iYSBGaXRuZXNz +IExMQzEUMBIGA1UEAwwLKi56dW1iYS5kZXYwHhcNMTMwNjI2MTYxNTA0WhcNNDAx +MTEwMTYxNTA0WjBRMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHRmxvcmlkYTEaMBgG +A1UECgwRWnVtYmEgRml0bmVzcyBMTEMxFDASBgNVBAMMCyouenVtYmEuZGV2MIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsuoZmAybq6Y7IJMFK2+Bo04Q +LMFpHMDln/ANDh+ZjGeXKrrW7lkbx/J/JWPSJh0q/iamgd/vrKCLUsjgY8HAxstP +Mfv6+Cr9khcdG53jIfH222C9o4eagQGZKfXNaSr5RGRqi1fOeXX2w/wMfZRo3AS1 +If6bVzbBa18B80IEJEPOQf/dy/6cdA5j5EdvyYktV/Xi43ws3SDtxSUNiW9VACaO +rLx6XLb+0CxaklMzowoAcAGn5sMzg/YK6OfWS6drPO5cw0HCshther/oZbCNArOr +U+RkdsxJ8uTAa8mb3rjAMVYFpP9rntG66t1hkGoPDlhqCm05qhV/VamCi+vY8QID +AQABo1AwTjAdBgNVHQ4EFgQUoijtfF6jqF9n25cAT5P+krNjOlQwHwYDVR0jBBgw +FoAUoijtfF6jqF9n25cAT5P+krNjOlQwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B +AQUFAAOCAQEAcs5wGxpkHoPorIdLy6UWlWM6droakw52l8ui8y5BBO2yZEwWffuu +8EBJxfEquuwuLtlhfXq8snbJbZPu9bw6XOjeHDhTB5RfqpA9yCB6/faWdUlf0pQR +U8ujwICHi5x0PIBtbykfg90joFSFKrfxj4nSvK+G7WZgQ0xpTh3hFRugS+wrap9W ++oBhaoVhH5WJK5k0QgnANUOuhPlBfVhB3iQCZd0HILO6OoTfuLrKxeO+ro/2Pcq7 +LTTRcDYJAeAvTBhmglEMpx2rp70svXbFNItPXonfng6Pzjek1C3Q2uAqf57GB7dn +Xl0096sChnx68zuxV9YgPyrRhithinU2WA== +-----END CERTIFICATE----- diff --git a/test/server.key b/test/server.key new file mode 100644 index 0000000..5c62e3f --- /dev/null +++ b/test/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDSrWDfSigJZfSs +iBBaX5DX6DA/e2q2PapdLMZq1bcpB5L/RVikzHHrOz7HwxNjJQFrn1jO8HQRCOIL +OhnN01HsXvYR0EL/aIUEZn7Uf0r9yIoKD/wQn9oPessHmvysJImXH6ZfDw74F+10 +kxncaFnXcKgJH5s1PHGNs5PeILcyyMb8yGVnDgUWIZEApNUCaqAxz2PFt2aNjthT +diLRxjEuWWnVMXjAdpLj+Bpp3QUJXgllIGTpHFICPg1Fy33qjxRIKI17JVANXeRf +qyHUT9yle3+Y8UBMcDtFfRi/7GFFKFU0+1pd3d1Ha+pHHIrASsSKK7CkoisKqnV2 +2iwnXMzNAgMBAAECggEAf/fQX9ErgIntlN8RD9P8pyjiJa1RpGDQmsgXvc0S9Fkl +cOkubuvARqI2ACD+JbrUogqoZwqndbFvnGIuSleR64HC9g8KoxlZstKPN4QaiQzG +zFXpRi/SxJRRvszlraqD6MfQxuzeqtPd6HFxw++eor5LUz9fh3avguMRbltw6+bu +E5y8ej2+XdBeonlUuMoDteRq28hs3zxrnL/e9lIxY7ULSFlz5gS5I+IfxNyGEXeV +KGVRqKarW1Nyh5BlVuUnMN14yB9s0gjI4dp3e2OIKHBcHglJqzHKWtsdjb1Hb09O +B3YS21dg2YWTKmfF/OvLn2W4TOlrYmsoeGEtIy0AAQKBgQD57UEr6Ef9jFVTsAPy +EtnnT+U7msMuHS7KN2VFHz1FOqOwcrKOK1I4ECpYz4GOyn00VaWz02Bv3Srhx6nN +uRWZw1i5RoBhzIEg9DDOwV84nWeSLMzTMBTC8Mhp/0n3NgUsifG13lYKIUR7sBmv +sRWdayBKgWgRKZrivCFAuTWtzQKBgQDXy/XVvwQ5ksH5E/kFL5LNdjxC6z3WOprX +NU3OiDzvuJt+Ince2yhFKg8sXKUQSmrfjkex0XYyzQFiDvzE7lH6ye0xzZg+MUoR +wa5ugk2iTuq9lktFQxMZllEhUb9//1nqX7FlakTDMde15T45EwDT6TjudICHsZWt +QNV9GZybAQKBgGLboPLAL3GwR7QRpI3lPQINDUx1XAzqiC5+mPUtdSvkFQlfZRdt +NVlts4JrmgCkQcAovKGT08qLvkGOm7D/K/clWPv4UiHdJZqmXIP74zFeubANPnuu +wheV7IBY9aXuXT+P1OcuafQZ0p/mOVLQhg89Z4lxBHv9bAGRjsmuOPhlAoGAfZQp +uXtxf/eRfdtLnOL0cTCPHPo8gACWvwo7/yZ1H6O0iRCRdZlfs/An6E75l6NW0kXA +HxCf0ixO07uZCRkPB/yeVc8o+3g/fFnTomedughmvnFJ2EKSSN+a0uL5qAj3UFbj +qvrOjTDiMO346cnPP4KHKO0PJugHDE2gby6KXgECgYEA6KpLRrI9FmNKILJ3FRcr +go3J6oR3EuQllwLeUQsBnwfk66d50x6+VPWWcBLYzqhAjwPJh1zOGjtQBqoE1FS6 +oqVTVrljRaiczonOl0boJZ08F5Phbm6dAxk+kMW3HdUQ95765JJrDpEW2q3sUJLB +ZUiY6/aNB+H+HH/vydXhCT0= +-----END PRIVATE KEY----- diff --git a/test/server.pub b/test/server.pub new file mode 100644 index 0000000..197cbc8 --- /dev/null +++ b/test/server.pub @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDnzCCAoegAwIBAgIJAIBYBZUTeK57MA0GCSqGSIb3DQEBBQUAMGYxCzAJBgNV +BAYTAlVTMRAwDgYDVQQIDAdGbG9yaWRhMRMwEQYDVQQHDApIYWxsZW5kYWxlMRow +GAYDVQQKDBFadW1iYSBGaXRuZXNzIExMQzEUMBIGA1UEAwwLKi56dW1iYS5kZXYw +HhcNMTMwNjI2MTYxMDI3WhcNNDAxMTEwMTYxMDI3WjBmMQswCQYDVQQGEwJVUzEQ +MA4GA1UECAwHRmxvcmlkYTETMBEGA1UEBwwKSGFsbGVuZGFsZTEaMBgGA1UECgwR +WnVtYmEgRml0bmVzcyBMTEMxFDASBgNVBAMMCyouenVtYmEuZGV2MIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0q1g30ooCWX0rIgQWl+Q1+gwP3tqtj2q +XSzGatW3KQeS/0VYpMxx6zs+x8MTYyUBa59YzvB0EQjiCzoZzdNR7F72EdBC/2iF +BGZ+1H9K/ciKCg/8EJ/aD3rLB5r8rCSJlx+mXw8O+BftdJMZ3GhZ13CoCR+bNTxx +jbOT3iC3MsjG/MhlZw4FFiGRAKTVAmqgMc9jxbdmjY7YU3Yi0cYxLllp1TF4wHaS +4/gaad0FCV4JZSBk6RxSAj4NRct96o8USCiNeyVQDV3kX6sh1E/cpXt/mPFATHA7 +RX0Yv+xhRShVNPtaXd3dR2vqRxyKwErEiiuwpKIrCqp1dtosJ1zMzQIDAQABo1Aw +TjAdBgNVHQ4EFgQUE3s5Qvvhufq/E/xNp7Td6SR2xO4wHwYDVR0jBBgwFoAUE3s5 +Qvvhufq/E/xNp7Td6SR2xO4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOC +AQEAOhYnOEjgKlg/voQ/i+7gQcnieHbruN6TeI4LebrLipa5LiNwraBlSYqZDMwy +ivAOVo5evd/5SCGPa+zpJkwOeIzjldAXTGTNTryjJUCaPhL9tdGMcFFvRIEz5ZDs +i9istge5PMiTuLYIEVc4hmM2WEEYUBg8IRmFZb+qKb36YOzznlLvKG6e7IaRnMk8 +JWnq6tpMBQ6YfsAeXlG2mS37BsuZiNLogP1NkZ62Ezp5TvI00zBNYaclCC8c4SYs +T16XKzhwSEiHopAgOblKwZK9iZ5t3AAiKZCn0O9ztM8K8thkM96z8+H5/U/dLtZ2 +i5nfgdSB3IFSeNfCyb0mq+Xjdg== +-----END CERTIFICATE----- diff --git a/test/winston-logstash_test.js b/test/winston-logstash_test.js index 4438262..605adb4 100644 --- a/test/winston-logstash_test.js +++ b/test/winston-logstash_test.js @@ -3,6 +3,8 @@ process.env.NODE_ENV = 'test'; var chai = require('chai'), expect = chai.expect, net = require('net'), + tls = require('tls'), + fs = require('fs'), winston = require('winston'), timekeeper = require('timekeeper'), freezed_time = new Date(1330688329321); @@ -24,10 +26,33 @@ describe('winston-logstash transport', function() { return server; } - function createLogger(port) { + function createTestSecureServer(port, options, on_data) { + var serverOptions = { + key: fs.readFileSync(__dirname + '/server.key'), + cert: fs.readFileSync(__dirname + '/server.pub'), + handshakeTimeout: 2000, + requestCert: options.verify ? options.verify : false, + ca: options.verify ? [ fs.readFileSync(__dirname + '/client.pub') ] : [] + }; + var server = tls.createServer(serverOptions, function(socket) { + socket.on('end', function () { }); + socket.on('data', on_data); + }); + server.listen(port, function() {}); + + return server + } + + function createLogger(port, secure) { return new (winston.Logger)({ transports: [ - new (winston.transports.Logstash)( { port: port, node_name: 'test', localhost: 'localhost', pid: 12345 } ) + new (winston.transports.Logstash)({ + port: port, + node_name: 'test', + localhost: 'localhost', + pid: 12345 , + ssl_enable: secure ? true : false + }) ] }); } @@ -78,6 +103,52 @@ describe('winston-logstash transport', function() { }); + describe('with secured logstash server', function() { + var test_server, port = 28777; + + beforeEach(function(done) { + timekeeper.freeze(freezed_time); + done(); + }); + + it('send logs over SSL secured TCP as valid json', function(done) { + var response; + var logger = createLogger(port, true); + var expected = {"stream":"sample","level":"info","message":"hello world"}; + + test_server = createTestSecureServer(port, {}, function (data) { + response = data.toString(); + expect(JSON.parse(response)).to.be.eql(expected); + done(); + }); + + logger.log('info', 'hello world', {stream: 'sample'}); + }); + + it('send logs over SSL secured TCP as valid json with SSL verification', function(done) { + var response; + var logger = createLogger(port, true); + var expected = {"stream":"sample","level":"info","message":"hello world"}; + + test_server = createTestSecureServer(port, { verify: true }, function (data) { + response = data.toString(); + expect(JSON.parse(response)).to.be.eql(expected); + done(); + }); + + logger.log('info', 'hello world', {stream: 'sample'}); + }); + + // Teardown + afterEach(function () { + if (test_server) { + test_server.close(function () {}); + } + timekeeper.reset(); + test_server = null; + }); + }); + describe('without logstash server', function () { var checkSocketStatus = function (retries, logger, done) { var interval = setInterval(function() {