1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-06 02:15:48 +03:00

Add tests for TLS connections

This commit is contained in:
Paddy Byers
2015-10-19 17:17:21 +01:00
committed by Ruben Bridgewater
parent eae5596a3c
commit 1fa9f15ae4
7 changed files with 365 additions and 0 deletions

4
test/conf/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
stunnel.conf
stunnel.log
stunnel.pid

View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDATCCAemgAwIBAgIJALkMmVkQOERnMA0GCSqGSIb3DQEBBQUAMBcxFTATBgNV
BAMMDHJlZGlzLmpzLm9yZzAeFw0xNTEwMTkxMjIzMjRaFw0yNTEwMTYxMjIzMjRa
MBcxFTATBgNVBAMMDHJlZGlzLmpzLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAJ/DmMTJHf7kyspxI1A/JmOc+KI9vxEcN5qn7IiZuGN7ghE43Q3q
XB2GUkMAuW1POkmM5yi3SuT1UXDR/4Gk7KlbHKMs37AV6PgJXX6oX0zu12LTAT7V
5byNrYtehSo42l1188dGEMCGaaf0cDntc7A3aW0ZtzrJt+2pu31Uatl2SEJCMra6
+v6O0c9aHMF1cArKeawGqR+jHw6vXFZQbUd05nW5nQlUA6wVt1JjlLPwBwYsWLsi
YQxMC8NqpgAIg5tULSCpKwx5isL/CeotVVGDNZ/G8R1nTrxuygPlc3Qskj57hmV4
tZK4JJxQFi7/9ehvjAvHohKrEPeqV5XL87cCAwEAAaNQME4wHQYDVR0OBBYEFCn/
5hB+XY4pVOnaqvrmZMxrLFjLMB8GA1UdIwQYMBaAFCn/5hB+XY4pVOnaqvrmZMxr
LFjLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEduPyTHpXkCVZRQ
v6p+Ug4iVeXpxGCVr34y7EDUMgmuDdqsz1SrmqeDd0VmjZT8htbWw7QBKDPEBsbi
wl606aAn01iM+oUrwbtXxid1xfZj/j6pIhQVkGu7e/8A7Pr4QOP4OMdHB7EmqkAo
d/OLHa9LdKv2UtJHD6U7oVQbdBHrRV62125GMmotpQuSkEfZM6edKNzHPlqV/zJc
2kGCw3lZC21mTrsSMIC/FQiobPnig4kAvfh0of2rK/XAntlwT8ie1v1aK+jERsfm
uzMihl6XXBdzheq6KdIlf+5STHBIIRcvBoRKr5Va7EhnO03tTzeJowtqDv47yPC6
w4kLcP8=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAn8OYxMkd/uTKynEjUD8mY5z4oj2/ERw3mqfsiJm4Y3uCETjd
DepcHYZSQwC5bU86SYznKLdK5PVRcNH/gaTsqVscoyzfsBXo+AldfqhfTO7XYtMB
PtXlvI2ti16FKjjaXXXzx0YQwIZpp/RwOe1zsDdpbRm3Osm37am7fVRq2XZIQkIy
trr6/o7Rz1ocwXVwCsp5rAapH6MfDq9cVlBtR3TmdbmdCVQDrBW3UmOUs/AHBixY
uyJhDEwLw2qmAAiDm1QtIKkrDHmKwv8J6i1VUYM1n8bxHWdOvG7KA+VzdCySPnuG
ZXi1krgknFAWLv/16G+MC8eiEqsQ96pXlcvztwIDAQABAoIBAGx4kLCLHCKDlGv+
hMtnFNltKiJ9acxkLByFBsN4GwjwQk8PHIbmJ8Sj/hYf18WvlRN65zdtuxvYs4K2
EZQkNcqGYdsoDHexaIt/UEs+ZfYF85bVTHMtJt3uE3Ycpq0UDK6H9wvFNnqAyBuQ
iuHJplJuTNYWL6Fqc8aZBwMA3crmwWTelgS+IXLH06E298+KIxbYrWSgrbcmV/Pj
Iwek4CPS0apoJnXxbZDDhAEYGOTxDNXGm+r7BaX/ePM2x1PPib2X9F2XqFV+A4T8
Z91axKJwMrVuTrJkaLPDx9lNUskvvV6KgjZAtYRGpLQTN1AqXJZ09IoK9sNPE4rX
9fm4awECgYEAzMJkABL0UOoGJhdRf/R0aUOQMO7vYetX5SK9QXcEI04XYFieSaPm
71st+R/JlJ+LhrTrzGXvyU0tFAQaQZtwaGj/JhbptIpLlGrVf3mqSvxkNi/wzQnn
jBJrrf1ZkDiqtSy7AxGAefWblgK3R1ZU5+0a5jubDkmOltIlbULf0skCgYEAx76l
+5KhWOJPvrjNGB1a8oVXiFzoCpaVVZIhSdl0AtvkKollm5Ou+CKYpE3fKrejRXTD
zmr5bJFXT3VlmIa010cgXJ2btlFa1RiNzgretsOmMcHxLkpAu2/a0L4psHlCrWVK
fxbUW0BYEFVXBDe/4JhFw41YqohdPkFAyo5OUn8CgYBQZGYkzUxVVHzTicY66bym
85ryS217UY5x7WDHCjZ6shdlgYWsPgjWo0L6k+tuSfHbEr+dwcwSihWPzUiNx7yr
kcXTq51YgA/KluN6KEefJ1clG099AU2C5lyWtGjswgLsHULTopSBzdenXyuce53c
bXBpQq/PPTwZpSqCqoX8WQKBgGe+nsk+jGz1BoRBycyHmrAyD5e04ZR2R9PtFTsd
JYNCoIxzVoHqv8sDdRKJm6q9PKEbl4PDzg7UomuTxxPki1LxD17rQW/9a1cY7LYi
sTBuCAj5+YGYcWypGRaoXlDZeodC/+Fogx1uGw9Is+xt5EwL6tg5tt7D+uIV1Egg
h4+TAoGBAKYA/jn9v93bzPi+w1rlZrlPufRSr4k3mcHae165N/1PnjSguTFIF5DW
+1f5S+XioNyTcfx5gKI8f6wRn1j5zbB24GXgu8dXCzRHC2gzrwq2D9v1od4zP/o7
xFxyiNGOMUJ7uW9d/nEL5Eg4CQKZEkZNmzHhuKNr8wDSr16DhXVK
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,12 @@
pid = __dirname/stunnel.pid
output = __dirname/stunnel.log
CAfile = __dirname/redis.js.org.cert
cert = __dirname/redis.js.org.cert
key = __dirname/redis.js.org.key
client = no
foreground = yes
libwrap = no
debug = 7
[redis]
accept = 127.0.0.1:6380
connect = 127.0.0.1:6379

View File

@@ -4,7 +4,9 @@ var assert = require("assert");
var path = require('path');
var config = require("./lib/config");
var RedisProcess = require("./lib/redis-process");
var StunnelProcess = require("./lib/stunnel-process");
var rp;
var stunnel_process;
function startRedis (conf, done) {
RedisProcess.start(function (err, _rp) {
@@ -13,6 +15,21 @@ function startRedis (conf, done) {
}, path.resolve(__dirname, conf));
}
function startStunnel(done) {
StunnelProcess.start(function (err, _stunnel_process) {
stunnel_process = _stunnel_process;
return done(err);
}, path.resolve(__dirname, './conf'));
}
function stopStunnel(done) {
if(stunnel_process) {
StunnelProcess.stop(stunnel_process, done);
} else {
done();
}
}
// don't start redis every time we
// include this helper file!
if (!process.env.REDIS_TESTS_STARTED) {
@@ -35,6 +52,8 @@ module.exports = {
rp.stop(done);
},
startRedis: startRedis,
stopStunnel: stopStunnel,
startStunnel: startStunnel,
isNumber: function (expected, done) {
return function (err, results) {
assert.strictEqual(null, err, "expected " + expected + ", got error: " + err);

View File

@@ -0,0 +1,83 @@
'use strict';
// helper to start and stop the stunnel process.
var spawn = require('child_process').spawn;
var EventEmitter = require('events').EventEmitter;
var fs = require('fs');
var path = require('path');
var util = require('util');
function once(cb) {
var called = false;
return function() {
if(called) return;
called = true;
cb.apply(this, arguments);
};
}
function StunnelProcess(conf_dir) {
EventEmitter.call(this);
// set up an stunnel to redis; edit the conf file to include required absolute paths
var conf_file = path.resolve(conf_dir, 'stunnel.conf');
var conf_text = fs.readFileSync(conf_file + '.template').toString().replace(/__dirname/g, conf_dir);
fs.writeFileSync(conf_file, conf_text);
var stunnel = this.stunnel = spawn('stunnel', [conf_file]);
// handle child process events, and failure to set up tunnel
var self = this;
this.timer = setTimeout(function() {
self.emit('error', new Error('Timeout waiting for stunnel to start'));
}, 8000);
stunnel.on('error', function(err) {
self.clear();
self.emit('error', err);
});
stunnel.on('exit', function(code) {
self.clear();
if(code === 0) {
self.emit('stopped');
} else {
self.emit('error', new Error('Stunnel exited unexpectedly; code = ' + code));
}
});
// wait to stunnel to start
stunnel.stderr.on("data", function(data) {
if(data.toString().match(/Service.+redis.+bound/)) {
clearTimeout(this.timer);
self.emit('started');
}
});
}
util.inherits(StunnelProcess, EventEmitter);
StunnelProcess.prototype.clear = function() {
this.stunnel = null;
clearTimeout(this.timer);
};
StunnelProcess.prototype.stop = function(done) {
if(this.stunnel) {
this.stunnel.kill();
}
};
module.exports = {
start: function(done, conf_dir) {
done = once(done);
var stunnel = new StunnelProcess(conf_dir);
stunnel.once('error', done.bind(done));
stunnel.once('started', done.bind(done, null, stunnel));
},
stop: function(stunnel, done) {
stunnel.removeAllListeners();
stunnel.stop();
stunnel.once('error', done.bind(done));
stunnel.once('stopped', done.bind(done, null));
}
};

201
test/tls.spec.js Normal file
View File

@@ -0,0 +1,201 @@
'use strict';
var assert = require("assert");
var config = require("./lib/config");
var fs = require('fs');
var helper = require('./helper');
var path = require('path');
var redis = config.redis;
var tls_options = {
servername: "redis.js.org",
rejectUnauthorized: false,
ca: [ String(fs.readFileSync(path.resolve(__dirname, "./conf/redis.js.org.cert"))) ]
};
var tls_port = 6380;
describe("TLS connection tests", function () {
before(function (done) {
helper.stopStunnel(function () {
helper.startStunnel(done);
});
});
after(function (done) {
helper.stopStunnel(done);
});
helper.allTests(function(parser, ip, args) {
describe("using " + parser + " and " + ip, function () {
var client;
afterEach(function () {
if (client) {
client.end();
}
});
describe("on lost connection", function () {
it("emit an error after max retry attempts and do not try to reconnect afterwards", function (done) {
var max_attempts = 4;
var options = {
parser: parser,
max_attempts: max_attempts,
port: tls_port,
tls: tls_options
};
client = redis.createClient(options);
var calls = 0;
client.once('ready', function() {
helper.killConnection(client);
});
client.on("reconnecting", function (params) {
calls++;
});
client.on('error', function(err) {
if (/Redis connection in broken state: maximum connection attempts.*?exceeded./.test(err.message)) {
setTimeout(function () {
assert.strictEqual(calls, max_attempts - 1);
done();
}, 500);
}
});
});
it("emit an error after max retry timeout and do not try to reconnect afterwards", function (done) {
var connect_timeout = 500; // in ms
client = redis.createClient({
parser: parser,
connect_timeout: connect_timeout,
port: tls_port,
tls: tls_options
});
var time = 0;
client.once('ready', function() {
helper.killConnection(client);
});
client.on("reconnecting", function (params) {
time += params.delay;
});
client.on('error', function(err) {
if (/Redis connection in broken state: connection timeout.*?exceeded./.test(err.message)) {
setTimeout(function () {
assert(time === connect_timeout);
done();
}, 500);
}
});
});
it("end connection while retry is still ongoing", function (done) {
var connect_timeout = 1000; // in ms
client = redis.createClient({
parser: parser,
connect_timeout: connect_timeout,
port: tls_port,
tls: tls_options
});
client.once('ready', function() {
helper.killConnection(client);
});
client.on("reconnecting", function (params) {
client.end();
setTimeout(done, 100);
});
});
it("can not connect with wrong host / port in the options object", function (done) {
var options = {
host: 'somewhere',
max_attempts: 1,
port: tls_port,
tls: tls_options
};
client = redis.createClient(options);
var end = helper.callFuncAfter(done, 2);
client.on('error', function (err) {
assert(/CONNECTION_BROKEN|ENOTFOUND|EAI_AGAIN/.test(err.code));
end();
});
});
});
describe("when not connected", function () {
it("connect with host and port provided in the options object", function (done) {
client = redis.createClient({
host: 'localhost',
parser: parser,
connect_timeout: 1000,
port: tls_port,
tls: tls_options
});
client.once('ready', function() {
done();
});
});
it("connects correctly with args", function (done) {
var args_host = args[1];
var args_options = args[2] || {};
args_options.tls = tls_options;
client = redis.createClient(tls_port, args_host, args_options);
client.on("error", done);
client.once("ready", function () {
client.removeListener("error", done);
client.get("recon 1", function (err, res) {
done(err);
});
});
});
if (ip === 'IPv4') {
it('allows connecting with the redis url and no auth and options as second parameter', function (done) {
var options = {
detect_buffers: false,
magic: Math.random(),
port: tls_port,
tls: tls_options
};
client = redis.createClient('redis://' + config.HOST[ip] + ':' + tls_port, options);
// verify connection is using TCP, not UNIX socket
assert.strictEqual(client.connection_options.host, config.HOST[ip]);
assert.strictEqual(client.connection_options.port, tls_port);
// verify passed options are in use
assert.strictEqual(client.options.magic, options.magic);
client.on("ready", function () {
return done();
});
});
it('allows connecting with the redis url and no auth and options as third parameter', function (done) {
client = redis.createClient('redis://' + config.HOST[ip] + ':' + tls_port, null, {
detect_buffers: false,
tls: tls_options
});
client.on("ready", function () {
return done();
});
});
}
});
});
});
});