From 4be7532e1a12bdbbfa433189d0587de256f0d449 Mon Sep 17 00:00:00 2001 From: Qu Chen Date: Sun, 23 Jul 2023 15:41:31 -0700 Subject: [PATCH] Add TLS support. --- CHANGELOG.md | 10 +- README.md | 1 + packages/memcache-client/README.md | 39 +++++- .../memcache-client/src/lib/connection.ts | 14 ++- .../memcache-client/src/test/spec/cacert.pem | 24 ++++ .../src/test/spec/client_crt.pem | 76 ++++++++++++ .../src/test/spec/client_key.pem | 27 +++++ .../src/test/spec/client_tls.spec.ts | 114 ++++++++++++++++++ .../src/test/spec/server_crt.pem | 76 ++++++++++++ .../src/test/spec/server_key.pem | 27 +++++ packages/memcache-client/src/types.ts | 2 + .../memcached-njs/src/lib/memcached-server.ts | 12 +- 12 files changed, 416 insertions(+), 6 deletions(-) create mode 100644 packages/memcache-client/src/test/spec/cacert.pem create mode 100644 packages/memcache-client/src/test/spec/client_crt.pem create mode 100644 packages/memcache-client/src/test/spec/client_key.pem create mode 100644 packages/memcache-client/src/test/spec/client_tls.spec.ts create mode 100644 packages/memcache-client/src/test/spec/server_crt.pem create mode 100644 packages/memcache-client/src/test/spec/server_key.pem diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b7c1ab..3a4f63a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log. +## (2023-07-24) + +### Features + +* **memcache-client:** + * add TLS support which allows secure connection to a remote memcached server with TLS enabled + * add new client test cases for TLS + ## (2022-09-07) ### Features @@ -22,4 +30,4 @@ Updated versions in `package.json` for both **memcache-client** and **memcache-p **Things to improve** -There can be some `any`s in either code base or test files, those should be changed to real types. typescript should be as typed as possible \ No newline at end of file +There can be some `any`s in either code base or test files, those should be changed to real types. typescript should be as typed as possible diff --git a/README.md b/README.md index 678ce73..dd67b69 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ This repo uses [lerna](https://lernajs.io/) to manage multiple packages. 3. run `npm ci` 4. run `npm run bootstrap` 5. optional: just to make sure everything is fine run `npm run build` and check if there's no error using that command +6. Testing: run `npm test` ## Publishing notes - Recommended publish flow is to publish the modules individually using `npm publish`, can be improved to use lerna in the future diff --git a/packages/memcache-client/README.md b/packages/memcache-client/README.md index f276f31..b13d434 100644 --- a/packages/memcache-client/README.md +++ b/packages/memcache-client/README.md @@ -167,7 +167,8 @@ const options = { connectTimeout: 8000, // connect to server timeout in ms compressor: require("custom-compressor"), logger: require("./custom-logger"), - Promise + Promise, + tls: {} }; const client = new MemcacheClient(options); @@ -192,6 +193,7 @@ const client = new MemcacheClient(options); - `compressor` - **_optional_** a custom compressor for compressing the data. See [data compression](#data-compression) for more details. - `logger` - **_optional_** Custom logger like this: - `Promise` - **_optional_** Internally this module will try to find `bluebird` in your `node_modules` and fallback to `global.Promise`. You can set this option to force the Promise to use. +- `tls` - **_optional_** If set, defines the TLS options to make the client connect to server in TLS mode ```js module.exports = { @@ -269,6 +271,41 @@ You can also pass in `server.config` with the following options: - `failedServerOutTime` - (ms) how long a failed server should be out before retrying it. Default 60000 ms (1 min). - `keepLastServer` - (boolean) Keep at least one server even if it failed connection. Default `true`. +### TLS / SSL + +If the memcached server is configured with TLS, you can make the client connect to it via specifying the `tls` ConnectionOptions. + +For production environments, the server should be using a TLS certificate that is signed by a trusted public CA. In +this case you can simply do the following to create the client: + +```js +const client = new MemcacheClient({server: "{server_hostname}:11211", tls: {}}); +client.set("key", "value"); +``` + +If the server requires client certificate authentication, you can do the following: + +```js +import Fs from "fs"; +const client = new MemcacheClient({server: "{server_hostname}:11211", tls: { + key: Fs.readFileSync("client-key.pem"), + cert: Fs.readFileSync("client-cert.pem"), +}}); +client.set("key", "value"); +``` + +If you are running the server with a self-signed certificate (i.e. for local developments), you can create the client +by specifying the CA certificate and disable hostname verification as follows: + +```js +import Fs from "fs"; +const client = new MemcacheClient({server: "localhost:11211", tls: { + ca: Fs.readFileSync("ca-cert.pem"), + checkServerIdentity: () => {return undefined;} +}}); +client.set("key", "value"); +``` + ### Data Compression The client supports automatic compression/decompression of the data you set. It's turned off by default. diff --git a/packages/memcache-client/src/lib/connection.ts b/packages/memcache-client/src/lib/connection.ts index 2bca4c7..14cc875 100644 --- a/packages/memcache-client/src/lib/connection.ts +++ b/packages/memcache-client/src/lib/connection.ts @@ -1,4 +1,5 @@ import Net, { Socket } from "net"; +import Tls from "tls"; import assert from "assert"; import { optionalRequire } from "optional-require"; @@ -103,7 +104,18 @@ export class MemcacheConnection extends MemcacheParser { assert(host, "Must provide server hostname"); assert(typeof port === "number" && port > 0, "Must provide valid server port"); - const socket = Net.createConnection({ host, port }); + let socket: Net.Socket | Tls.TLSSocket; + if (this.client?.options?.tls !== undefined) { + // Create a TLS connection + socket = Tls.connect({ + host: host, + port: port, + ...this.client.options.tls + }); + } else { + // Create a regular TCP connection + socket = Net.createConnection({ host, port }); + } this._connectPromise = new Promise((resolve: ResolveCallback, reject: RejectCallback) => { this._status = Status.CONNECTING; diff --git a/packages/memcache-client/src/test/spec/cacert.pem b/packages/memcache-client/src/test/spec/cacert.pem new file mode 100644 index 0000000..68c0cbb --- /dev/null +++ b/packages/memcache-client/src/test/spec/cacert.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID8DCCAtigAwIBAgIJAOCrnjDZoOG/MA0GCSqGSIb3DQEBCwUAMIGLMSgwJgYD +VQQDDB9UZXN0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQswCQYDVQQIDAJD +QTELMAkGA1UEBhMCVVMxHDAaBgkqhkiG9w0BCQEWDXJvb3RAdGVzdC5jb20xDTAL +BgNVBAoMBFRlc3QxGDAWBgNVBAsMD1Rlc3QgRGVwYXJ0bWVudDAgFw0yMDEwMjcw +MzA0NTVaGA8yNTIwMDYyODAzMDQ1NVowgYsxKDAmBgNVBAMMH1Rlc3QgUm9vdCBD +ZXJ0aWZpY2F0ZSBBdXRob3JpdHkxCzAJBgNVBAgMAkNBMQswCQYDVQQGEwJVUzEc +MBoGCSqGSIb3DQEJARYNcm9vdEB0ZXN0LmNvbTENMAsGA1UECgwEVGVzdDEYMBYG +A1UECwwPVGVzdCBEZXBhcnRtZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAvz238GzKt1UpaxgIxv76hlty5hCZKmD5B952MCWBRi7OyYatl0sMjv38 +pgsICIT68wGqQaTmV4W0XQQ54+NZ3o5ujJaDQX2kLs8x0sjuNkmt5Wt+orqdxO99 +lb7f4hpKyGq0OGeqz4nBHCZeqEt37OLxUgGWQtKGVZI0X/m/m4LudmWwZpLgAp0w +5mY+FLaPavLoXQ95+f3htUzgRdzsFWMXi0piH/D4ua2i+ekajf8owxmR9AIKpAYY +oV1J+XexlzVzXG1OPUEkGvbxnoDIHUoL6IM4i/QEI52UL0b+sVVV0fmJw08mkN95 +XhW+OhS52lSBdqiC3/uRRF/g8/5nowIDAQABo1MwUTAdBgNVHQ4EFgQUcqmERx2+ +c9d5Qd04od22AYhwY/AwHwYDVR0jBBgwFoAUcqmERx2+c9d5Qd04od22AYhwY/Aw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAFYdGLXwXTU4p/CIF +bVRzS+RTEMc6cwd/CC+q0iVZT5Yfhu+MvdlaLMEimYTO44qu32hZHO6ovvSoSNJ1 +fofv5crljHyyQY8GoZIq2jm7DcUyWJLuanqIcVXM9o0xR2CdOhvvDo/M0vRpR6EB +ASZ7uV+w/tuc05YLdQOsXJyiXk80H7emJ0bRl1m/H2wxZRnL2jN2Iz1xeY8tGiP0 +UU9x0hNS7eNzC1IdRUGVcfstyXznKs37F0ZyNHF1TOTZT80mdJxn0D1alVLqkiN3 +RsIZpo1aZ0SShdtIx6luPU2wmGo5kLL2wN6Md18XSHViGA9dx2Mz00GxxDbiQl+F +6qzVDA== +-----END CERTIFICATE----- diff --git a/packages/memcache-client/src/test/spec/client_crt.pem b/packages/memcache-client/src/test/spec/client_crt.pem new file mode 100644 index 0000000..e1f5697 --- /dev/null +++ b/packages/memcache-client/src/test/spec/client_crt.pem @@ -0,0 +1,76 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 78:f7:9b:75:a7:9d:83:12:a5:c7:a0:b6:af:87:da:c3:d9:32:c2:d7 + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN = Test Root Certificate Authority, ST = CA, C = US, emailAddress = root@test.com, O = Test, OU = Test Department + Validity + Not Before: Oct 27 03:10:04 2020 GMT + Not After : Jun 28 03:10:04 2520 GMT + Subject: C = US, ST = CA, O = Test Client, OU = Subunit of Test Organization, CN = client.test.com, emailAddress = root@client.test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:d5:66:c9:c9:ef:64:ad:0f:c7:fe:74:fb:e4:75: + b3:76:60:7f:bb:5e:38:e9:25:e4:70:8c:99:63:ad: + 76:26:b8:06:65:f5:0e:b6:6f:22:66:b7:4b:7a:8c: + 4f:3a:fb:0e:ac:c1:0e:6e:76:db:b5:95:83:d4:a2: + 86:eb:78:63:c5:d3:47:97:d9:6e:0f:39:ee:cd:51: + b6:af:cd:01:2c:a0:c0:bb:58:eb:23:6a:aa:09:e3: + 69:31:2e:83:49:88:42:71:7d:25:aa:f1:63:5e:93: + a5:44:19:47:b2:3b:1c:7a:f1:f2:24:75:51:6f:13: + 7e:9a:24:2c:42:e3:23:31:b6:dd:fa:50:36:92:ef: + c3:c2:40:fe:45:b9:80:79:d9:64:62:02:f4:de:8c: + 58:fa:74:b9:fa:65:08:8f:46:79:a8:c6:69:df:88: + ee:18:d8:4d:90:a6:30:4f:3d:b7:78:56:1e:a7:24: + 20:48:3b:e5:ab:e7:8e:41:6e:9f:f1:3d:34:f6:1a: + a3:ef:ad:3f:87:00:b9:88:0f:bf:ce:c4:4e:4a:fc: + 1e:90:43:40:b1:df:86:cc:dd:1e:35:4f:95:9c:28: + 65:e4:5e:ba:0d:0b:3f:c7:5f:28:b3:4a:be:d2:d7: + 4f:38:da:f0:3d:68:3c:40:e7:db:fd:7a:79:89:4c: + 43:d1 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:client.test.com, DNS:alt.client.test.com + Signature Algorithm: sha256WithRSAEncryption + 47:6a:90:28:93:f3:31:ad:8d:70:59:1b:1d:c2:9a:63:b1:40: + 64:39:a1:5d:22:25:c1:45:e0:0b:98:82:47:c9:0d:31:44:d7: + 6a:cc:38:8b:ee:67:f4:43:7a:63:7e:b7:84:64:23:de:98:eb: + a3:5f:5a:ca:da:bc:37:16:76:dc:55:5b:eb:71:ef:f5:f9:f9: + db:77:34:1c:8e:b5:fe:9a:00:9d:bf:9f:5a:1a:51:68:54:47: + ef:0c:fb:cd:7f:08:81:01:bd:6e:6a:05:27:1d:66:38:75:7b: + 69:66:bf:8d:e7:e1:15:a6:5b:2d:1f:7a:c3:8a:af:14:88:da: + ab:6f:87:8f:df:cd:b6:82:f0:5e:f4:aa:de:36:ab:a0:de:eb: + dd:8b:dc:e7:58:ec:48:8a:27:3f:a2:97:69:aa:63:9e:74:5b: + 33:2d:1e:0a:17:02:7f:e3:97:10:75:3e:7d:77:48:a6:72:fb: + 5d:d7:68:78:1e:5c:ca:5a:8d:6f:48:f0:ea:92:e3:25:85:9f: + 7b:ec:fa:93:8e:ff:ef:bc:70:bd:77:5b:8e:38:cb:df:4e:5e: + 35:51:1b:ee:58:1d:cc:cb:bb:e0:85:1f:b6:84:c3:da:c0:1e: + 9b:71:88:02:c3:cc:8c:a1:77:0c:bb:e2:ea:05:d5:30:e6:9c: + 2d:d6:d2:e5 +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIUePebdaedgxKlx6C2r4faw9kywtcwDQYJKoZIhvcNAQEL +BQAwgYsxKDAmBgNVBAMMH1Rlc3QgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkx +CzAJBgNVBAgMAkNBMQswCQYDVQQGEwJVUzEcMBoGCSqGSIb3DQEJARYNcm9vdEB0 +ZXN0LmNvbTENMAsGA1UECgwEVGVzdDEYMBYGA1UECwwPVGVzdCBEZXBhcnRtZW50 +MCAXDTIwMTAyNzAzMTAwNFoYDzI1MjAwNjI4MDMxMDA0WjCBljELMAkGA1UEBhMC +VVMxCzAJBgNVBAgMAkNBMRQwEgYDVQQKDAtUZXN0IENsaWVudDElMCMGA1UECwwc +U3VidW5pdCBvZiBUZXN0IE9yZ2FuaXphdGlvbjEYMBYGA1UEAwwPY2xpZW50LnRl +c3QuY29tMSMwIQYJKoZIhvcNAQkBFhRyb290QGNsaWVudC50ZXN0LmNvbTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANVmycnvZK0Px/50++R1s3Zgf7te +OOkl5HCMmWOtdia4BmX1DrZvIma3S3qMTzr7DqzBDm5227WVg9Sihut4Y8XTR5fZ +bg857s1Rtq/NASygwLtY6yNqqgnjaTEug0mIQnF9JarxY16TpUQZR7I7HHrx8iR1 +UW8TfpokLELjIzG23fpQNpLvw8JA/kW5gHnZZGIC9N6MWPp0ufplCI9GeajGad+I +7hjYTZCmME89t3hWHqckIEg75avnjkFun/E9NPYao++tP4cAuYgPv87ETkr8HpBD +QLHfhszdHjVPlZwoZeReug0LP8dfKLNKvtLXTzja8D1oPEDn2/16eYlMQ9ECAwEA +AaMzMDEwLwYDVR0RBCgwJoIPY2xpZW50LnRlc3QuY29tghNhbHQuY2xpZW50LnRl +c3QuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBHapAok/MxrY1wWRsdwppjsUBkOaFd +IiXBReALmIJHyQ0xRNdqzDiL7mf0Q3pjfreEZCPemOujX1rK2rw3FnbcVVvrce/1 ++fnbdzQcjrX+mgCdv59aGlFoVEfvDPvNfwiBAb1uagUnHWY4dXtpZr+N5+EVplst +H3rDiq8UiNqrb4eP3822gvBe9KreNqug3uvdi9znWOxIiic/opdpqmOedFszLR4K +FwJ/45cQdT59d0imcvtd12h4HlzKWo1vSPDqkuMlhZ977PqTjv/vvHC9d1uOOMvf +Tl41URvuWB3My7vghR+2hMPawB6bcYgCw8yMoXcMu+LqBdUw5pwt1tLl +-----END CERTIFICATE----- diff --git a/packages/memcache-client/src/test/spec/client_key.pem b/packages/memcache-client/src/test/spec/client_key.pem new file mode 100644 index 0000000..cfc7691 --- /dev/null +++ b/packages/memcache-client/src/test/spec/client_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA1WbJye9krQ/H/nT75HWzdmB/u1446SXkcIyZY612JrgGZfUO +tm8iZrdLeoxPOvsOrMEObnbbtZWD1KKG63hjxdNHl9luDznuzVG2r80BLKDAu1jr +I2qqCeNpMS6DSYhCcX0lqvFjXpOlRBlHsjscevHyJHVRbxN+miQsQuMjMbbd+lA2 +ku/DwkD+RbmAedlkYgL03oxY+nS5+mUIj0Z5qMZp34juGNhNkKYwTz23eFYepyQg +SDvlq+eOQW6f8T009hqj760/hwC5iA+/zsROSvwekENAsd+GzN0eNU+VnChl5F66 +DQs/x18os0q+0tdPONrwPWg8QOfb/Xp5iUxD0QIDAQABAoIBAFbHF6NwH8ZoNnF4 +7L14R2QJ1adBp27Xo89IvU9VSEhyaNepETSKeLjALIdG6ykW0l7Zmp684pcyl4su +FCNIr3nQcrqDBkhWzI7dfaZ7fNDDBdypSeT7CO5AdZQ0T9rk9+/ibiDXTAs3iuro +RMlrVCeCn27H7jd2+/J+ZylD6BncfpgF6wAz80Ac6PzhlE28tjl8JhDV3AQtoSXp +1oFFT1OIvg6+0FcrxocGsM61akEKdJYmWP8mvQvBWIDFKib9VQuIQ3UEJrwG8H6A +at1H1hFIZpgYTXJnU1n34xxfml16kSpp61Sap531vwLrfi6jrsCgCppdfpX3HEdl +ApGed0kCgYEA9MgV1cB3W/18zALmVf3LRfwVT2oAikeIYr34AcdQ+09TYo20iFIi ++s993M5LOIRkXuL1a3zQYzQplp5hwyXy0+4v6NVHDiEothbOcViCE6pxesHxRlty +ujud2taUIXo3z+fod0TBW19MOzC2EZFzuy6XOle2uTe785NzmmGtKpsCgYEA3y6I +z53b1S4uXtqm494pPxhYvQTZJZND5XeIIW/L/WjWZaLlXtwqv/x94PyX9a3BjLvC +NHuAJDV54KOXZDLn5d+nuqYhwx6DQ6eARTRkqcbsMxqBpbOxHo1CInUWkbCAzeI6 +JiHa6+mbfyF1j9waxUcLLVCvI7/chYGCwEZojAMCgYAZOe2WI8mpP0x3Me1O+2LR +iZMgpgn8NjcGBhHjDQl5fMlZJMVwLxbPxkZwVQpHfs1hQf6M30YSkzOeBCCLHgQS +dz+UvTYfbKdnbJ5F7Bsrr6sWPZqB57bBHpe8D9UgRhouYPYCJKHQqteOWgzJbrYA +mmKXbqa0G9xQ0+dcB6jy9QKBgQDcuRoHMxkJ8b1chCSPsdTbGMoSTmwvlECO/bN9 +ViLJwVhgdkUgluAUtbMMwuPKzwhflgXQf9/Qb67UsxXzu8DLAHHSz6EOZuvtCgh6 +6QExQ5GwTOOxrFBcZdnlWCV0+rm4ZKebtNndfVES31V0bHtxZfyw2V4NHiALTXWx +kTCuXwKBgQDfDWjNsvHjHtY2RDO6OwI/X9sME49l9MmyaFlr7eCTvuGpTvvaZSvD +vJV2J9aBqlJlowpoH0UCy2TWjVSlh3GLFqlKVGOEVIT3FaR2QLHESzNhMCLoYKfd +819yNk6np4WYrN7RpcVoG1XR8sj4NJJQZJy0Iu1N8tnPm9AZEu8jxQ== +-----END RSA PRIVATE KEY----- diff --git a/packages/memcache-client/src/test/spec/client_tls.spec.ts b/packages/memcache-client/src/test/spec/client_tls.spec.ts new file mode 100644 index 0000000..233a7db --- /dev/null +++ b/packages/memcache-client/src/test/spec/client_tls.spec.ts @@ -0,0 +1,114 @@ +// eslint-disable-next-line filenames/match-regex +import { + MemcacheClient +} from "../../"; +import fs from "fs"; +import Path from "path"; +import { text1, text2 } from "../data/text"; +import NullLoger from "../../lib/null-logger"; +import memcached, { MemcachedServer } from "memcached-njs"; +import { AddressInfo } from "net"; + +describe("memcache TLS client", function () { + let memcachedServer: MemcachedServer | undefined; + let server: string; + let serverPort: number; + const defaultTlsOptions = { + key: fs.readFileSync(Path.join(__dirname, "server_key.pem")), + cert: fs.readFileSync(Path.join(__dirname, "server_crt.pem")), + requestCert: false + }; + + const tlsOptionsClientAuth = { + key: fs.readFileSync(Path.join(__dirname, "server_key.pem")), + cert: fs.readFileSync(Path.join(__dirname, "server_crt.pem")), + ca: fs.readFileSync(Path.join(__dirname, "cacert.pem")), + requestCert: true + }; + + const startMemcachedServer = (tlsOptions: any, port?: number) => { + const options: Record = { logger: NullLoger }; + options.tls = tlsOptions; + if (port) { + options.port = port; + } + return memcached.startServer(options).then((ms: MemcachedServer) => { + serverPort = (ms._server?.address() as AddressInfo).port; + server = `localhost:${serverPort}`; + memcachedServer = ms; + }); + }; + + afterEach((done) => { + if (memcachedServer !== undefined) { + memcachedServer.shutdown(); + memcachedServer = undefined; + } + done(); + }); + + it("TLS handles ECONNREFUSED", (done) => { + startMemcachedServer(defaultTlsOptions).then(() => done()).catch(done); + const x = new MemcacheClient({ server: "localhost:65000", tls: {} }); + let testError: Error; + x.cmd("stats") + .catch((err: Error) => (testError = err)) + .then(() => { + expect(testError.message).toContain("ECONNREFUSED"); + done(); + }); + }); + + it("TLS handles ENOTFOUND", (done) => { + const x = new MemcacheClient({ server: "badhost.baddomain.com:65000", tls: {} }); + let testError: Error; + x.cmd("stats") + .catch((err: Error) => (testError = err)) + .then(() => { + expect(testError.message).toContain("ENOTFOUND"); + done(); + }); + }); + + it("TLS connection with basic commands", (done) => { + startMemcachedServer(defaultTlsOptions).then(() => { + const c = new MemcacheClient({server: server, + tls: { + ca: fs.readFileSync(Path.join(__dirname, "cacert.pem")), + checkServerIdentity: () => {return undefined;} + }}); + c.version() + .then((v: string[]) => { + expect(v[0]).toEqual("VERSION"); + expect(v[1]).not.toBe(""); + }) + .then(() => c.set("key", text1)) + .then(() => c.get("key").then((r) => { + expect(r.value).toEqual(text1); + done(); + })); + }); + }); + + it("TLS connection with client authentication", (done) => { + startMemcachedServer(tlsOptionsClientAuth).then(() => { + const c = new MemcacheClient({server: server, + tls: { + key: fs.readFileSync(Path.join(__dirname, "client_key.pem")), + cert: fs.readFileSync(Path.join(__dirname, "client_crt.pem")), + ca: fs.readFileSync(Path.join(__dirname, "cacert.pem")), + checkServerIdentity: () => {return undefined;} + }}); + c.version() + .then((v: string[]) => { + expect(v[0]).toEqual("VERSION"); + expect(v[1]).not.toBe(""); + }) + .then(() => c.set("key", text2)) + .then(() => c.get("key").then((r) => { + expect(r.value).toEqual(text2); + done(); + })); + }); + }); +}); diff --git a/packages/memcache-client/src/test/spec/server_crt.pem b/packages/memcache-client/src/test/spec/server_crt.pem new file mode 100644 index 0000000..b0e49e1 --- /dev/null +++ b/packages/memcache-client/src/test/spec/server_crt.pem @@ -0,0 +1,76 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 78:f7:9b:75:a7:9d:83:12:a5:c7:a0:b6:af:87:da:c3:d9:32:c2:d6 + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN = Test Root Certificate Authority, ST = CA, C = US, emailAddress = root@test.com, O = Test, OU = Test Department + Validity + Not Before: Oct 27 03:09:35 2020 GMT + Not After : Jun 28 03:09:35 2520 GMT + Subject: C = US, ST = CA, O = Test, OU = Subunit of Test Organization, CN = test.com, emailAddress = root@test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:c8:9f:ff:4d:a0:6e:9e:ab:6f:0f:5b:7a:67:b7: + 2d:0b:eb:69:6b:9f:19:7a:6e:f5:b2:91:50:71:d4: + b4:42:84:b9:e0:8c:98:61:89:2d:90:c2:0d:68:d4: + ea:1c:6f:6d:d3:28:52:29:04:a1:4d:28:8e:38:35: + 98:1b:5b:f3:c7:da:41:17:83:c8:d5:32:40:7e:90: + bc:69:88:29:96:93:b4:d1:45:65:ac:7c:88:4c:54: + dc:b0:b1:06:82:7e:0d:d3:a6:2d:50:e0:cd:6b:0b: + c3:74:c4:96:8c:af:87:34:d7:3b:d7:33:60:e3:16: + 99:01:06:5e:93:77:8b:6a:c3:3f:ce:9f:e0:44:3c: + bc:b6:bc:e8:65:b3:72:94:ee:e4:99:6c:5f:a9:43: + e4:0a:8d:13:38:48:dd:08:9c:51:6d:d6:d3:6b:dc: + 44:b1:10:0f:7d:87:97:fc:ab:41:e5:11:28:b2:9b: + e1:4b:8d:64:94:ee:3e:5f:b5:9a:03:ac:64:e1:b8: + 3c:51:78:05:b9:98:f8:41:0b:36:7f:1e:76:a2:6a: + 51:75:2d:e2:fa:0d:00:d2:b9:96:50:eb:ad:33:c3: + ab:8c:ce:49:09:82:7f:69:77:b7:38:70:90:e7:16: + 9c:6b:f2:c0:7e:61:ab:c4:34:04:aa:38:dc:4a:00: + 50:ed + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:test.com, DNS:alt.test.com + Signature Algorithm: sha256WithRSAEncryption + 0f:ac:cb:a5:b2:13:63:3a:e7:f4:59:a6:ec:ad:ef:2e:61:5d: + 16:23:df:fb:a4:24:75:60:d4:fe:5a:d6:ed:f9:a7:a4:9d:49: + e0:1e:c6:e6:10:8d:d2:de:90:54:d4:c6:a8:e9:99:7b:62:c4: + b5:86:9f:19:96:cb:96:93:79:47:81:48:bf:32:20:45:6f:00: + 02:76:3f:1f:d5:3a:0b:ac:9e:b7:13:14:6f:b6:27:ad:34:d5: + fa:82:5c:c6:1a:b4:4f:36:7b:e6:03:6d:4f:85:ce:43:42:d3: + d3:7d:b4:7e:d1:dc:3b:27:31:94:d4:0a:08:ba:67:ec:e0:75: + 63:4a:48:b0:01:c7:90:2d:da:2e:c9:ef:c7:8a:20:76:75:89: + b5:cf:dc:a3:00:07:c0:26:73:89:00:63:ce:cf:7b:6e:95:c9: + 5b:5a:d9:2a:0c:d5:96:61:9e:dd:b5:db:88:61:45:df:29:c1: + de:34:e6:fa:25:23:2c:4e:71:c9:ba:d1:53:f9:38:28:6b:5a: + 47:a4:db:91:2d:54:3f:35:e4:a6:b6:1d:d1:7b:f9:c8:1a:35: + 71:1d:7d:94:2e:53:22:d8:e1:ca:93:fa:e5:c4:48:cd:ee:29: + 4a:65:71:2c:9a:ff:98:6f:48:25:2c:1a:90:14:4f:37:6e:e6: + 87:1e:f8:f4 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIUePebdaedgxKlx6C2r4faw9kywtYwDQYJKoZIhvcNAQEL +BQAwgYsxKDAmBgNVBAMMH1Rlc3QgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkx +CzAJBgNVBAgMAkNBMQswCQYDVQQGEwJVUzEcMBoGCSqGSIb3DQEJARYNcm9vdEB0 +ZXN0LmNvbTENMAsGA1UECgwEVGVzdDEYMBYGA1UECwwPVGVzdCBEZXBhcnRtZW50 +MCAXDTIwMTAyNzAzMDkzNVoYDzI1MjAwNjI4MDMwOTM1WjCBgTELMAkGA1UEBhMC +VVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQKDARUZXN0MSUwIwYDVQQLDBxTdWJ1bml0 +IG9mIFRlc3QgT3JnYW5pemF0aW9uMREwDwYDVQQDDAh0ZXN0LmNvbTEcMBoGCSqG +SIb3DQEJARYNcm9vdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAMif/02gbp6rbw9beme3LQvraWufGXpu9bKRUHHUtEKEueCMmGGJLZDC +DWjU6hxvbdMoUikEoU0ojjg1mBtb88faQReDyNUyQH6QvGmIKZaTtNFFZax8iExU +3LCxBoJ+DdOmLVDgzWsLw3TEloyvhzTXO9czYOMWmQEGXpN3i2rDP86f4EQ8vLa8 +6GWzcpTu5JlsX6lD5AqNEzhI3QicUW3W02vcRLEQD32Hl/yrQeURKLKb4UuNZJTu +Pl+1mgOsZOG4PFF4BbmY+EELNn8edqJqUXUt4voNANK5llDrrTPDq4zOSQmCf2l3 +tzhwkOcWnGvywH5hq8Q0BKo43EoAUO0CAwEAAaMlMCMwIQYDVR0RBBowGIIIdGVz +dC5jb22CDGFsdC50ZXN0LmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAD6zLpbITYzrn +9Fmm7K3vLmFdFiPf+6QkdWDU/lrW7fmnpJ1J4B7G5hCN0t6QVNTGqOmZe2LEtYaf +GZbLlpN5R4FIvzIgRW8AAnY/H9U6C6yetxMUb7YnrTTV+oJcxhq0TzZ75gNtT4XO +Q0LT0320ftHcOycxlNQKCLpn7OB1Y0pIsAHHkC3aLsnvx4ogdnWJtc/cowAHwCZz +iQBjzs97bpXJW1rZKgzVlmGe3bXbiGFF3ynB3jTm+iUjLE5xybrRU/k4KGtaR6Tb +kS1UPzXkprYd0Xv5yBo1cR19lC5TItjhypP65cRIze4pSmVxLJr/mG9IJSwakBRP +N27mhx749A== +-----END CERTIFICATE----- diff --git a/packages/memcache-client/src/test/spec/server_key.pem b/packages/memcache-client/src/test/spec/server_key.pem new file mode 100644 index 0000000..74869d0 --- /dev/null +++ b/packages/memcache-client/src/test/spec/server_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAyJ//TaBunqtvD1t6Z7ctC+tpa58Zem71spFQcdS0QoS54IyY +YYktkMINaNTqHG9t0yhSKQShTSiOODWYG1vzx9pBF4PI1TJAfpC8aYgplpO00UVl +rHyITFTcsLEGgn4N06YtUODNawvDdMSWjK+HNNc71zNg4xaZAQZek3eLasM/zp/g +RDy8trzoZbNylO7kmWxfqUPkCo0TOEjdCJxRbdbTa9xEsRAPfYeX/KtB5REospvh +S41klO4+X7WaA6xk4bg8UXgFuZj4QQs2fx52ompRdS3i+g0A0rmWUOutM8OrjM5J +CYJ/aXe3OHCQ5xaca/LAfmGrxDQEqjjcSgBQ7QIDAQABAoIBACmp4HEUgiR9YaEE +1FS5m6dACjKJZdchN/EPcG9TRuQRgDB7wiFvRYEsa3B71up00Y/qbbWK+px1caOG +rcHwxJ2aW64wdgKgXvhpwlcAKfLVVdWn3ceGTR/c97/R45Ix71kmx35mUQKL/NlB +AirRQPjeQdUdHF/Mj5XA5t8lElTnPSbxTHqTsS4ktwZDz5szMyrMYTIj2Pwe9VJv +Q6HpmaWhiMBDtc03yM+v2M4kXrtjYM0z3PGQsH77Of87Cq1E2gtxfroZC7lfsH5G +lDffmW6fVGKeRnXya0WKRB58gJh/wF6rdhSbDjtOMl1UuObuFaz7hArI0LLFjiLp +hkxt5EECgYEA6tHGsTdW3w3NBSihjqyqJdhHXnRyekJy/LBTyoUtW2yXSP5Dy/Iq +ZVkAjxiGnOOuPKfDSCm6eKHKaB4+mhXD1ySclDdiurUg6zfbPjK9nzIuETJAPpR7 +Wbl4AzrTdpbFct4svcZa4zx1wbzjlLgKzTonmZTLufMmt7VoypNlxykCgYEA2rii +zAnY94uO66WZNaxAHG1DBmz20scpmQGuwqm08Pp0Wecim91gaonJWTV0RLRNXNB8 +Y+lTlDCdhGzIaOtSUJBjIVtbaz/X6FuNvuuWRi+No/QtsA35dZeg3vNSlAp/IHKE +tqLf7ep9DkheLqNPIv2OMHZNw0BmjeqMjqXvSCUCgYBrayNVllcrGokbPwcI2XvM +bC/ZybNEsnkflxn9nwast/RM8+PXvCQg0KIs069gvdbK8IOo0032OSz6jDtfCW3Z +UWo/c0a62nkAoUCuJ1APL0lbnH0/I3V6ChoYgCSUL0yLy470EXUqVlIYGmyRb8+h +KmVLIIJBwjWH/Hi/ksYQYQKBgQCD1FejXagdhEtvcw+GTz0RJTYJ0HFwl8RVybLY +98rsmDi621p70ZHEhSoMD/D1XCto7uyal87defPnFl4xBu1FS9HEEDloykFLdqtX +0M1xnkXj9U/4VmPuYab/2m8CddUr7HpbTo5j6zrW+f1yZNJVB7jFMvkp304w6Zcw +f4la1QKBgCc2ksKWuoSt3Kfv6s2dXRsO4p/osPjQc3bnVK2GiAJAjLf7fqadRdNL +jCMzgA+lxRP4dzA0KD6Z8J6ePr6KmGa8b+lbCvQFKvJCWvsLLKqkz/lnfljp8VvA +ntXBBQr+46peeLGehVvcprwc4jANagkiQq5DoVDONN+lobRbCM21 +-----END RSA PRIVATE KEY----- diff --git a/packages/memcache-client/src/types.ts b/packages/memcache-client/src/types.ts index 55400f8..01f7c33 100644 --- a/packages/memcache-client/src/types.ts +++ b/packages/memcache-client/src/types.ts @@ -1,3 +1,4 @@ +import { ConnectionOptions } from "tls"; import { MemcacheConnection } from "./lib/connection"; export type CommandCallback = (conn: MemcacheConnection) => void; @@ -49,6 +50,7 @@ export type MemcacheClientOptions = { compressor?: CompressorLibrary; logger?: any; Promise?: PromiseConstructor; + tls?: ConnectionOptions; }; // connection diff --git a/packages/memcached-njs/src/lib/memcached-server.ts b/packages/memcached-njs/src/lib/memcached-server.ts index 0347e9b..304f3a0 100644 --- a/packages/memcached-njs/src/lib/memcached-server.ts +++ b/packages/memcached-njs/src/lib/memcached-server.ts @@ -1,5 +1,6 @@ /* eslint-disable no-console,no-magic-numbers,no-var,camelcase,no-unused-vars,max-statements */ import net, { Server, AddressInfo, Socket } from "net"; +import tls, { TlsOptions } from "tls"; import { DefaultLogger, PendingData } from "../types"; import MemcacheConnection from "./memcache-connection"; @@ -29,6 +30,7 @@ export type MemcacheServerOptions = { port?: number | string | undefined; logger?: any; compress?: boolean | undefined; + tls?: TlsOptions; }; export class MemcachedServer { @@ -55,8 +57,13 @@ export class MemcachedServer { } startup(): Promise { - const server = net.createServer(); - server.on("connection", this.newConnection.bind(this)); + let server: net.Server | tls.Server; + if (this.options.tls !== undefined) { + server = tls.createServer(this.options.tls, this.newConnection.bind(this)); + } else { + server = net.createServer(); + server.on("connection", this.newConnection.bind(this)); + } this._server = server; return new Promise((resolve, reject) => { server.once("error", (err) => { @@ -66,7 +73,6 @@ export class MemcachedServer { server.listen(this.options.port, () => { this._port = (server.address() as AddressInfo).port; - this.logger.info(`server listening at port ${this._port}`); server.removeAllListeners("error"); server.on("error", this._onError.bind(this));