Skip to content

Commit

Permalink
feat(tls): add TLS profiles for easier configuration (#1441)
Browse files Browse the repository at this point in the history
  • Loading branch information
GertSallaerts committed Oct 13, 2021
1 parent 7a3f78c commit 4680211
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 4 deletions.
26 changes: 25 additions & 1 deletion README.md
Expand Up @@ -791,7 +791,7 @@ const redis = new Redis({ enableOfflineQueue: false });

## TLS Options

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:
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:

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

### TLS Profiles

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.

Profiles:

- `RedisCloudFixed`: Contains the CA for [Redis.com](https://redis.com/) Cloud fixed subscriptions
- `RedisCloudFlexible`: Contains the CA for [Redis.com](https://redis.com/) Cloud flexible subscriptions

```javascript
const redis = new Redis({
host: "localhost",
tls: "RedisCloudFixed",
});

const redisWithClientCertificate = new Redis({
host: "localhost",
tls: {
profile: "RedisCloudFixed",
key: "123",
},
});
```

<hr>

## Sentinel
Expand Down
4 changes: 2 additions & 2 deletions lib/cluster/util.ts
@@ -1,4 +1,4 @@
import { parseURL } from "../utils";
import { parseURL, resolveTLSProfile } from "../utils";
import { isIP } from "net";
import { SrvRecord } from "dns";

Expand Down Expand Up @@ -67,7 +67,7 @@ export function normalizeNodeOptions(
options.host = "127.0.0.1";
}

return options;
return resolveTLSProfile(options);
});
}

Expand Down
103 changes: 103 additions & 0 deletions lib/constants/TLSProfiles.ts
@@ -0,0 +1,103 @@
export default {
/**
* TLS settings for Redis.com Cloud Fixed plan. Updated on 2021-10-06.
*/
RedisCloudFixed: {
ca:
"-----BEGIN CERTIFICATE-----\n" +
"MIIDTzCCAjegAwIBAgIJAKSVpiDswLcwMA0GCSqGSIb3DQEBBQUAMD4xFjAUBgNV\n" +
"BAoMDUdhcmFudGlhIERhdGExJDAiBgNVBAMMG1NTTCBDZXJ0aWZpY2F0aW9uIEF1\n" +
"dGhvcml0eTAeFw0xMzEwMDExMjE0NTVaFw0yMzA5MjkxMjE0NTVaMD4xFjAUBgNV\n" +
"BAoMDUdhcmFudGlhIERhdGExJDAiBgNVBAMMG1NTTCBDZXJ0aWZpY2F0aW9uIEF1\n" +
"dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALZqkh/DczWP\n" +
"JnxnHLQ7QL0T4B4CDKWBKCcisriGbA6ZePWVNo4hfKQC6JrzfR+081NeD6VcWUiz\n" +
"rmd+jtPhIY4c+WVQYm5PKaN6DT1imYdxQw7aqO5j2KUCEh/cznpLxeSHoTxlR34E\n" +
"QwF28Wl3eg2vc5ct8LjU3eozWVk3gb7alx9mSA2SgmuX5lEQawl++rSjsBStemY2\n" +
"BDwOpAMXIrdEyP/cVn8mkvi/BDs5M5G+09j0gfhyCzRWMQ7Hn71u1eolRxwVxgi3\n" +
"TMn+/vTaFSqxKjgck6zuAYjBRPaHe7qLxHNr1So/Mc9nPy+3wHebFwbIcnUojwbp\n" +
"4nctkWbjb2cCAwEAAaNQME4wHQYDVR0OBBYEFP1whtcrydmW3ZJeuSoKZIKjze3w\n" +
"MB8GA1UdIwQYMBaAFP1whtcrydmW3ZJeuSoKZIKjze3wMAwGA1UdEwQFMAMBAf8w\n" +
"DQYJKoZIhvcNAQEFBQADggEBAG2erXhwRAa7+ZOBs0B6X57Hwyd1R4kfmXcs0rta\n" +
"lbPpvgULSiB+TCbf3EbhJnHGyvdCY1tvlffLjdA7HJ0PCOn+YYLBA0pTU/dyvrN6\n" +
"Su8NuS5yubnt9mb13nDGYo1rnt0YRfxN+8DM3fXIVr038A30UlPX2Ou1ExFJT0MZ\n" +
"uFKY6ZvLdI6/1cbgmguMlAhM+DhKyV6Sr5699LM3zqeI816pZmlREETYkGr91q7k\n" +
"BpXJu/dtHaGxg1ZGu6w/PCsYGUcECWENYD4VQPd8N32JjOfu6vEgoEAwfPP+3oGp\n" +
"Z4m3ewACcWOAenqflb+cQYC4PsF7qbXDmRaWrbKntOlZ3n0=\n" +
"-----END CERTIFICATE-----\n",
},
/**
* TLS settings for Redis.com Cloud Flexible plan. Updated on 2021-10-06.
*/
RedisCloudFlexible: {
ca:
"-----BEGIN CERTIFICATE-----\n" +
"MIIGMTCCBBmgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwajELMAkGA1UEBhMCVVMx\n" +
"CzAJBgNVBAgMAkNBMQswCQYDVQQHDAJDQTESMBAGA1UECgwJUmVkaXNMYWJzMS0w\n" +
"KwYDVQQDDCRSZWRpc0xhYnMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN\n" +
"MTgwMjI1MTUzNzM3WhcNMjgwMjIzMTUzNzM3WjBfMQswCQYDVQQGEwJVUzELMAkG\n" +
"A1UECAwCQ0ExEjAQBgNVBAoMCVJlZGlzTGFiczEvMC0GA1UEAwwmUkNQIEludGVy\n" +
"bWVkaWF0ZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA\n" +
"A4ICDwAwggIKAoICAQDf9dqbxc8Bq7Ctq9rWcxrGNKKHivqLAFpPq02yLPx6fsOv\n" +
"Tq7GsDChAYBBc4v7Y2Ap9RD5Vs3dIhEANcnolf27QwrG9RMnnvzk8pCvp1o6zSU4\n" +
"VuOE1W66/O1/7e2rVxyrnTcP7UgK43zNIXu7+tiAqWsO92uSnuMoGPGpeaUm1jym\n" +
"hjWKtkAwDFSqvHY+XL5qDVBEjeUe+WHkYUg40cAXjusAqgm2hZt29c2wnVrxW25W\n" +
"P0meNlzHGFdA2AC5z54iRiqj57dTfBTkHoBczQxcyw6hhzxZQ4e5I5zOKjXXEhZN\n" +
"r0tA3YC14CTabKRus/JmZieyZzRgEy2oti64tmLYTqSlAD78pRL40VNoaSYetXLw\n" +
"hhNsXCHgWaY6d5bLOc/aIQMAV5oLvZQKvuXAF1IDmhPA+bZbpWipp0zagf1P1H3s\n" +
"UzsMdn2KM0ejzgotbtNlj5TcrVwpmvE3ktvUAuA+hi3FkVx1US+2Gsp5x4YOzJ7u\n" +
"P1WPk6ShF0JgnJH2ILdj6kttTWwFzH17keSFICWDfH/+kM+k7Y1v3EXMQXE7y0T9\n" +
"MjvJskz6d/nv+sQhY04xt64xFMGTnZjlJMzfQNi7zWFLTZnDD0lPowq7l3YiPoTT\n" +
"t5Xky83lu0KZsZBo0WlWaDG00gLVdtRgVbcuSWxpi5BdLb1kRab66JptWjxwXQID\n" +
"AQABo4HrMIHoMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHBzOi8vcmwtY2Etc2VydmVy\n" +
"LnJlZGlzbGFicy5jb20vdjEvY3JsMEYGCCsGAQUFBwEBBDowODA2BggrBgEFBQcw\n" +
"AYYqaHR0cHM6Ly9ybC1jYS1zZXJ2ZXIucmVkaXNsYWJzLmNvbS92MS9vY3NwMB0G\n" +
"A1UdDgQWBBQHar5OKvQUpP2qWt6mckzToeCOHDAfBgNVHSMEGDAWgBQi42wH6hM4\n" +
"L2sujEvLM0/u8lRXTzASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIB\n" +
"hjANBgkqhkiG9w0BAQsFAAOCAgEAirEn/iTsAKyhd+pu2W3Z5NjCko4NPU0EYUbr\n" +
"AP7+POK2rzjIrJO3nFYQ/LLuC7KCXG+2qwan2SAOGmqWst13Y+WHp44Kae0kaChW\n" +
"vcYLXXSoGQGC8QuFSNUdaeg3RbMDYFT04dOkqufeWVccoHVxyTSg9eD8LZuHn5jw\n" +
"7QDLiEECBmIJHk5Eeo2TAZrx4Yx6ufSUX5HeVjlAzqwtAqdt99uCJ/EL8bgpWbe+\n" +
"XoSpvUv0SEC1I1dCAhCKAvRlIOA6VBcmzg5Am12KzkqTul12/VEFIgzqu0Zy2Jbc\n" +
"AUPrYVu/+tOGXQaijy7YgwH8P8n3s7ZeUa1VABJHcxrxYduDDJBLZi+MjheUDaZ1\n" +
"jQRHYevI2tlqeSBqdPKG4zBY5lS0GiAlmuze5oENt0P3XboHoZPHiqcK3VECgTVh\n" +
"/BkJcuudETSJcZDmQ8YfoKfBzRQNg2sv/hwvUv73Ss51Sco8GEt2lD8uEdib1Q6z\n" +
"zDT5lXJowSzOD5ZA9OGDjnSRL+2riNtKWKEqvtEG3VBJoBzu9GoxbAc7wIZLxmli\n" +
"iF5a/Zf5X+UXD3s4TMmy6C4QZJpAA2egsSQCnraWO2ULhh7iXMysSkF/nzVfZn43\n" +
"iqpaB8++9a37hWq14ZmOv0TJIDz//b2+KC4VFXWQ5W5QC6whsjT+OlG4p5ZYG0jo\n" +
"616pxqo=\n" +
"-----END CERTIFICATE-----\n" +
"-----BEGIN CERTIFICATE-----\n" +
"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\n" +
"-----END CERTIFICATE-----\n",
},
};
10 changes: 9 additions & 1 deletion lib/redis/index.ts
Expand Up @@ -4,7 +4,13 @@ import { EventEmitter } from "events";
import Deque = require("denque");
import Command from "../command";
import Commander from "../commander";
import { isInt, CONNECTION_CLOSED_ERROR_MSG, parseURL, Debug } from "../utils";
import {
isInt,
CONNECTION_CLOSED_ERROR_MSG,
parseURL,
Debug,
resolveTLSProfile,
} from "../utils";
import asCallback from "standard-as-callback";
import * as eventHandler from "./event_handler";
import { StandaloneConnector, SentinelConnector } from "../connectors";
Expand Down Expand Up @@ -262,6 +268,8 @@ Redis.prototype.parseOptions = function () {
"Hiredis parser is abandoned since ioredis v3.0, and JavaScript parser will be used"
);
}

this.options = resolveTLSProfile(this.options);
};

/**
Expand Down
24 changes: 24 additions & 0 deletions lib/utils/index.ts
Expand Up @@ -2,6 +2,8 @@ import { parse as urllibParse } from "url";
import { defaults, noop, flatten } from "./lodash";
import Debug from "./debug";

import TLSProfiles from "../constants/TLSProfiles";

/**
* Test if two buffers are equal
*
Expand Down Expand Up @@ -289,6 +291,28 @@ export function parseURL(url) {
return result;
}

/**
* Resolve TLS profile shortcut in connection options
*
* @param {Object} options - the redis connection options
* @return {Object}
*/
export function resolveTLSProfile(options) {
let tls = options?.tls;

if (typeof tls === "string") tls = { profile: tls };

const profile = TLSProfiles[tls?.profile];

if (profile) {
tls = Object.assign({}, profile, tls);
delete tls.profile;
options = Object.assign({}, options, { tls });
}

return options;
}

/**
* Get a random element from `array`
*
Expand Down
45 changes: 45 additions & 0 deletions test/unit/utils.ts
@@ -1,6 +1,7 @@
import * as sinon from "sinon";
import { expect } from "chai";
import * as utils from "../../lib/utils";
import TLSProfiles from "../../lib/constants/TLSProfiles";

describe("utils", function () {
describe(".bufferEqual", function () {
Expand Down Expand Up @@ -279,6 +280,50 @@ describe("utils", function () {
});
});

describe(".resolveTLSProfile", function () {
it("should leave options alone when no tls profile is set", function () {
[
{},
{ tls: true },
{ tls: false },
{ tls: "foo" },
{ tls: {} },
{ tls: { ca: "foo" } },
{ tls: { profile: "foo" } },
].forEach((options) => {
expect(utils.resolveTLSProfile(options)).to.eql(options);
});
});

it("should have redis.com profiles defined", function () {
expect(TLSProfiles).to.have.property("RedisCloudFixed");
expect(TLSProfiles).to.have.property("RedisCloudFlexible");
});

it("should read profile from options.tls.profile", function () {
const input = { tls: { profile: "RedisCloudFixed" } };
const expected = { tls: TLSProfiles.RedisCloudFixed };

expect(utils.resolveTLSProfile(input)).to.eql(expected);
});

it("should read profile from options.tls", function () {
const input = { tls: "RedisCloudFixed" };
const expected = { tls: TLSProfiles.RedisCloudFixed };

expect(utils.resolveTLSProfile(input)).to.eql(expected);
});

it("supports extra options when using options.tls.profile", function () {
const input = { tls: { profile: "RedisCloudFixed", key: "foo" } };
const expected = {
tls: { ...TLSProfiles.RedisCloudFixed, key: "foo" },
};

expect(utils.resolveTLSProfile(input)).to.eql(expected);
});
});

describe(".sample", function () {
it("should return a random value", function () {
let stub = sinon.stub(Math, "random").callsFake(() => 0);
Expand Down

0 comments on commit 4680211

Please sign in to comment.