@bedrock/kms 9.0.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/.eslintrc.cjs +12 -0
- package/.github/workflows/main.yml +77 -0
- package/CHANGELOG.md +219 -0
- package/LICENSE.md +115 -0
- package/README.md +2 -0
- package/lib/BedrockKmsModuleManager.js +16 -0
- package/lib/config.js +16 -0
- package/lib/index.js +34 -0
- package/lib/keystores.js +330 -0
- package/package.json +52 -0
- package/test/mocha/.eslintrc +9 -0
- package/test/mocha/10-keystores-insert-api.js +275 -0
- package/test/mocha/11-keystores-get-api.js +79 -0
- package/test/mocha/12-keystores-find-api.js +127 -0
- package/test/mocha/13-keystores-update-api.js +163 -0
- package/test/mocha/14-keystores-getStorageUsage-api.js +119 -0
- package/test/mocha/20-key-operations.js +241 -0
- package/test/mocha/30-bulk-operations.js +111 -0
- package/test/mocha/40-database.js +95 -0
- package/test/mocha/50-document-loader.js +40 -0
- package/test/mocha/cryptoLd.js +22 -0
- package/test/mocha/helpers.js +46 -0
- package/test/mocha/mock.data.js +62 -0
- package/test/package.json +49 -0
- package/test/test.config.js +17 -0
- package/test/test.js +9 -0
package/lib/keystores.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import * as bedrock from '@bedrock/core';
|
|
5
|
+
import * as database from '@bedrock/mongodb';
|
|
6
|
+
import assert from 'assert-plus';
|
|
7
|
+
import pAll from 'p-all';
|
|
8
|
+
import {createRequire} from 'module';
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const {LruCache} = require('@digitalbazaar/lru-memoize');
|
|
11
|
+
|
|
12
|
+
const {util: {BedrockError}} = bedrock;
|
|
13
|
+
|
|
14
|
+
// load config defaults
|
|
15
|
+
import './config.js';
|
|
16
|
+
|
|
17
|
+
const USAGE_COUNTER_MAX_CONCURRENCY = 100;
|
|
18
|
+
let KEYSTORE_CONFIG_CACHE;
|
|
19
|
+
|
|
20
|
+
bedrock.events.on('bedrock.init', async () => {
|
|
21
|
+
const cfg = bedrock.config.kms;
|
|
22
|
+
KEYSTORE_CONFIG_CACHE = new LruCache({
|
|
23
|
+
max: cfg.keystoreConfigCache.maxSize,
|
|
24
|
+
maxAge: cfg.keystoreConfigCache.maxAge
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
bedrock.events.on('bedrock-mongodb.ready', async () => {
|
|
29
|
+
await database.openCollections(['kms-keystore']);
|
|
30
|
+
|
|
31
|
+
await database.createIndexes([{
|
|
32
|
+
// cover queries keystore by ID
|
|
33
|
+
collection: 'kms-keystore',
|
|
34
|
+
fields: {'config.id': 1},
|
|
35
|
+
options: {unique: true, background: false}
|
|
36
|
+
}, {
|
|
37
|
+
// cover config queries by controller
|
|
38
|
+
collection: 'kms-keystore',
|
|
39
|
+
fields: {'config.controller': 1},
|
|
40
|
+
options: {unique: false, background: false}
|
|
41
|
+
}, {
|
|
42
|
+
// ensure config uniqueness of reference ID per controller
|
|
43
|
+
collection: 'kms-keystore',
|
|
44
|
+
fields: {'config.controller': 1, 'config.referenceId': 1},
|
|
45
|
+
options: {
|
|
46
|
+
partialFilterExpression: {
|
|
47
|
+
'config.referenceId': {$exists: true}
|
|
48
|
+
},
|
|
49
|
+
unique: true,
|
|
50
|
+
background: false
|
|
51
|
+
}
|
|
52
|
+
}, {
|
|
53
|
+
// cover counting keystores in use by meter ID, if present
|
|
54
|
+
collection: 'kms-keystore',
|
|
55
|
+
fields: {'config.meterId': 1},
|
|
56
|
+
options: {
|
|
57
|
+
partialFilterExpression: {
|
|
58
|
+
'config.meterId': {$exists: true}
|
|
59
|
+
},
|
|
60
|
+
unique: false, background: false
|
|
61
|
+
}
|
|
62
|
+
}]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* An object containing information on the query plan.
|
|
67
|
+
*
|
|
68
|
+
* @typedef {object} ExplainObject
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Establishes a new keystore by inserting its configuration into storage.
|
|
73
|
+
*
|
|
74
|
+
* @param {object} options - The options to use.
|
|
75
|
+
* @param {object} options.config - The keystore configuration.
|
|
76
|
+
*
|
|
77
|
+
* @returns {Promise<object>} The database record.
|
|
78
|
+
*/
|
|
79
|
+
export async function insert({config} = {}) {
|
|
80
|
+
assert.object(config, 'config');
|
|
81
|
+
assert.string(config.id, 'config.id');
|
|
82
|
+
assert.string(config.controller, 'config.controller');
|
|
83
|
+
assert.string(config.kmsModule, 'config.kmsModule');
|
|
84
|
+
|
|
85
|
+
// require starting sequence to be 0
|
|
86
|
+
if(config.sequence !== 0) {
|
|
87
|
+
throw new BedrockError(
|
|
88
|
+
'Keystore config sequence must be "0".',
|
|
89
|
+
'DataError', {
|
|
90
|
+
public: true,
|
|
91
|
+
httpStatusCode: 400
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// insert the configuration and get the updated record
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
const meta = {created: now, updated: now};
|
|
98
|
+
const record = {
|
|
99
|
+
meta,
|
|
100
|
+
config
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
const result = await database.collections['kms-keystore'].insertOne(
|
|
104
|
+
record, database.writeOptions);
|
|
105
|
+
return result.ops[0];
|
|
106
|
+
} catch(e) {
|
|
107
|
+
if(!database.isDuplicateError(e)) {
|
|
108
|
+
throw e;
|
|
109
|
+
}
|
|
110
|
+
throw new BedrockError(
|
|
111
|
+
'Duplicate keystore configuration.',
|
|
112
|
+
'DuplicateError', {
|
|
113
|
+
public: true,
|
|
114
|
+
httpStatusCode: 409
|
|
115
|
+
}, e);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Retrieves all keystore configs matching the given query.
|
|
121
|
+
*
|
|
122
|
+
* @param {object} options - The options to use.
|
|
123
|
+
* @param {string} options.controller - The controller for the keystores to
|
|
124
|
+
* retrieve.
|
|
125
|
+
* @param {object} [options.query={}] - The query to use.
|
|
126
|
+
* @param {object} [options.options={}] - The query options (eg: 'sort').
|
|
127
|
+
* @param {boolean} [options.explain=false] - An optional explain boolean.
|
|
128
|
+
*
|
|
129
|
+
* @returns {Promise<Array | ExplainObject>} Resolves with the records that
|
|
130
|
+
* matched the query or an ExplainObject if `explain=true`.
|
|
131
|
+
*/
|
|
132
|
+
export async function find({
|
|
133
|
+
controller, query = {}, options = {}, explain = false
|
|
134
|
+
} = {}) {
|
|
135
|
+
assert.string(controller, 'options.controller');
|
|
136
|
+
const collection = database.collections['kms-keystore'];
|
|
137
|
+
|
|
138
|
+
// force controller ID
|
|
139
|
+
query['config.controller'] = controller;
|
|
140
|
+
const cursor = await collection.find(query, options);
|
|
141
|
+
|
|
142
|
+
if(explain) {
|
|
143
|
+
return cursor.explain('executionStats');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return cursor.toArray();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Updates a keystore config if its sequence number is next.
|
|
151
|
+
*
|
|
152
|
+
* @param {object} options - The options to use.
|
|
153
|
+
* @param {object} options.config - The keystore configuration.
|
|
154
|
+
* @param {boolean} [options.explain=false] - An optional explain boolean.
|
|
155
|
+
*
|
|
156
|
+
* @returns {Promise<boolean | ExplainObject>} Resolves with `true` on update
|
|
157
|
+
* success or an ExplainObject if `explain=true`.
|
|
158
|
+
*/
|
|
159
|
+
export async function update({config, explain = false} = {}) {
|
|
160
|
+
assert.object(config, 'config');
|
|
161
|
+
|
|
162
|
+
const collection = database.collections['kms-keystore'];
|
|
163
|
+
const query = {
|
|
164
|
+
'config.id': config.id,
|
|
165
|
+
'config.sequence': config.sequence - 1,
|
|
166
|
+
// it is illegal to change the `kmsModule`, so it must match
|
|
167
|
+
'config.kmsModule': config.kmsModule
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
if(explain) {
|
|
171
|
+
// 'find().limit(1)' is used here because 'updateOne()' doesn't return a
|
|
172
|
+
// cursor which allows the use of the explain function.
|
|
173
|
+
const cursor = await collection.find(query).limit(1);
|
|
174
|
+
return cursor.explain('executionStats');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// updates the keystore configuration
|
|
178
|
+
const result = await collection.updateOne(query, {
|
|
179
|
+
$set: {
|
|
180
|
+
config,
|
|
181
|
+
'meta.updated': Date.now()
|
|
182
|
+
}
|
|
183
|
+
}, database.writeOptions);
|
|
184
|
+
|
|
185
|
+
if(result.result.n === 0) {
|
|
186
|
+
// no records changed...
|
|
187
|
+
throw new BedrockError(
|
|
188
|
+
'Could not update keystore configuration. ' +
|
|
189
|
+
'Record sequence and "kmsModule" do not match or keystore does ' +
|
|
190
|
+
'not exist.',
|
|
191
|
+
'InvalidStateError', {
|
|
192
|
+
id: config.id,
|
|
193
|
+
sequence: config.sequence,
|
|
194
|
+
httpStatusCode: 409,
|
|
195
|
+
public: true
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// delete record from cache
|
|
200
|
+
KEYSTORE_CONFIG_CACHE.delete(config.id);
|
|
201
|
+
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Gets a keystore configuration.
|
|
207
|
+
*
|
|
208
|
+
* @param {object} options - The options to use.
|
|
209
|
+
* @param {string} options.id - The ID of the keystore.
|
|
210
|
+
*
|
|
211
|
+
* @returns {Promise<object>} Resolves to `{config, meta}`.
|
|
212
|
+
*/
|
|
213
|
+
export async function get({id} = {}) {
|
|
214
|
+
assert.string(id, 'id');
|
|
215
|
+
const fn = () => _getUncachedRecord({id});
|
|
216
|
+
return KEYSTORE_CONFIG_CACHE.memoize({key: id, fn});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Gets storage statistics for the given meter. This includes the total number
|
|
221
|
+
* of keystores and keys associated with a meter ID, represented as storage
|
|
222
|
+
* units according to this module's configuration.
|
|
223
|
+
*
|
|
224
|
+
* @param {object} options - The options to use.
|
|
225
|
+
* @param {string} options.meterId - The ID of the meter to get.
|
|
226
|
+
* @param {object} options.moduleManager - The KMS module manager to use.
|
|
227
|
+
* @param {AbortSignal} [options.signal] - An abort signal to check.
|
|
228
|
+
* @param {Function} [options.aggregate] - An aggregate function that will
|
|
229
|
+
* receive each keystore config that matches the `meterId` and the current
|
|
230
|
+
* usage; this function may be called to add custom additional usage
|
|
231
|
+
* associated with a keystore.
|
|
232
|
+
* @param {boolean} [options.explain=false] - An optional explain boolean.
|
|
233
|
+
*
|
|
234
|
+
* @returns {Promise<object | ExplainObject>} Resolves with the storage usage
|
|
235
|
+
* for the meter or an ExplainObject if `explain=true`.
|
|
236
|
+
*/
|
|
237
|
+
export async function getStorageUsage({
|
|
238
|
+
meterId, moduleManager, signal, aggregate, explain = false
|
|
239
|
+
} = {}) {
|
|
240
|
+
// find all keystores with the given meter ID
|
|
241
|
+
const cursor = await database.collections['kms-keystore'].find(
|
|
242
|
+
{'config.meterId': meterId},
|
|
243
|
+
{projection: {_id: 0, config: 1}});
|
|
244
|
+
|
|
245
|
+
if(explain) {
|
|
246
|
+
return cursor.explain('executionStats');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const {storageCost} = bedrock.config.kms;
|
|
250
|
+
const usage = {storage: 0};
|
|
251
|
+
const counters = [];
|
|
252
|
+
while(await cursor.hasNext()) {
|
|
253
|
+
// get next keystore config
|
|
254
|
+
const {config} = await cursor.next();
|
|
255
|
+
|
|
256
|
+
// add storage units for keystore
|
|
257
|
+
usage.storage += storageCost.keystore;
|
|
258
|
+
|
|
259
|
+
// if custom aggregator has been given, call it
|
|
260
|
+
if(aggregate) {
|
|
261
|
+
counters.push(() => {
|
|
262
|
+
_checkComputeStorageSignal({signal, meterId});
|
|
263
|
+
return aggregate({meterId, config, usage});
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// add storage units for keys in keystore
|
|
268
|
+
const {id: keystoreId, kmsModule} = config;
|
|
269
|
+
|
|
270
|
+
// get KMS module API and ensure its keys can be counted
|
|
271
|
+
const moduleApi = await moduleManager.get({id: kmsModule});
|
|
272
|
+
if(!(moduleApi && typeof moduleApi.getKeyCount === 'function')) {
|
|
273
|
+
throw new BedrockError(
|
|
274
|
+
'Bedrock KMS Module API is missing "getKeyCount()".',
|
|
275
|
+
'NotFoundError',
|
|
276
|
+
{kmsModule, httpStatusCode: 404, public: true});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// start counting keys in keystore
|
|
280
|
+
counters.push(() => {
|
|
281
|
+
_checkComputeStorageSignal({signal, meterId});
|
|
282
|
+
return _addKeyCount({usage, moduleApi, keystoreId, signal});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
_checkComputeStorageSignal({signal, meterId});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// await any counters that didn't complete
|
|
289
|
+
await pAll(counters, {concurrency: USAGE_COUNTER_MAX_CONCURRENCY});
|
|
290
|
+
|
|
291
|
+
return usage;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function _checkComputeStorageSignal({signal, meterId}) {
|
|
295
|
+
if(signal && signal.abort) {
|
|
296
|
+
throw new BedrockError(
|
|
297
|
+
'Computing metered storage aborted.',
|
|
298
|
+
'AbortError',
|
|
299
|
+
{meterId, httpStatusCode: 503, public: true});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function _addKeyCount({usage, moduleApi, keystoreId}) {
|
|
304
|
+
const {storageCost} = bedrock.config.kms;
|
|
305
|
+
const {count} = await moduleApi.getKeyCount({keystoreId});
|
|
306
|
+
usage.storage += count * storageCost.key;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// exported for testing purposes
|
|
310
|
+
export async function _getUncachedRecord({id, explain = false} = {}) {
|
|
311
|
+
const collection = database.collections['kms-keystore'];
|
|
312
|
+
const query = {'config.id': id};
|
|
313
|
+
const projection = {_id: 0, config: 1, meta: 1};
|
|
314
|
+
|
|
315
|
+
if(explain) {
|
|
316
|
+
// 'find().limit(1)' is used here because 'findOne()' doesn't return a
|
|
317
|
+
// cursor which allows the use of the explain function.
|
|
318
|
+
const cursor = await collection.find(query, {projection}).limit(1);
|
|
319
|
+
return cursor.explain('executionStats');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const record = await collection.findOne(query, {projection});
|
|
323
|
+
if(!record) {
|
|
324
|
+
throw new BedrockError(
|
|
325
|
+
'Keystore configuration not found.',
|
|
326
|
+
'NotFoundError',
|
|
327
|
+
{keystoreId: id, httpStatusCode: 404, public: true});
|
|
328
|
+
}
|
|
329
|
+
return record;
|
|
330
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bedrock/kms",
|
|
3
|
+
"version": "9.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Key management for Bedrock applications",
|
|
6
|
+
"main": "./lib/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"lint": "eslint ."
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/digitalbazaar/bedrock-kms"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"bedrock"
|
|
16
|
+
],
|
|
17
|
+
"author": {
|
|
18
|
+
"name": "Digital Bazaar, Inc.",
|
|
19
|
+
"email": "support@digitalbazaar.com",
|
|
20
|
+
"url": "https://digitalbazaar.com"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/digitalbazaar/bedrock-kms/issues"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=14"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/digitalbazaar/bedrock-kms",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@digitalbazaar/lru-memoize": "^2.0.0",
|
|
31
|
+
"p-all": "^4.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@bedrock/core": "^5.0.0",
|
|
35
|
+
"@bedrock/did-context": "^3.0.0",
|
|
36
|
+
"@bedrock/did-io": "^7.0.0",
|
|
37
|
+
"@bedrock/jsonld-document-loader": "^2.0.0",
|
|
38
|
+
"@bedrock/mongodb": "^9.0.0",
|
|
39
|
+
"@bedrock/package-manager": "^2.0.0",
|
|
40
|
+
"@bedrock/security-context": "^6.0.0",
|
|
41
|
+
"@bedrock/veres-one-context": "^13.0.0"
|
|
42
|
+
},
|
|
43
|
+
"directories": {
|
|
44
|
+
"lib": "./lib"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"eslint": "^7.32.0",
|
|
48
|
+
"eslint-config-digitalbazaar": "^2.8.0",
|
|
49
|
+
"eslint-plugin-jsdoc": "^37.9.7",
|
|
50
|
+
"jsdoc-to-markdown": "^7.1.1"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import {keystores} from '@bedrock/kms';
|
|
5
|
+
|
|
6
|
+
describe('keystores APIs', () => {
|
|
7
|
+
describe('insert API', () => {
|
|
8
|
+
it('throws error on missing config', async () => {
|
|
9
|
+
let err;
|
|
10
|
+
let result;
|
|
11
|
+
try {
|
|
12
|
+
result = await keystores.insert();
|
|
13
|
+
} catch(e) {
|
|
14
|
+
err = e;
|
|
15
|
+
}
|
|
16
|
+
should.not.exist(result);
|
|
17
|
+
should.exist(err);
|
|
18
|
+
err.message.should.contain('config (object) is required');
|
|
19
|
+
});
|
|
20
|
+
it('throws error on missing config.id', async () => {
|
|
21
|
+
let err;
|
|
22
|
+
let result;
|
|
23
|
+
const config = {};
|
|
24
|
+
try {
|
|
25
|
+
result = await keystores.insert({config});
|
|
26
|
+
} catch(e) {
|
|
27
|
+
err = e;
|
|
28
|
+
}
|
|
29
|
+
should.not.exist(result);
|
|
30
|
+
should.exist(err);
|
|
31
|
+
err.message.should.contain('config.id (string) is required');
|
|
32
|
+
});
|
|
33
|
+
it('throws error on missing config.controller', async () => {
|
|
34
|
+
let err;
|
|
35
|
+
let result;
|
|
36
|
+
const config = {
|
|
37
|
+
id: 'https://example.com/keystores/foo',
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
result = await keystores.insert({config});
|
|
41
|
+
} catch(e) {
|
|
42
|
+
err = e;
|
|
43
|
+
}
|
|
44
|
+
should.not.exist(result);
|
|
45
|
+
should.exist(err);
|
|
46
|
+
err.message.should.contain('config.controller (string) is required');
|
|
47
|
+
});
|
|
48
|
+
it('throws error on missing config.kmsModule', async () => {
|
|
49
|
+
let err;
|
|
50
|
+
let result;
|
|
51
|
+
const config = {
|
|
52
|
+
id: 'https://example.com/keystores/foo',
|
|
53
|
+
controller: 'bar',
|
|
54
|
+
};
|
|
55
|
+
try {
|
|
56
|
+
result = await keystores.insert({config});
|
|
57
|
+
} catch(e) {
|
|
58
|
+
err = e;
|
|
59
|
+
}
|
|
60
|
+
should.not.exist(result);
|
|
61
|
+
should.exist(err);
|
|
62
|
+
err.message.should.contain('config.kmsModule (string) is required');
|
|
63
|
+
});
|
|
64
|
+
it('throws error on missing config.sequence', async () => {
|
|
65
|
+
let err;
|
|
66
|
+
let result;
|
|
67
|
+
const config = {
|
|
68
|
+
id: 'https://example.com/keystores/foo',
|
|
69
|
+
controller: 'bar',
|
|
70
|
+
kmsModule: 'ssm-v1'
|
|
71
|
+
};
|
|
72
|
+
try {
|
|
73
|
+
result = await keystores.insert({config});
|
|
74
|
+
} catch(e) {
|
|
75
|
+
err = e;
|
|
76
|
+
}
|
|
77
|
+
should.not.exist(result);
|
|
78
|
+
should.exist(err);
|
|
79
|
+
err.message.should.contain('Keystore config sequence must be "0".');
|
|
80
|
+
});
|
|
81
|
+
it('throws error on negative config.sequence', async () => {
|
|
82
|
+
let err;
|
|
83
|
+
let result;
|
|
84
|
+
const config = {
|
|
85
|
+
id: 'https://example.com/keystores/foo',
|
|
86
|
+
controller: 'bar',
|
|
87
|
+
kmsModule: 'ssm-v1',
|
|
88
|
+
sequence: -1,
|
|
89
|
+
};
|
|
90
|
+
try {
|
|
91
|
+
result = await keystores.insert({config});
|
|
92
|
+
} catch(e) {
|
|
93
|
+
err = e;
|
|
94
|
+
}
|
|
95
|
+
should.not.exist(result);
|
|
96
|
+
should.exist(err);
|
|
97
|
+
err.message.should.contain('Keystore config sequence must be "0".');
|
|
98
|
+
});
|
|
99
|
+
it('throws error on float config.sequence', async () => {
|
|
100
|
+
let err;
|
|
101
|
+
let result;
|
|
102
|
+
const config = {
|
|
103
|
+
id: 'https://example.com/keystores/foo',
|
|
104
|
+
controller: 'bar',
|
|
105
|
+
kmsModule: 'ssm-v1',
|
|
106
|
+
sequence: 1.1,
|
|
107
|
+
};
|
|
108
|
+
try {
|
|
109
|
+
result = await keystores.insert({config});
|
|
110
|
+
} catch(e) {
|
|
111
|
+
err = e;
|
|
112
|
+
}
|
|
113
|
+
should.not.exist(result);
|
|
114
|
+
should.exist(err);
|
|
115
|
+
err.message.should.contain('Keystore config sequence must be "0".');
|
|
116
|
+
});
|
|
117
|
+
it('throws error on non-zero config.sequence', async () => {
|
|
118
|
+
let err;
|
|
119
|
+
let result;
|
|
120
|
+
const config = {
|
|
121
|
+
id: 'https://example.com/keystores/foo',
|
|
122
|
+
controller: 'bar',
|
|
123
|
+
kmsModule: 'ssm-v1',
|
|
124
|
+
sequence: 1,
|
|
125
|
+
};
|
|
126
|
+
try {
|
|
127
|
+
result = await keystores.insert({config});
|
|
128
|
+
} catch(e) {
|
|
129
|
+
err = e;
|
|
130
|
+
}
|
|
131
|
+
should.not.exist(result);
|
|
132
|
+
should.exist(err);
|
|
133
|
+
err.message.should.contain('Keystore config sequence must be "0".');
|
|
134
|
+
});
|
|
135
|
+
it('throws error on string config.sequence', async () => {
|
|
136
|
+
let err;
|
|
137
|
+
let result;
|
|
138
|
+
const config = {
|
|
139
|
+
id: 'https://example.com/keystores/foo',
|
|
140
|
+
controller: 'bar',
|
|
141
|
+
kmsModule: 'ssm-v1',
|
|
142
|
+
sequence: '0',
|
|
143
|
+
};
|
|
144
|
+
try {
|
|
145
|
+
result = await keystores.insert({config});
|
|
146
|
+
} catch(e) {
|
|
147
|
+
err = e;
|
|
148
|
+
}
|
|
149
|
+
should.not.exist(result);
|
|
150
|
+
should.exist(err);
|
|
151
|
+
err.message.should.contain('Keystore config sequence must be "0".');
|
|
152
|
+
});
|
|
153
|
+
it('throws error on non-string config.id', async () => {
|
|
154
|
+
let err;
|
|
155
|
+
let result;
|
|
156
|
+
const config = {
|
|
157
|
+
id: 1,
|
|
158
|
+
controller: 'bar',
|
|
159
|
+
kmsModule: 'ssm-v1',
|
|
160
|
+
sequence: '0',
|
|
161
|
+
};
|
|
162
|
+
try {
|
|
163
|
+
result = await keystores.insert({config});
|
|
164
|
+
} catch(e) {
|
|
165
|
+
err = e;
|
|
166
|
+
}
|
|
167
|
+
should.not.exist(result);
|
|
168
|
+
should.exist(err);
|
|
169
|
+
err.message.should.contain('config.id (string) is required');
|
|
170
|
+
});
|
|
171
|
+
it('throws error on non-string config.controller', async () => {
|
|
172
|
+
let err;
|
|
173
|
+
let result;
|
|
174
|
+
const config = {
|
|
175
|
+
id: 'https://example.com/keystores/foo',
|
|
176
|
+
controller: 1,
|
|
177
|
+
kmsModule: 'ssm-v1',
|
|
178
|
+
sequence: '0',
|
|
179
|
+
};
|
|
180
|
+
try {
|
|
181
|
+
result = await keystores.insert({config});
|
|
182
|
+
} catch(e) {
|
|
183
|
+
err = e;
|
|
184
|
+
}
|
|
185
|
+
should.not.exist(result);
|
|
186
|
+
should.exist(err);
|
|
187
|
+
err.message.should.contain('config.controller (string) is required');
|
|
188
|
+
});
|
|
189
|
+
it('successfully creates a keystore', async () => {
|
|
190
|
+
let err;
|
|
191
|
+
let result;
|
|
192
|
+
const config = {
|
|
193
|
+
id: 'https://example.com/keystores/foo',
|
|
194
|
+
controller: 'bar',
|
|
195
|
+
kmsModule: 'ssm-v1',
|
|
196
|
+
sequence: 0,
|
|
197
|
+
};
|
|
198
|
+
try {
|
|
199
|
+
result = await keystores.insert({config});
|
|
200
|
+
} catch(e) {
|
|
201
|
+
err = e;
|
|
202
|
+
}
|
|
203
|
+
assertNoError(err);
|
|
204
|
+
should.exist(result);
|
|
205
|
+
result.should.be.an('object');
|
|
206
|
+
result.should.have.property('config');
|
|
207
|
+
result.config.should.eql(config);
|
|
208
|
+
});
|
|
209
|
+
it('throws DuplicateError on duplicate keystore config', async () => {
|
|
210
|
+
let err;
|
|
211
|
+
let result;
|
|
212
|
+
const config = {
|
|
213
|
+
id:
|
|
214
|
+
'https://example.com/keystores/fbea027c-ecc4-4562-b3dc-392db7b7c7c6',
|
|
215
|
+
controller: 'bar',
|
|
216
|
+
kmsModule: 'ssm-v1',
|
|
217
|
+
sequence: 0,
|
|
218
|
+
};
|
|
219
|
+
try {
|
|
220
|
+
result = await keystores.insert({config});
|
|
221
|
+
} catch(e) {
|
|
222
|
+
err = e;
|
|
223
|
+
}
|
|
224
|
+
assertNoError(err);
|
|
225
|
+
should.exist(result);
|
|
226
|
+
result = undefined;
|
|
227
|
+
err = undefined;
|
|
228
|
+
try {
|
|
229
|
+
result = await keystores.insert({config});
|
|
230
|
+
} catch(e) {
|
|
231
|
+
err = e;
|
|
232
|
+
}
|
|
233
|
+
should.exist(err);
|
|
234
|
+
err.name.should.equal('DuplicateError');
|
|
235
|
+
});
|
|
236
|
+
it('throws DuplicateError on config with same controller and referenceId',
|
|
237
|
+
async () => {
|
|
238
|
+
// configs have unique IDs, but the same controller and referenceId
|
|
239
|
+
let err;
|
|
240
|
+
let result;
|
|
241
|
+
const keystoreConfig1 = {
|
|
242
|
+
id: 'https://example.com/keystores/fbea027c',
|
|
243
|
+
controller: 'bar',
|
|
244
|
+
kmsModule: 'ssm-v1',
|
|
245
|
+
referenceId: 'urn:uuid:72b89236-7bb7-4d00-8930-9c74c4a7a4a8',
|
|
246
|
+
sequence: 0,
|
|
247
|
+
};
|
|
248
|
+
try {
|
|
249
|
+
result = await keystores.insert({config: keystoreConfig1});
|
|
250
|
+
} catch(e) {
|
|
251
|
+
err = e;
|
|
252
|
+
}
|
|
253
|
+
assertNoError(err);
|
|
254
|
+
should.exist(result);
|
|
255
|
+
|
|
256
|
+
const keystoreConfig2 = {
|
|
257
|
+
id: 'https://example.com/keystores/4f398f8f',
|
|
258
|
+
controller: 'bar',
|
|
259
|
+
kmsModule: 'ssm-v1',
|
|
260
|
+
referenceId: 'urn:uuid:72b89236-7bb7-4d00-8930-9c74c4a7a4a8',
|
|
261
|
+
sequence: 0,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
result = undefined;
|
|
265
|
+
err = undefined;
|
|
266
|
+
try {
|
|
267
|
+
result = await keystores.insert({config: keystoreConfig2});
|
|
268
|
+
} catch(e) {
|
|
269
|
+
err = e;
|
|
270
|
+
}
|
|
271
|
+
should.exist(err);
|
|
272
|
+
err.name.should.equal('DuplicateError');
|
|
273
|
+
});
|
|
274
|
+
}); // end insert API
|
|
275
|
+
}); // end keystore APIs
|