@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.
- package/lib/BedrockKeystoreConfigStorage.js +24 -0
- package/lib/config.js +30 -1
- package/lib/http.js +78 -6
- package/lib/middleware.js +1 -1
- package/package.json +2 -1
- package/schemas/bedrock-kms-http.js +11 -0
|
@@ -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-
|
|
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-
|
|
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
|
-
|
|
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.
|
|
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
|