Skip to content

Commit 1fa9f15

Browse files
paddybyersRuben Bridgewater
authored andcommitted
Add tests for TLS connections
1 parent eae5596 commit 1fa9f15

File tree

7 files changed

+365
-0
lines changed

7 files changed

+365
-0
lines changed

test/conf/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
stunnel.conf
2+
stunnel.log
3+
stunnel.pid
4+

test/conf/redis.js.org.cert

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDATCCAemgAwIBAgIJALkMmVkQOERnMA0GCSqGSIb3DQEBBQUAMBcxFTATBgNV
3+
BAMMDHJlZGlzLmpzLm9yZzAeFw0xNTEwMTkxMjIzMjRaFw0yNTEwMTYxMjIzMjRa
4+
MBcxFTATBgNVBAMMDHJlZGlzLmpzLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEP
5+
ADCCAQoCggEBAJ/DmMTJHf7kyspxI1A/JmOc+KI9vxEcN5qn7IiZuGN7ghE43Q3q
6+
XB2GUkMAuW1POkmM5yi3SuT1UXDR/4Gk7KlbHKMs37AV6PgJXX6oX0zu12LTAT7V
7+
5byNrYtehSo42l1188dGEMCGaaf0cDntc7A3aW0ZtzrJt+2pu31Uatl2SEJCMra6
8+
+v6O0c9aHMF1cArKeawGqR+jHw6vXFZQbUd05nW5nQlUA6wVt1JjlLPwBwYsWLsi
9+
YQxMC8NqpgAIg5tULSCpKwx5isL/CeotVVGDNZ/G8R1nTrxuygPlc3Qskj57hmV4
10+
tZK4JJxQFi7/9ehvjAvHohKrEPeqV5XL87cCAwEAAaNQME4wHQYDVR0OBBYEFCn/
11+
5hB+XY4pVOnaqvrmZMxrLFjLMB8GA1UdIwQYMBaAFCn/5hB+XY4pVOnaqvrmZMxr
12+
LFjLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEduPyTHpXkCVZRQ
13+
v6p+Ug4iVeXpxGCVr34y7EDUMgmuDdqsz1SrmqeDd0VmjZT8htbWw7QBKDPEBsbi
14+
wl606aAn01iM+oUrwbtXxid1xfZj/j6pIhQVkGu7e/8A7Pr4QOP4OMdHB7EmqkAo
15+
d/OLHa9LdKv2UtJHD6U7oVQbdBHrRV62125GMmotpQuSkEfZM6edKNzHPlqV/zJc
16+
2kGCw3lZC21mTrsSMIC/FQiobPnig4kAvfh0of2rK/XAntlwT8ie1v1aK+jERsfm
17+
uzMihl6XXBdzheq6KdIlf+5STHBIIRcvBoRKr5Va7EhnO03tTzeJowtqDv47yPC6
18+
w4kLcP8=
19+
-----END CERTIFICATE-----

test/conf/redis.js.org.key

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEowIBAAKCAQEAn8OYxMkd/uTKynEjUD8mY5z4oj2/ERw3mqfsiJm4Y3uCETjd
3+
DepcHYZSQwC5bU86SYznKLdK5PVRcNH/gaTsqVscoyzfsBXo+AldfqhfTO7XYtMB
4+
PtXlvI2ti16FKjjaXXXzx0YQwIZpp/RwOe1zsDdpbRm3Osm37am7fVRq2XZIQkIy
5+
trr6/o7Rz1ocwXVwCsp5rAapH6MfDq9cVlBtR3TmdbmdCVQDrBW3UmOUs/AHBixY
6+
uyJhDEwLw2qmAAiDm1QtIKkrDHmKwv8J6i1VUYM1n8bxHWdOvG7KA+VzdCySPnuG
7+
ZXi1krgknFAWLv/16G+MC8eiEqsQ96pXlcvztwIDAQABAoIBAGx4kLCLHCKDlGv+
8+
hMtnFNltKiJ9acxkLByFBsN4GwjwQk8PHIbmJ8Sj/hYf18WvlRN65zdtuxvYs4K2
9+
EZQkNcqGYdsoDHexaIt/UEs+ZfYF85bVTHMtJt3uE3Ycpq0UDK6H9wvFNnqAyBuQ
10+
iuHJplJuTNYWL6Fqc8aZBwMA3crmwWTelgS+IXLH06E298+KIxbYrWSgrbcmV/Pj
11+
Iwek4CPS0apoJnXxbZDDhAEYGOTxDNXGm+r7BaX/ePM2x1PPib2X9F2XqFV+A4T8
12+
Z91axKJwMrVuTrJkaLPDx9lNUskvvV6KgjZAtYRGpLQTN1AqXJZ09IoK9sNPE4rX
13+
9fm4awECgYEAzMJkABL0UOoGJhdRf/R0aUOQMO7vYetX5SK9QXcEI04XYFieSaPm
14+
71st+R/JlJ+LhrTrzGXvyU0tFAQaQZtwaGj/JhbptIpLlGrVf3mqSvxkNi/wzQnn
15+
jBJrrf1ZkDiqtSy7AxGAefWblgK3R1ZU5+0a5jubDkmOltIlbULf0skCgYEAx76l
16+
+5KhWOJPvrjNGB1a8oVXiFzoCpaVVZIhSdl0AtvkKollm5Ou+CKYpE3fKrejRXTD
17+
zmr5bJFXT3VlmIa010cgXJ2btlFa1RiNzgretsOmMcHxLkpAu2/a0L4psHlCrWVK
18+
fxbUW0BYEFVXBDe/4JhFw41YqohdPkFAyo5OUn8CgYBQZGYkzUxVVHzTicY66bym
19+
85ryS217UY5x7WDHCjZ6shdlgYWsPgjWo0L6k+tuSfHbEr+dwcwSihWPzUiNx7yr
20+
kcXTq51YgA/KluN6KEefJ1clG099AU2C5lyWtGjswgLsHULTopSBzdenXyuce53c
21+
bXBpQq/PPTwZpSqCqoX8WQKBgGe+nsk+jGz1BoRBycyHmrAyD5e04ZR2R9PtFTsd
22+
JYNCoIxzVoHqv8sDdRKJm6q9PKEbl4PDzg7UomuTxxPki1LxD17rQW/9a1cY7LYi
23+
sTBuCAj5+YGYcWypGRaoXlDZeodC/+Fogx1uGw9Is+xt5EwL6tg5tt7D+uIV1Egg
24+
h4+TAoGBAKYA/jn9v93bzPi+w1rlZrlPufRSr4k3mcHae165N/1PnjSguTFIF5DW
25+
+1f5S+XioNyTcfx5gKI8f6wRn1j5zbB24GXgu8dXCzRHC2gzrwq2D9v1od4zP/o7
26+
xFxyiNGOMUJ7uW9d/nEL5Eg4CQKZEkZNmzHhuKNr8wDSr16DhXVK
27+
-----END RSA PRIVATE KEY-----

test/conf/stunnel.conf.template

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
pid = __dirname/stunnel.pid
2+
output = __dirname/stunnel.log
3+
CAfile = __dirname/redis.js.org.cert
4+
cert = __dirname/redis.js.org.cert
5+
key = __dirname/redis.js.org.key
6+
client = no
7+
foreground = yes
8+
libwrap = no
9+
debug = 7
10+
[redis]
11+
accept = 127.0.0.1:6380
12+
connect = 127.0.0.1:6379

test/helper.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ var assert = require("assert");
44
var path = require('path');
55
var config = require("./lib/config");
66
var RedisProcess = require("./lib/redis-process");
7+
var StunnelProcess = require("./lib/stunnel-process");
78
var rp;
9+
var stunnel_process;
810

911
function startRedis (conf, done) {
1012
RedisProcess.start(function (err, _rp) {
@@ -13,6 +15,21 @@ function startRedis (conf, done) {
1315
}, path.resolve(__dirname, conf));
1416
}
1517

18+
function startStunnel(done) {
19+
StunnelProcess.start(function (err, _stunnel_process) {
20+
stunnel_process = _stunnel_process;
21+
return done(err);
22+
}, path.resolve(__dirname, './conf'));
23+
}
24+
25+
function stopStunnel(done) {
26+
if(stunnel_process) {
27+
StunnelProcess.stop(stunnel_process, done);
28+
} else {
29+
done();
30+
}
31+
}
32+
1633
// don't start redis every time we
1734
// include this helper file!
1835
if (!process.env.REDIS_TESTS_STARTED) {
@@ -35,6 +52,8 @@ module.exports = {
3552
rp.stop(done);
3653
},
3754
startRedis: startRedis,
55+
stopStunnel: stopStunnel,
56+
startStunnel: startStunnel,
3857
isNumber: function (expected, done) {
3958
return function (err, results) {
4059
assert.strictEqual(null, err, "expected " + expected + ", got error: " + err);

test/lib/stunnel-process.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
'use strict';
2+
3+
// helper to start and stop the stunnel process.
4+
var spawn = require('child_process').spawn;
5+
var EventEmitter = require('events').EventEmitter;
6+
var fs = require('fs');
7+
var path = require('path');
8+
var util = require('util');
9+
10+
function once(cb) {
11+
var called = false;
12+
return function() {
13+
if(called) return;
14+
called = true;
15+
cb.apply(this, arguments);
16+
};
17+
}
18+
19+
function StunnelProcess(conf_dir) {
20+
EventEmitter.call(this);
21+
22+
// set up an stunnel to redis; edit the conf file to include required absolute paths
23+
var conf_file = path.resolve(conf_dir, 'stunnel.conf');
24+
var conf_text = fs.readFileSync(conf_file + '.template').toString().replace(/__dirname/g, conf_dir);
25+
26+
fs.writeFileSync(conf_file, conf_text);
27+
var stunnel = this.stunnel = spawn('stunnel', [conf_file]);
28+
29+
// handle child process events, and failure to set up tunnel
30+
var self = this;
31+
this.timer = setTimeout(function() {
32+
self.emit('error', new Error('Timeout waiting for stunnel to start'));
33+
}, 8000);
34+
35+
stunnel.on('error', function(err) {
36+
self.clear();
37+
self.emit('error', err);
38+
});
39+
40+
stunnel.on('exit', function(code) {
41+
self.clear();
42+
if(code === 0) {
43+
self.emit('stopped');
44+
} else {
45+
self.emit('error', new Error('Stunnel exited unexpectedly; code = ' + code));
46+
}
47+
});
48+
49+
// wait to stunnel to start
50+
stunnel.stderr.on("data", function(data) {
51+
if(data.toString().match(/Service.+redis.+bound/)) {
52+
clearTimeout(this.timer);
53+
self.emit('started');
54+
}
55+
});
56+
}
57+
util.inherits(StunnelProcess, EventEmitter);
58+
59+
StunnelProcess.prototype.clear = function() {
60+
this.stunnel = null;
61+
clearTimeout(this.timer);
62+
};
63+
64+
StunnelProcess.prototype.stop = function(done) {
65+
if(this.stunnel) {
66+
this.stunnel.kill();
67+
}
68+
};
69+
70+
module.exports = {
71+
start: function(done, conf_dir) {
72+
done = once(done);
73+
var stunnel = new StunnelProcess(conf_dir);
74+
stunnel.once('error', done.bind(done));
75+
stunnel.once('started', done.bind(done, null, stunnel));
76+
},
77+
stop: function(stunnel, done) {
78+
stunnel.removeAllListeners();
79+
stunnel.stop();
80+
stunnel.once('error', done.bind(done));
81+
stunnel.once('stopped', done.bind(done, null));
82+
}
83+
};

test/tls.spec.js

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
'use strict';
2+
3+
var assert = require("assert");
4+
var config = require("./lib/config");
5+
var fs = require('fs');
6+
var helper = require('./helper');
7+
var path = require('path');
8+
var redis = config.redis;
9+
10+
var tls_options = {
11+
servername: "redis.js.org",
12+
rejectUnauthorized: false,
13+
ca: [ String(fs.readFileSync(path.resolve(__dirname, "./conf/redis.js.org.cert"))) ]
14+
};
15+
16+
var tls_port = 6380;
17+
18+
describe("TLS connection tests", function () {
19+
before(function (done) {
20+
helper.stopStunnel(function () {
21+
helper.startStunnel(done);
22+
});
23+
});
24+
25+
after(function (done) {
26+
helper.stopStunnel(done);
27+
});
28+
29+
helper.allTests(function(parser, ip, args) {
30+
31+
describe("using " + parser + " and " + ip, function () {
32+
33+
var client;
34+
35+
afterEach(function () {
36+
if (client) {
37+
client.end();
38+
}
39+
});
40+
41+
describe("on lost connection", function () {
42+
it("emit an error after max retry attempts and do not try to reconnect afterwards", function (done) {
43+
var max_attempts = 4;
44+
var options = {
45+
parser: parser,
46+
max_attempts: max_attempts,
47+
port: tls_port,
48+
tls: tls_options
49+
};
50+
client = redis.createClient(options);
51+
var calls = 0;
52+
53+
client.once('ready', function() {
54+
helper.killConnection(client);
55+
});
56+
57+
client.on("reconnecting", function (params) {
58+
calls++;
59+
});
60+
61+
client.on('error', function(err) {
62+
if (/Redis connection in broken state: maximum connection attempts.*?exceeded./.test(err.message)) {
63+
setTimeout(function () {
64+
assert.strictEqual(calls, max_attempts - 1);
65+
done();
66+
}, 500);
67+
}
68+
});
69+
});
70+
71+
it("emit an error after max retry timeout and do not try to reconnect afterwards", function (done) {
72+
var connect_timeout = 500; // in ms
73+
client = redis.createClient({
74+
parser: parser,
75+
connect_timeout: connect_timeout,
76+
port: tls_port,
77+
tls: tls_options
78+
});
79+
var time = 0;
80+
81+
client.once('ready', function() {
82+
helper.killConnection(client);
83+
});
84+
85+
client.on("reconnecting", function (params) {
86+
time += params.delay;
87+
});
88+
89+
client.on('error', function(err) {
90+
if (/Redis connection in broken state: connection timeout.*?exceeded./.test(err.message)) {
91+
setTimeout(function () {
92+
assert(time === connect_timeout);
93+
done();
94+
}, 500);
95+
}
96+
});
97+
});
98+
99+
it("end connection while retry is still ongoing", function (done) {
100+
var connect_timeout = 1000; // in ms
101+
client = redis.createClient({
102+
parser: parser,
103+
connect_timeout: connect_timeout,
104+
port: tls_port,
105+
tls: tls_options
106+
});
107+
108+
client.once('ready', function() {
109+
helper.killConnection(client);
110+
});
111+
112+
client.on("reconnecting", function (params) {
113+
client.end();
114+
setTimeout(done, 100);
115+
});
116+
});
117+
118+
it("can not connect with wrong host / port in the options object", function (done) {
119+
var options = {
120+
host: 'somewhere',
121+
max_attempts: 1,
122+
port: tls_port,
123+
tls: tls_options
124+
};
125+
client = redis.createClient(options);
126+
var end = helper.callFuncAfter(done, 2);
127+
128+
client.on('error', function (err) {
129+
assert(/CONNECTION_BROKEN|ENOTFOUND|EAI_AGAIN/.test(err.code));
130+
end();
131+
});
132+
133+
});
134+
});
135+
136+
describe("when not connected", function () {
137+
138+
it("connect with host and port provided in the options object", function (done) {
139+
client = redis.createClient({
140+
host: 'localhost',
141+
parser: parser,
142+
connect_timeout: 1000,
143+
port: tls_port,
144+
tls: tls_options
145+
});
146+
147+
client.once('ready', function() {
148+
done();
149+
});
150+
});
151+
152+
it("connects correctly with args", function (done) {
153+
var args_host = args[1];
154+
var args_options = args[2] || {};
155+
args_options.tls = tls_options;
156+
client = redis.createClient(tls_port, args_host, args_options);
157+
client.on("error", done);
158+
159+
client.once("ready", function () {
160+
client.removeListener("error", done);
161+
client.get("recon 1", function (err, res) {
162+
done(err);
163+
});
164+
});
165+
});
166+
167+
if (ip === 'IPv4') {
168+
it('allows connecting with the redis url and no auth and options as second parameter', function (done) {
169+
var options = {
170+
detect_buffers: false,
171+
magic: Math.random(),
172+
port: tls_port,
173+
tls: tls_options
174+
};
175+
client = redis.createClient('redis://' + config.HOST[ip] + ':' + tls_port, options);
176+
// verify connection is using TCP, not UNIX socket
177+
assert.strictEqual(client.connection_options.host, config.HOST[ip]);
178+
assert.strictEqual(client.connection_options.port, tls_port);
179+
// verify passed options are in use
180+
assert.strictEqual(client.options.magic, options.magic);
181+
client.on("ready", function () {
182+
return done();
183+
});
184+
});
185+
186+
it('allows connecting with the redis url and no auth and options as third parameter', function (done) {
187+
client = redis.createClient('redis://' + config.HOST[ip] + ':' + tls_port, null, {
188+
detect_buffers: false,
189+
tls: tls_options
190+
});
191+
client.on("ready", function () {
192+
return done();
193+
});
194+
});
195+
}
196+
197+
});
198+
199+
});
200+
});
201+
});

0 commit comments

Comments
 (0)