You've already forked node-redis
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:
committed by
Ruben Bridgewater
parent
eae5596a3c
commit
1fa9f15ae4
4
test/conf/.gitignore
vendored
Normal file
4
test/conf/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
stunnel.conf
|
||||
stunnel.log
|
||||
stunnel.pid
|
||||
|
19
test/conf/redis.js.org.cert
Normal file
19
test/conf/redis.js.org.cert
Normal 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-----
|
27
test/conf/redis.js.org.key
Normal file
27
test/conf/redis.js.org.key
Normal 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-----
|
12
test/conf/stunnel.conf.template
Normal file
12
test/conf/stunnel.conf.template
Normal 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
|
@@ -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);
|
||||
|
83
test/lib/stunnel-process.js
Normal file
83
test/lib/stunnel-process.js
Normal 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
201
test/tls.spec.js
Normal 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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user