diff --git a/README.md b/README.md index 44fe4dc..88679f4 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ const SocksProxyAgent = require('socks-proxy-agent'); const agent = new SocksProxyAgent(`socks://127.0.0.1:${socks4Port}`, true); const options = { apiVersion: 'v1', - rpOptions: { + requestOptions: { agent, }, }; @@ -128,6 +128,42 @@ const options = { const vault = require('node-vault')(options); ``` +## Custom SSL/TLS Configuration + +If you encounter SSL errors after upgrading to Node 18+ (e.g., `EPROTO` errors related to +`unsafe legacy renegotiation disabled`), you can pass SSL/TLS options via `requestOptions` +or `rpDefaults` when initializing the client: + +```javascript +const vault = require('node-vault')({ + apiVersion: 'v1', + endpoint: 'https://vault.example.com:8200', + token: 'MY_TOKEN', + requestOptions: { + agentOptions: { + securityOptions: 'SSL_OP_LEGACY_SERVER_CONNECT', + }, + }, +}); +``` + +The `requestOptions` object is passed through to the underlying HTTP library +([postman-request](https://www.npmjs.com/package/postman-request)) for every request. You can +use it to configure any supported request option, including `agentOptions`, custom `headers`, +or a custom `agent`. + +You can also pass request options per-call to any method: + +```javascript +vault.read('secret/hello', { + agentOptions: { + securityOptions: 'SSL_OP_LEGACY_SERVER_CONNECT', + }, +}); +``` + +See [example/pass_request_options.js](example/pass_request_options.js) for more examples. + [![Backers](https://opencollective.com/node-vault/tiers/backers.svg?avatarHeight=80&width=600)](https://opencollective.com/node-vault/contribute) [examples]: https://github.com/nodevault/node-vault/tree/master/example diff --git a/example/pass_request_options.js b/example/pass_request_options.js index 2c81a24..c48b1a1 100644 --- a/example/pass_request_options.js +++ b/example/pass_request_options.js @@ -2,20 +2,32 @@ process.env.DEBUG = 'node-vault'; // switch on debug mode -const vault = require('./../src/index')(); +// Pass request options at initialization time. +// These options are forwarded to postman-request for every request. +const vault = require('./../src/index')({ + requestOptions: { + agentOptions: { + cert: '', + key: '', + passphrase: '', + securityOptions: 'SSL_OP_NO_SSLv3', + }, + }, +}); -const options = { +// You can also pass (or override) request options per-call. +const perCallOptions = { headers: { 'X-HELLO': 'world', }, agentOptions: { - cert: 'mycert', - key: 'mykey', - passphrase: 'password', + cert: '', + key: '', + passphrase: '', securityOptions: 'SSL_OP_NO_SSLv3', }, }; -vault.help('sys/policy', options) +vault.help('sys/policy', perCallOptions) .then(() => vault.help('sys/mounts')) .catch((err) => console.error(err.message)); diff --git a/test/unit.js b/test/unit.js index 2ac9525..1ba6738 100644 --- a/test/unit.js +++ b/test/unit.js @@ -295,6 +295,142 @@ describe('node-vault', () => { }); }); + describe('config.requestOptions forwarding', () => { + let requestWithOpts = null; + let vaultWithOpts = null; + + const agentOpts = { + securityOptions: 'SSL_OP_LEGACY_SERVER_CONNECT', + }; + + function getURIWithOpts(path) { + return [vaultWithOpts.endpoint, vaultWithOpts.apiVersion, path].join('/'); + } + + function assertRequestWithOpts(thisRequest, params, done) { + return () => { + thisRequest.should.have.calledOnce(); + thisRequest.calledWithMatch(params).should.be.ok(); + return done(); + }; + } + + beforeEach(() => { + requestWithOpts = sinon.stub(); + const resp = sinon.stub(); + resp.statusCode = 200; + + requestWithOpts.returns({ + then(fn) { + return fn(resp); + }, + catch(fn) { + return fn(); + }, + }); + + vaultWithOpts = index({ + endpoint: 'http://localhost:8200', + token: '123', + 'request-promise': { + defaults: () => requestWithOpts, + }, + requestOptions: { + agentOptions: agentOpts, + }, + }); + }); + + it('should forward agentOptions from config.requestOptions in help()', (done) => { + const path = 'sys/policy'; + const params = { + method: 'GET', + uri: `${getURIWithOpts(path)}?help=1`, + agentOptions: agentOpts, + }; + vaultWithOpts.help(path) + .then(assertRequestWithOpts(requestWithOpts, params, done)) + .catch(done); + }); + + it('should forward agentOptions from config.requestOptions in read()', (done) => { + const path = 'secret/hello'; + const params = { + method: 'GET', + uri: getURIWithOpts(path), + agentOptions: agentOpts, + }; + vaultWithOpts.read(path) + .then(assertRequestWithOpts(requestWithOpts, params, done)) + .catch(done); + }); + + it('should forward agentOptions from config.requestOptions in write()', (done) => { + const path = 'secret/hello'; + const params = { + method: 'POST', + uri: getURIWithOpts(path), + agentOptions: agentOpts, + }; + vaultWithOpts.write(path, { value: 'world' }) + .then(assertRequestWithOpts(requestWithOpts, params, done)) + .catch(done); + }); + + it('should forward agentOptions from config.requestOptions in delete()', (done) => { + const path = 'secret/hello'; + const params = { + method: 'DELETE', + uri: getURIWithOpts(path), + agentOptions: agentOpts, + }; + vaultWithOpts.delete(path) + .then(assertRequestWithOpts(requestWithOpts, params, done)) + .catch(done); + }); + + it('should forward agentOptions from config.requestOptions in list()', (done) => { + const path = 'secret/hello'; + const params = { + method: 'LIST', + uri: getURIWithOpts(path), + agentOptions: agentOpts, + }; + vaultWithOpts.list(path) + .then(assertRequestWithOpts(requestWithOpts, params, done)) + .catch(done); + }); + + it('should forward agentOptions from config.requestOptions in generated functions', (done) => { + const name = 'myTestFunction'; + vaultWithOpts.generateFunction(name, { + method: 'GET', + path: '/myroute', + }); + const params = { + agentOptions: agentOpts, + }; + vaultWithOpts[name]() + .then(assertRequestWithOpts(requestWithOpts, params, done)) + .catch(done); + }); + + it('should allow per-call requestOptions to override config.requestOptions', (done) => { + const path = 'secret/hello'; + const overrideOpts = { + securityOptions: 'SSL_OP_NO_SSLv3', + }; + const params = { + method: 'GET', + uri: getURIWithOpts(path), + agentOptions: overrideOpts, + }; + vaultWithOpts.read(path, { agentOptions: overrideOpts }) + .then(assertRequestWithOpts(requestWithOpts, params, done)) + .catch(done); + }); + }); + describe('unwrap(options)', () => { it('should return original response', (done) => { const path = 'sys/wrapping/unwrap';