diff --git a/README.md b/README.md
index 06b13d49..e784a0ad 100644
--- a/README.md
+++ b/README.md
@@ -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({
@@ -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",
+ },
+});
+```
+
## Sentinel
diff --git a/lib/cluster/util.ts b/lib/cluster/util.ts
index 8a83f2df..1f24ca41 100644
--- a/lib/cluster/util.ts
+++ b/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";
@@ -67,7 +67,7 @@ export function normalizeNodeOptions(
options.host = "127.0.0.1";
}
- return options;
+ return resolveTLSProfile(options);
});
}
diff --git a/lib/constants/TLSProfiles.ts b/lib/constants/TLSProfiles.ts
new file mode 100644
index 00000000..0d72d10a
--- /dev/null
+++ b/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" +
+ "MIIFujCCA6KgAwIBAgIJAJ1aTT1lu2ScMA0GCSqGSIb3DQEBCwUAMGoxCzAJBgNV\n" +
+ "BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCQ0ExEjAQBgNVBAoMCVJlZGlz\n" +
+ "TGFiczEtMCsGA1UEAwwkUmVkaXNMYWJzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9y\n" +
+ "aXR5MB4XDTE4MDIyNTE1MjA0MloXDTM4MDIyMDE1MjA0MlowajELMAkGA1UEBhMC\n" +
+ "VVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJDQTESMBAGA1UECgwJUmVkaXNMYWJz\n" +
+ "MS0wKwYDVQQDDCRSZWRpc0xhYnMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw\n" +
+ "ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLEjXy7YrbN5Waau5cd6g1\n" +
+ "G5C2tMmeTpZ0duFAPxNU4oE3RHS5gGiok346fUXuUxbZ6QkuzeN2/2Z+RmRcJhQY\n" +
+ "Dm0ZgdG4x59An1TJfnzKKoWj8ISmoHS/TGNBdFzXV7FYNLBuqZouqePI6ReC6Qhl\n" +
+ "pp45huV32Q3a6IDrrvx7Wo5ZczEQeFNbCeCOQYNDdTmCyEkHqc2AGo8eoIlSTutT\n" +
+ "ULOC7R5gzJVTS0e1hesQ7jmqHjbO+VQS1NAL4/5K6cuTEqUl+XhVhPdLWBXJQ5ag\n" +
+ "54qhX4v+ojLzeU1R/Vc6NjMvVtptWY6JihpgplprN0Yh2556ewcXMeturcKgXfGJ\n" +
+ "xeYzsjzXerEjrVocX5V8BNrg64NlifzTMKNOOv4fVZszq1SIHR8F9ROrqiOdh8iC\n" +
+ "JpUbLpXH9hWCSEO6VRMB2xJoKu3cgl63kF30s77x7wLFMEHiwsQRKxooE1UhgS9K\n" +
+ "2sO4TlQ1eWUvFvHSTVDQDlGQ6zu4qjbOpb3Q8bQwoK+ai2alkXVR4Ltxe9QlgYK3\n" +
+ "StsnPhruzZGA0wbXdpw0bnM+YdlEm5ffSTpNIfgHeaa7Dtb801FtA71ZlH7A6TaI\n" +
+ "SIQuUST9EKmv7xrJyx0W1pGoPOLw5T029aTjnICSLdtV9bLwysrLhIYG5bnPq78B\n" +
+ "cS+jZHFGzD7PUVGQD01nOQIDAQABo2MwYTAdBgNVHQ4EFgQUIuNsB+oTOC9rLoxL\n" +
+ "yzNP7vJUV08wHwYDVR0jBBgwFoAUIuNsB+oTOC9rLoxLyzNP7vJUV08wDwYDVR0T\n" +
+ "AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBAHfg\n" +
+ "z5pMNUAKdMzK1aS1EDdK9yKz4qicILz5czSLj1mC7HKDRy8cVADUxEICis++CsCu\n" +
+ "rYOvyCVergHQLREcxPq4rc5Nq1uj6J6649NEeh4WazOOjL4ZfQ1jVznMbGy+fJm3\n" +
+ "3Hoelv6jWRG9iqeJZja7/1s6YC6bWymI/OY1e4wUKeNHAo+Vger7MlHV+RuabaX+\n" +
+ "hSJ8bJAM59NCM7AgMTQpJCncrcdLeceYniGy5Q/qt2b5mJkQVkIdy4TPGGB+AXDJ\n" +
+ "D0q3I/JDRkDUFNFdeW0js7fHdsvCR7O3tJy5zIgEV/o/BCkmJVtuwPYOrw/yOlKj\n" +
+ "TY/U7ATAx9VFF6/vYEOMYSmrZlFX+98L6nJtwDqfLB5VTltqZ4H/KBxGE3IRSt9l\n" +
+ "FXy40U+LnXzhhW+7VBAvyYX8GEXhHkKU8Gqk1xitrqfBXY74xKgyUSTolFSfFVgj\n" +
+ "mcM/X4K45bka+qpkj7Kfv/8D4j6aZekwhN2ly6hhC1SmQ8qjMjpG/mrWOSSHZFmf\n" +
+ "ybu9iD2AYHeIOkshIl6xYIa++Q/00/vs46IzAbQyriOi0XxlSMMVtPx0Q3isp+ji\n" +
+ "n8Mq9eOuxYOEQ4of8twUkUDd528iwGtEdwf0Q01UyT84S62N8AySl1ZBKXJz6W4F\n" +
+ "UhWfa/HQYOAPDdEjNgnVwLI23b8t0TozyCWw7q8h\n" +
+ "-----END CERTIFICATE-----\n",
+ },
+};
diff --git a/lib/redis/index.ts b/lib/redis/index.ts
index e5bf5b53..64542c59 100644
--- a/lib/redis/index.ts
+++ b/lib/redis/index.ts
@@ -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";
@@ -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);
};
/**
diff --git a/lib/utils/index.ts b/lib/utils/index.ts
index 6ba975f5..df0f4f37 100644
--- a/lib/utils/index.ts
+++ b/lib/utils/index.ts
@@ -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
*
@@ -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`
*
diff --git a/test/unit/utils.ts b/test/unit/utils.ts
index cdffd1f1..728be0dd 100644
--- a/test/unit/utils.ts
+++ b/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 () {
@@ -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);