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