@bedrock/kms-http 23.0.0 → 23.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,4 +33,28 @@ export class BedrockKeystoreConfigStorage extends KeystoreConfigStorage {
33
33
 
34
34
  return returnRecord ? record : keystoreConfig;
35
35
  }
36
+
37
+ async getAll({
38
+ controller, req, query = {}, options = {}, returnRecord = false
39
+ } = {}) {
40
+ const records = await keystores.find({controller, query, options});
41
+ const keystoreConfigs = records.map(r => r.config);
42
+ // skip request checks if specifically requested
43
+ if(req === false) {
44
+ return returnRecord ? records : keystoreConfigs;
45
+ }
46
+ // verify that request is from an IP that is allowed to access the config
47
+ for(let i = 0; i < keystoreConfigs.length; ++i) {
48
+ const keystoreConfig = keystoreConfigs[i];
49
+ const {verified} = helpers.verifyRequestIp({keystoreConfig, req});
50
+ if(!verified) {
51
+ throw new BedrockError(
52
+ 'Permission denied. Source IP is not allowed.', 'NotAllowedError', {
53
+ httpStatusCode: 403,
54
+ public: true,
55
+ });
56
+ }
57
+ }
58
+ return returnRecord ? records : keystoreConfigs;
59
+ }
36
60
  }
package/lib/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
2
+ * Copyright (c) 2019-2026 Digital Bazaar, Inc.
3
3
  */
4
4
  import {config} from '@bedrock/core';
5
5
  import '@bedrock/app-identity';
@@ -35,3 +35,32 @@ cfg.kmsOperationOptions = {
35
35
  // 1000 year max TTL by default
36
36
  maxDelegationTtl: 1000 * 60 * 60 * 24 * 365 * 1000
37
37
  };
38
+
39
+ /* Optional proxy feature: An application running bedrock-kms-http can use
40
+ these configuration options to redirect KMS operations to another
41
+ bedrock-kms-http application, called a "proxy target". The proxy target must
42
+ shares the same database and be configured with the same public address
43
+ (domain / origin) but it is expected to be run behind a firewall (with
44
+ restricted access) and inside of an isolated environment, such as a secure
45
+ enclave, to add an additional layer of security during the processing of
46
+ KMS operations.
47
+
48
+ The proxying application will not have access to secret key material or to
49
+ secrets that can decrypt/unseal/unwrap other secrets, only the proxy target
50
+ will have access to some or all of these (or be capable of using some or all
51
+ of these via authz credentials only it can access). Both applications will
52
+ have access to the same database and will be expected to run compatible
53
+ versions of software to ensure database record compatibility. The proxy
54
+ target MUST properly check authz on all incoming requests; authz is is NOT
55
+ checked by the proxy.
56
+
57
+ Proxy entries are defined by KMS module, i.e., at most one proxy target can
58
+ be configured per KMS module. When requests for a particular KMS module arrive,
59
+ they are forwarded in their entirety to the related proxy target. */
60
+ cfg.kmsModuleProxy = {
61
+ /*
62
+ <kms-module-name>: {
63
+ proxyTarget: <address (base URL) for kms proxy target>
64
+ }
65
+ */
66
+ };
package/lib/http.js CHANGED
@@ -1,19 +1,21 @@
1
1
  /*!
2
- * Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
2
+ * Copyright (c) 2019-2026 Digital Bazaar, Inc.
3
3
  */
4
4
  import * as bedrock from '@bedrock/core';
5
5
  import * as brZCapStorage from '@bedrock/zcap-storage';
6
6
  import * as helpers from './helpers.js';
7
7
  import * as middleware from './middleware.js';
8
+ import {createProxyMiddleware, fixRequestBody} from 'http-proxy-middleware';
8
9
  import {
9
- keystores,
10
- defaultModuleManager as moduleManager
11
- } from '@bedrock/kms';
12
- import {
10
+ getConfigsQuery,
13
11
  postKeystoreBody,
14
12
  postRevocationBody,
15
13
  updateKeystoreConfigBody
16
14
  } from '../schemas/bedrock-kms-http.js';
15
+ import {
16
+ keystores,
17
+ defaultModuleManager as moduleManager
18
+ } from '@bedrock/kms';
17
19
  import {reportOperationUsage, SERVICE_TYPE} from './metering.js';
18
20
  import {asyncHandler} from '@bedrock/express';
19
21
  import {generateRandom} from '@digitalbazaar/webkms-switch';
@@ -21,7 +23,7 @@ import {meters} from '@bedrock/meter-usage-reporter';
21
23
  import {createValidateMiddleware as validate} from '@bedrock/validation';
22
24
 
23
25
  const {config, util: {BedrockError}} = bedrock;
24
- const {cors} = middleware;
26
+ const {cors, storage} = middleware;
25
27
 
26
28
  bedrock.events.on('bedrock-express.configure.routes', app => {
27
29
  const cfg = config['kms-http'];
@@ -48,6 +50,9 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
48
50
  // associated with a request
49
51
  const getKeystoreConfig = middleware.createGetKeystoreConfig({routes});
50
52
 
53
+ // create KMS module proxy middleware for use with KMS operations
54
+ const kmsModuleProxyMiddleware = _createKmsModuleProxyMiddleware();
55
+
51
56
  /* Note: CORS is used on all endpoints. This is safe because authorization
52
57
  uses HTTP signatures + capabilities, not cookies; CSRF is not possible. */
53
58
 
@@ -116,6 +121,30 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
116
121
  res.status(201).location(id).json(record.config);
117
122
  }));
118
123
 
124
+ // get all keystores by controller query
125
+ app.get(
126
+ routes.keystores,
127
+ cors(),
128
+ validate({querySchema: getConfigsQuery}),
129
+ middleware.authorizeZcapInvocation({
130
+ async getExpectedValues() {
131
+ const {baseUri, host} = bedrock.config.server;
132
+ return {
133
+ host,
134
+ rootInvocationTarget: `${baseUri}${routes.keystores}`
135
+ };
136
+ },
137
+ async getRootController({req}) {
138
+ return req.query.controller;
139
+ }
140
+ }),
141
+ asyncHandler(async (req, res) => {
142
+ const controller = req.query.controller;
143
+ const options = {projection: {_id: 0, config: 1}, limit: 100};
144
+ const results = await storage.getAll({controller, req, options});
145
+ res.json({results});
146
+ }));
147
+
119
148
  // update keystore config
120
149
  app.options(routes.keystore, cors());
121
150
  app.post(
@@ -190,6 +219,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
190
219
  routes.keys,
191
220
  cors(),
192
221
  getKeystoreConfig,
222
+ kmsModuleProxyMiddleware,
193
223
  handleOperation);
194
224
 
195
225
  // invoke KMS operation on an existing key
@@ -198,6 +228,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
198
228
  routes.key,
199
229
  cors(),
200
230
  getKeystoreConfig,
231
+ kmsModuleProxyMiddleware,
201
232
  handleOperation);
202
233
 
203
234
  // TODO: consider whether this should be exposed w/o authorization
@@ -208,6 +239,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
208
239
  routes.key,
209
240
  cors(),
210
241
  getKeystoreConfig,
242
+ kmsModuleProxyMiddleware,
211
243
  asyncHandler(async (req, res) => {
212
244
  const {webkms: {keystore}} = req;
213
245
  const keyId = `${keystore.id}/keys/${req.params.keyId}`;
@@ -261,3 +293,43 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
261
293
  reportOperationUsage({req});
262
294
  }));
263
295
  });
296
+
297
+ function _createKmsModuleProxyMiddleware() {
298
+ const cfg = config['kms-http'];
299
+
300
+ const kmsModuleProxyEntries = Object.entries(cfg.kmsModuleProxy ?? {});
301
+ if(kmsModuleProxyEntries.length === 0) {
302
+ // nothing to proxy; no-op
303
+ return function kmsModuleProxyMiddleware(req, res, next) {
304
+ next();
305
+ };
306
+ }
307
+
308
+ const map = new Map();
309
+ for(const [kmsModule, {proxyTarget}] of kmsModuleProxyEntries) {
310
+ map.set(kmsModule, createProxyMiddleware({
311
+ target: proxyTarget,
312
+ // do not rewrite host header
313
+ changeOrigin: false,
314
+ // match TLS certificate checking expectations w/bedrock config
315
+ secure: config['https-agent'].rejectUnauthorized,
316
+ // include x-forward* headers
317
+ xfwd: true,
318
+ // do not forward websockets
319
+ ws: false,
320
+ on: {
321
+ // required to rewrite body since it has been parsed
322
+ proxyReq: fixRequestBody
323
+ }
324
+ }));
325
+ }
326
+ return function kmsModuleProxyMiddleware(req, res, next) {
327
+ const {webkms: {keystore}} = req;
328
+ const {kmsModule} = keystore;
329
+ const mw = map.get(kmsModule);
330
+ if(!mw) {
331
+ return next();
332
+ }
333
+ mw(req, res, next);
334
+ };
335
+ }
package/lib/middleware.js CHANGED
@@ -24,7 +24,7 @@ const {helpers: {inspectCapabilityChain}} = brZCapStorage;
24
24
 
25
25
  const FIVE_MINUTES = 1000 * 60 * 5;
26
26
 
27
- const storage = new BedrockKeystoreConfigStorage();
27
+ export const storage = new BedrockKeystoreConfigStorage();
28
28
 
29
29
  // creates middleware to get the keystore config for the current request and
30
30
  // caches it in `req.webkms.keystore`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrock/kms-http",
3
- "version": "23.0.0",
3
+ "version": "23.2.0",
4
4
  "type": "module",
5
5
  "description": "Bedrock KMS HTTP API",
6
6
  "main": "./lib/index.js",
@@ -35,6 +35,7 @@
35
35
  "cidr-regex": "^4.1.3",
36
36
  "cors": "^2.8.5",
37
37
  "forwarded": "^0.2.0",
38
+ "http-proxy-middleware": "^3.0.5",
38
39
  "ipaddr.js": "^2.2.0"
39
40
  },
40
41
  "peerDependencies": {
@@ -162,7 +162,18 @@ const postRevocationBody = {
162
162
  ...delegatedZcap
163
163
  };
164
164
 
165
+ const getConfigsQuery = {
166
+ title: 'Service Object Configuration Query',
167
+ type: 'object',
168
+ required: ['controller'],
169
+ additionalProperties: false,
170
+ properties: {
171
+ controller
172
+ }
173
+ };
174
+
165
175
  export {
176
+ getConfigsQuery,
166
177
  postKeystoreBody,
167
178
  postRevocationBody,
168
179
  updateKeystoreConfigBody