Skip to content

Commit 4680211

Browse files
authoredOct 13, 2021
feat(tls): add TLS profiles for easier configuration (#1441)
1 parent 7a3f78c commit 4680211

File tree

6 files changed

+208
-4
lines changed

6 files changed

+208
-4
lines changed
 

‎README.md

+25-1
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ const redis = new Redis({ enableOfflineQueue: false });
791791

792792
## TLS Options
793793

794-
Redis doesn't support TLS natively, however if the redis server you want to connect to is hosted behind a TLS proxy (e.g. [stunnel](https://www.stunnel.org/)) or is offered by a PaaS service that supports TLS connection (e.g. [Redis Labs](https://redislabs.com/)), you can set the `tls` option:
794+
Redis doesn't support TLS natively, however if the redis server you want to connect to is hosted behind a TLS proxy (e.g. [stunnel](https://www.stunnel.org/)) or is offered by a PaaS service that supports TLS connection (e.g. [Redis.com](https://redis.com/)), you can set the `tls` option:
795795

796796
```javascript
797797
const redis = new Redis({
@@ -811,6 +811,30 @@ Alternatively, specify the connection through a [`rediss://` URL](https://www.ia
811811
const redis = new Redis("rediss://redis.my-service.com");
812812
```
813813

814+
### TLS Profiles
815+
816+
To make it easier to configure we provide a few pre-configured TLS profiles that can be specified by setting the `tls` option to the profile's name or specifying a `tls.profile` option in case you need to customize some values of the profile.
817+
818+
Profiles:
819+
820+
- `RedisCloudFixed`: Contains the CA for [Redis.com](https://redis.com/) Cloud fixed subscriptions
821+
- `RedisCloudFlexible`: Contains the CA for [Redis.com](https://redis.com/) Cloud flexible subscriptions
822+
823+
```javascript
824+
const redis = new Redis({
825+
host: "localhost",
826+
tls: "RedisCloudFixed",
827+
});
828+
829+
const redisWithClientCertificate = new Redis({
830+
host: "localhost",
831+
tls: {
832+
profile: "RedisCloudFixed",
833+
key: "123",
834+
},
835+
});
836+
```
837+
814838
<hr>
815839

816840
## Sentinel

‎lib/cluster/util.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { parseURL } from "../utils";
1+
import { parseURL, resolveTLSProfile } from "../utils";
22
import { isIP } from "net";
33
import { SrvRecord } from "dns";
44

@@ -67,7 +67,7 @@ export function normalizeNodeOptions(
6767
options.host = "127.0.0.1";
6868
}
6969

70-
return options;
70+
return resolveTLSProfile(options);
7171
});
7272
}
7373

‎lib/constants/TLSProfiles.ts

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
export default {
2+
/**
3+
* TLS settings for Redis.com Cloud Fixed plan. Updated on 2021-10-06.
4+
*/
5+
RedisCloudFixed: {
6+
ca:
7+
"-----BEGIN CERTIFICATE-----\n" +
8+
"MIIDTzCCAjegAwIBAgIJAKSVpiDswLcwMA0GCSqGSIb3DQEBBQUAMD4xFjAUBgNV\n" +
9+
"BAoMDUdhcmFudGlhIERhdGExJDAiBgNVBAMMG1NTTCBDZXJ0aWZpY2F0aW9uIEF1\n" +
10+
"dGhvcml0eTAeFw0xMzEwMDExMjE0NTVaFw0yMzA5MjkxMjE0NTVaMD4xFjAUBgNV\n" +
11+
"BAoMDUdhcmFudGlhIERhdGExJDAiBgNVBAMMG1NTTCBDZXJ0aWZpY2F0aW9uIEF1\n" +
12+
"dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALZqkh/DczWP\n" +
13+
"JnxnHLQ7QL0T4B4CDKWBKCcisriGbA6ZePWVNo4hfKQC6JrzfR+081NeD6VcWUiz\n" +
14+
"rmd+jtPhIY4c+WVQYm5PKaN6DT1imYdxQw7aqO5j2KUCEh/cznpLxeSHoTxlR34E\n" +
15+
"QwF28Wl3eg2vc5ct8LjU3eozWVk3gb7alx9mSA2SgmuX5lEQawl++rSjsBStemY2\n" +
16+
"BDwOpAMXIrdEyP/cVn8mkvi/BDs5M5G+09j0gfhyCzRWMQ7Hn71u1eolRxwVxgi3\n" +
17+
"TMn+/vTaFSqxKjgck6zuAYjBRPaHe7qLxHNr1So/Mc9nPy+3wHebFwbIcnUojwbp\n" +
18+
"4nctkWbjb2cCAwEAAaNQME4wHQYDVR0OBBYEFP1whtcrydmW3ZJeuSoKZIKjze3w\n" +
19+
"MB8GA1UdIwQYMBaAFP1whtcrydmW3ZJeuSoKZIKjze3wMAwGA1UdEwQFMAMBAf8w\n" +
20+
"DQYJKoZIhvcNAQEFBQADggEBAG2erXhwRAa7+ZOBs0B6X57Hwyd1R4kfmXcs0rta\n" +
21+
"lbPpvgULSiB+TCbf3EbhJnHGyvdCY1tvlffLjdA7HJ0PCOn+YYLBA0pTU/dyvrN6\n" +
22+
"Su8NuS5yubnt9mb13nDGYo1rnt0YRfxN+8DM3fXIVr038A30UlPX2Ou1ExFJT0MZ\n" +
23+
"uFKY6ZvLdI6/1cbgmguMlAhM+DhKyV6Sr5699LM3zqeI816pZmlREETYkGr91q7k\n" +
24+
"BpXJu/dtHaGxg1ZGu6w/PCsYGUcECWENYD4VQPd8N32JjOfu6vEgoEAwfPP+3oGp\n" +
25+
"Z4m3ewACcWOAenqflb+cQYC4PsF7qbXDmRaWrbKntOlZ3n0=\n" +
26+
"-----END CERTIFICATE-----\n",
27+
},
28+
/**
29+
* TLS settings for Redis.com Cloud Flexible plan. Updated on 2021-10-06.
30+
*/
31+
RedisCloudFlexible: {
32+
ca:
33+
"-----BEGIN CERTIFICATE-----\n" +
34+
"MIIGMTCCBBmgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwajELMAkGA1UEBhMCVVMx\n" +
35+
"CzAJBgNVBAgMAkNBMQswCQYDVQQHDAJDQTESMBAGA1UECgwJUmVkaXNMYWJzMS0w\n" +
36+
"KwYDVQQDDCRSZWRpc0xhYnMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN\n" +
37+
"MTgwMjI1MTUzNzM3WhcNMjgwMjIzMTUzNzM3WjBfMQswCQYDVQQGEwJVUzELMAkG\n" +
38+
"A1UECAwCQ0ExEjAQBgNVBAoMCVJlZGlzTGFiczEvMC0GA1UEAwwmUkNQIEludGVy\n" +
39+
"bWVkaWF0ZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA\n" +
40+
"A4ICDwAwggIKAoICAQDf9dqbxc8Bq7Ctq9rWcxrGNKKHivqLAFpPq02yLPx6fsOv\n" +
41+
"Tq7GsDChAYBBc4v7Y2Ap9RD5Vs3dIhEANcnolf27QwrG9RMnnvzk8pCvp1o6zSU4\n" +
42+
"VuOE1W66/O1/7e2rVxyrnTcP7UgK43zNIXu7+tiAqWsO92uSnuMoGPGpeaUm1jym\n" +
43+
"hjWKtkAwDFSqvHY+XL5qDVBEjeUe+WHkYUg40cAXjusAqgm2hZt29c2wnVrxW25W\n" +
44+
"P0meNlzHGFdA2AC5z54iRiqj57dTfBTkHoBczQxcyw6hhzxZQ4e5I5zOKjXXEhZN\n" +
45+
"r0tA3YC14CTabKRus/JmZieyZzRgEy2oti64tmLYTqSlAD78pRL40VNoaSYetXLw\n" +
46+
"hhNsXCHgWaY6d5bLOc/aIQMAV5oLvZQKvuXAF1IDmhPA+bZbpWipp0zagf1P1H3s\n" +
47+
"UzsMdn2KM0ejzgotbtNlj5TcrVwpmvE3ktvUAuA+hi3FkVx1US+2Gsp5x4YOzJ7u\n" +
48+
"P1WPk6ShF0JgnJH2ILdj6kttTWwFzH17keSFICWDfH/+kM+k7Y1v3EXMQXE7y0T9\n" +
49+
"MjvJskz6d/nv+sQhY04xt64xFMGTnZjlJMzfQNi7zWFLTZnDD0lPowq7l3YiPoTT\n" +
50+
"t5Xky83lu0KZsZBo0WlWaDG00gLVdtRgVbcuSWxpi5BdLb1kRab66JptWjxwXQID\n" +
51+
"AQABo4HrMIHoMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHBzOi8vcmwtY2Etc2VydmVy\n" +
52+
"LnJlZGlzbGFicy5jb20vdjEvY3JsMEYGCCsGAQUFBwEBBDowODA2BggrBgEFBQcw\n" +
53+
"AYYqaHR0cHM6Ly9ybC1jYS1zZXJ2ZXIucmVkaXNsYWJzLmNvbS92MS9vY3NwMB0G\n" +
54+
"A1UdDgQWBBQHar5OKvQUpP2qWt6mckzToeCOHDAfBgNVHSMEGDAWgBQi42wH6hM4\n" +
55+
"L2sujEvLM0/u8lRXTzASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIB\n" +
56+
"hjANBgkqhkiG9w0BAQsFAAOCAgEAirEn/iTsAKyhd+pu2W3Z5NjCko4NPU0EYUbr\n" +
57+
"AP7+POK2rzjIrJO3nFYQ/LLuC7KCXG+2qwan2SAOGmqWst13Y+WHp44Kae0kaChW\n" +
58+
"vcYLXXSoGQGC8QuFSNUdaeg3RbMDYFT04dOkqufeWVccoHVxyTSg9eD8LZuHn5jw\n" +
59+
"7QDLiEECBmIJHk5Eeo2TAZrx4Yx6ufSUX5HeVjlAzqwtAqdt99uCJ/EL8bgpWbe+\n" +
60+
"XoSpvUv0SEC1I1dCAhCKAvRlIOA6VBcmzg5Am12KzkqTul12/VEFIgzqu0Zy2Jbc\n" +
61+
"AUPrYVu/+tOGXQaijy7YgwH8P8n3s7ZeUa1VABJHcxrxYduDDJBLZi+MjheUDaZ1\n" +
62+
"jQRHYevI2tlqeSBqdPKG4zBY5lS0GiAlmuze5oENt0P3XboHoZPHiqcK3VECgTVh\n" +
63+
"/BkJcuudETSJcZDmQ8YfoKfBzRQNg2sv/hwvUv73Ss51Sco8GEt2lD8uEdib1Q6z\n" +
64+
"zDT5lXJowSzOD5ZA9OGDjnSRL+2riNtKWKEqvtEG3VBJoBzu9GoxbAc7wIZLxmli\n" +
65+
"iF5a/Zf5X+UXD3s4TMmy6C4QZJpAA2egsSQCnraWO2ULhh7iXMysSkF/nzVfZn43\n" +
66+
"iqpaB8++9a37hWq14ZmOv0TJIDz//b2+KC4VFXWQ5W5QC6whsjT+OlG4p5ZYG0jo\n" +
67+
"616pxqo=\n" +
68+
"-----END CERTIFICATE-----\n" +
69+
"-----BEGIN CERTIFICATE-----\n" +
70+
"MIIFujCCA6KgAwIBAgIJAJ1aTT1lu2ScMA0GCSqGSIb3DQEBCwUAMGoxCzAJBgNV\n" +
71+
"BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCQ0ExEjAQBgNVBAoMCVJlZGlz\n" +
72+
"TGFiczEtMCsGA1UEAwwkUmVkaXNMYWJzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9y\n" +
73+
"aXR5MB4XDTE4MDIyNTE1MjA0MloXDTM4MDIyMDE1MjA0MlowajELMAkGA1UEBhMC\n" +
74+
"VVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJDQTESMBAGA1UECgwJUmVkaXNMYWJz\n" +
75+
"MS0wKwYDVQQDDCRSZWRpc0xhYnMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw\n" +
76+
"ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLEjXy7YrbN5Waau5cd6g1\n" +
77+
"G5C2tMmeTpZ0duFAPxNU4oE3RHS5gGiok346fUXuUxbZ6QkuzeN2/2Z+RmRcJhQY\n" +
78+
"Dm0ZgdG4x59An1TJfnzKKoWj8ISmoHS/TGNBdFzXV7FYNLBuqZouqePI6ReC6Qhl\n" +
79+
"pp45huV32Q3a6IDrrvx7Wo5ZczEQeFNbCeCOQYNDdTmCyEkHqc2AGo8eoIlSTutT\n" +
80+
"ULOC7R5gzJVTS0e1hesQ7jmqHjbO+VQS1NAL4/5K6cuTEqUl+XhVhPdLWBXJQ5ag\n" +
81+
"54qhX4v+ojLzeU1R/Vc6NjMvVtptWY6JihpgplprN0Yh2556ewcXMeturcKgXfGJ\n" +
82+
"xeYzsjzXerEjrVocX5V8BNrg64NlifzTMKNOOv4fVZszq1SIHR8F9ROrqiOdh8iC\n" +
83+
"JpUbLpXH9hWCSEO6VRMB2xJoKu3cgl63kF30s77x7wLFMEHiwsQRKxooE1UhgS9K\n" +
84+
"2sO4TlQ1eWUvFvHSTVDQDlGQ6zu4qjbOpb3Q8bQwoK+ai2alkXVR4Ltxe9QlgYK3\n" +
85+
"StsnPhruzZGA0wbXdpw0bnM+YdlEm5ffSTpNIfgHeaa7Dtb801FtA71ZlH7A6TaI\n" +
86+
"SIQuUST9EKmv7xrJyx0W1pGoPOLw5T029aTjnICSLdtV9bLwysrLhIYG5bnPq78B\n" +
87+
"cS+jZHFGzD7PUVGQD01nOQIDAQABo2MwYTAdBgNVHQ4EFgQUIuNsB+oTOC9rLoxL\n" +
88+
"yzNP7vJUV08wHwYDVR0jBBgwFoAUIuNsB+oTOC9rLoxLyzNP7vJUV08wDwYDVR0T\n" +
89+
"AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBAHfg\n" +
90+
"z5pMNUAKdMzK1aS1EDdK9yKz4qicILz5czSLj1mC7HKDRy8cVADUxEICis++CsCu\n" +
91+
"rYOvyCVergHQLREcxPq4rc5Nq1uj6J6649NEeh4WazOOjL4ZfQ1jVznMbGy+fJm3\n" +
92+
"3Hoelv6jWRG9iqeJZja7/1s6YC6bWymI/OY1e4wUKeNHAo+Vger7MlHV+RuabaX+\n" +
93+
"hSJ8bJAM59NCM7AgMTQpJCncrcdLeceYniGy5Q/qt2b5mJkQVkIdy4TPGGB+AXDJ\n" +
94+
"D0q3I/JDRkDUFNFdeW0js7fHdsvCR7O3tJy5zIgEV/o/BCkmJVtuwPYOrw/yOlKj\n" +
95+
"TY/U7ATAx9VFF6/vYEOMYSmrZlFX+98L6nJtwDqfLB5VTltqZ4H/KBxGE3IRSt9l\n" +
96+
"FXy40U+LnXzhhW+7VBAvyYX8GEXhHkKU8Gqk1xitrqfBXY74xKgyUSTolFSfFVgj\n" +
97+
"mcM/X4K45bka+qpkj7Kfv/8D4j6aZekwhN2ly6hhC1SmQ8qjMjpG/mrWOSSHZFmf\n" +
98+
"ybu9iD2AYHeIOkshIl6xYIa++Q/00/vs46IzAbQyriOi0XxlSMMVtPx0Q3isp+ji\n" +
99+
"n8Mq9eOuxYOEQ4of8twUkUDd528iwGtEdwf0Q01UyT84S62N8AySl1ZBKXJz6W4F\n" +
100+
"UhWfa/HQYOAPDdEjNgnVwLI23b8t0TozyCWw7q8h\n" +
101+
"-----END CERTIFICATE-----\n",
102+
},
103+
};

‎lib/redis/index.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ import { EventEmitter } from "events";
44
import Deque = require("denque");
55
import Command from "../command";
66
import Commander from "../commander";
7-
import { isInt, CONNECTION_CLOSED_ERROR_MSG, parseURL, Debug } from "../utils";
7+
import {
8+
isInt,
9+
CONNECTION_CLOSED_ERROR_MSG,
10+
parseURL,
11+
Debug,
12+
resolveTLSProfile,
13+
} from "../utils";
814
import asCallback from "standard-as-callback";
915
import * as eventHandler from "./event_handler";
1016
import { StandaloneConnector, SentinelConnector } from "../connectors";
@@ -262,6 +268,8 @@ Redis.prototype.parseOptions = function () {
262268
"Hiredis parser is abandoned since ioredis v3.0, and JavaScript parser will be used"
263269
);
264270
}
271+
272+
this.options = resolveTLSProfile(this.options);
265273
};
266274

267275
/**

‎lib/utils/index.ts

+24
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { parse as urllibParse } from "url";
22
import { defaults, noop, flatten } from "./lodash";
33
import Debug from "./debug";
44

5+
import TLSProfiles from "../constants/TLSProfiles";
6+
57
/**
68
* Test if two buffers are equal
79
*
@@ -289,6 +291,28 @@ export function parseURL(url) {
289291
return result;
290292
}
291293

294+
/**
295+
* Resolve TLS profile shortcut in connection options
296+
*
297+
* @param {Object} options - the redis connection options
298+
* @return {Object}
299+
*/
300+
export function resolveTLSProfile(options) {
301+
let tls = options?.tls;
302+
303+
if (typeof tls === "string") tls = { profile: tls };
304+
305+
const profile = TLSProfiles[tls?.profile];
306+
307+
if (profile) {
308+
tls = Object.assign({}, profile, tls);
309+
delete tls.profile;
310+
options = Object.assign({}, options, { tls });
311+
}
312+
313+
return options;
314+
}
315+
292316
/**
293317
* Get a random element from `array`
294318
*

‎test/unit/utils.ts

+45
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as sinon from "sinon";
22
import { expect } from "chai";
33
import * as utils from "../../lib/utils";
4+
import TLSProfiles from "../../lib/constants/TLSProfiles";
45

56
describe("utils", function () {
67
describe(".bufferEqual", function () {
@@ -279,6 +280,50 @@ describe("utils", function () {
279280
});
280281
});
281282

283+
describe(".resolveTLSProfile", function () {
284+
it("should leave options alone when no tls profile is set", function () {
285+
[
286+
{},
287+
{ tls: true },
288+
{ tls: false },
289+
{ tls: "foo" },
290+
{ tls: {} },
291+
{ tls: { ca: "foo" } },
292+
{ tls: { profile: "foo" } },
293+
].forEach((options) => {
294+
expect(utils.resolveTLSProfile(options)).to.eql(options);
295+
});
296+
});
297+
298+
it("should have redis.com profiles defined", function () {
299+
expect(TLSProfiles).to.have.property("RedisCloudFixed");
300+
expect(TLSProfiles).to.have.property("RedisCloudFlexible");
301+
});
302+
303+
it("should read profile from options.tls.profile", function () {
304+
const input = { tls: { profile: "RedisCloudFixed" } };
305+
const expected = { tls: TLSProfiles.RedisCloudFixed };
306+
307+
expect(utils.resolveTLSProfile(input)).to.eql(expected);
308+
});
309+
310+
it("should read profile from options.tls", function () {
311+
const input = { tls: "RedisCloudFixed" };
312+
const expected = { tls: TLSProfiles.RedisCloudFixed };
313+
314+
expect(utils.resolveTLSProfile(input)).to.eql(expected);
315+
});
316+
317+
it("supports extra options when using options.tls.profile", function () {
318+
const input = { tls: { profile: "RedisCloudFixed", key: "foo" } };
319+
const expected = {
320+
tls: { ...TLSProfiles.RedisCloudFixed, key: "foo" },
321+
};
322+
323+
expect(utils.resolveTLSProfile(input)).to.eql(expected);
324+
});
325+
});
326+
282327
describe(".sample", function () {
283328
it("should return a random value", function () {
284329
let stub = sinon.stub(Math, "random").callsFake(() => 0);

0 commit comments

Comments
 (0)
Please sign in to comment.