@faizahmed/secret-keystore 1.1.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/LICENSE +21 -0
- package/README.md +1203 -0
- package/SECURITY.md +505 -0
- package/bin/cli.js +969 -0
- package/package.json +77 -0
- package/src/attestation/attestation-client.js +146 -0
- package/src/attestation/attestation-manager.js +339 -0
- package/src/attestation/cms-unwrap.js +166 -0
- package/src/attestation/index.js +66 -0
- package/src/attestation/key-pair.js +129 -0
- package/src/config.js +130 -0
- package/src/content-operations.js +494 -0
- package/src/errors.js +372 -0
- package/src/index.d.ts +641 -0
- package/src/index.js +438 -0
- package/src/keystore.js +678 -0
- package/src/kms.js +858 -0
- package/src/object-operations.js +232 -0
- package/src/options.js +541 -0
- package/src/path-matcher.js +319 -0
- package/src/rotate.js +92 -0
- package/src/yaml-utils.js +265 -0
package/src/errors.js
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @faizahmed/secret-keystore - Error Classes
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive error taxonomy with codes for proper handling.
|
|
5
|
+
* All errors extend SecretKeyStoreError as the base class.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
9
|
+
// BASE ERROR CLASS
|
|
10
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
11
|
+
|
|
12
|
+
class SecretKeyStoreError extends Error {
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} message - Error message
|
|
15
|
+
* @param {string} code - Error code
|
|
16
|
+
* @param {Error} [cause] - Original error that caused this
|
|
17
|
+
*/
|
|
18
|
+
constructor(message, code, cause) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'SecretKeyStoreError';
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.cause = cause;
|
|
23
|
+
this.timestamp = new Date();
|
|
24
|
+
Error.captureStackTrace(this, this.constructor);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
toJSON() {
|
|
28
|
+
return {
|
|
29
|
+
name: this.name,
|
|
30
|
+
code: this.code,
|
|
31
|
+
message: this.message,
|
|
32
|
+
timestamp: this.timestamp.toISOString(),
|
|
33
|
+
cause: this.cause
|
|
34
|
+
? {
|
|
35
|
+
name: this.cause.name,
|
|
36
|
+
message: this.cause.message
|
|
37
|
+
}
|
|
38
|
+
: undefined
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
44
|
+
// KMS ERRORS
|
|
45
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
46
|
+
|
|
47
|
+
class KmsError extends SecretKeyStoreError {
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} message - Error message
|
|
50
|
+
* @param {string} code - Error code
|
|
51
|
+
* @param {string} kmsKeyId - KMS key ID
|
|
52
|
+
* @param {Error} [cause] - Original error
|
|
53
|
+
*/
|
|
54
|
+
constructor(message, code, kmsKeyId, cause) {
|
|
55
|
+
super(message, code, cause);
|
|
56
|
+
this.name = 'KmsError';
|
|
57
|
+
this.kmsKeyId = kmsKeyId;
|
|
58
|
+
this.awsRequestId = cause?.['$metadata']?.requestId;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// KMS Error Codes
|
|
63
|
+
const KMS_ERROR_CODES = {
|
|
64
|
+
KEY_NOT_FOUND: 'KMS_KEY_NOT_FOUND',
|
|
65
|
+
KEY_DISABLED: 'KMS_KEY_DISABLED',
|
|
66
|
+
ACCESS_DENIED: 'KMS_ACCESS_DENIED',
|
|
67
|
+
INVALID_CIPHERTEXT: 'KMS_INVALID_CIPHERTEXT',
|
|
68
|
+
THROTTLED: 'KMS_THROTTLED',
|
|
69
|
+
ENCRYPT_FAILED: 'KMS_ENCRYPT_FAILED',
|
|
70
|
+
DECRYPT_FAILED: 'KMS_DECRYPT_FAILED',
|
|
71
|
+
CONNECTION_ERROR: 'KMS_CONNECTION_ERROR'
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
75
|
+
// ATTESTATION ERRORS
|
|
76
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
77
|
+
|
|
78
|
+
class AttestationError extends SecretKeyStoreError {
|
|
79
|
+
constructor(message, code, cause) {
|
|
80
|
+
super(message, code, cause);
|
|
81
|
+
this.name = 'AttestationError';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Attestation Error Codes
|
|
86
|
+
const ATTESTATION_ERROR_CODES = {
|
|
87
|
+
DOCUMENT_MISSING: 'ATTESTATION_DOCUMENT_MISSING',
|
|
88
|
+
DOCUMENT_INVALID: 'ATTESTATION_DOCUMENT_INVALID',
|
|
89
|
+
DOCUMENT_EXPIRED: 'ATTESTATION_DOCUMENT_EXPIRED',
|
|
90
|
+
GETTER_FAILED: 'ATTESTATION_GETTER_FAILED',
|
|
91
|
+
NOT_AVAILABLE: 'ATTESTATION_NOT_AVAILABLE',
|
|
92
|
+
RETRY_FAILED: 'ATTESTATION_RETRY_FAILED',
|
|
93
|
+
INIT_FAILED: 'ATTESTATION_INIT_FAILED',
|
|
94
|
+
CMS_UNWRAP_FAILED: 'ATTESTATION_CMS_UNWRAP_FAILED',
|
|
95
|
+
KEYPAIR_GENERATION_FAILED: 'ATTESTATION_KEYPAIR_GENERATION_FAILED',
|
|
96
|
+
ENDPOINT_UNREACHABLE: 'ATTESTATION_ENDPOINT_UNREACHABLE'
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
100
|
+
// CONTENT ERRORS
|
|
101
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
102
|
+
|
|
103
|
+
class ContentError extends SecretKeyStoreError {
|
|
104
|
+
/**
|
|
105
|
+
* @param {string} message - Error message
|
|
106
|
+
* @param {string} code - Error code
|
|
107
|
+
* @param {string} [format] - Content format (env, json, yaml)
|
|
108
|
+
* @param {Error} [cause] - Original error
|
|
109
|
+
*/
|
|
110
|
+
constructor(message, code, format, cause) {
|
|
111
|
+
super(message, code, cause);
|
|
112
|
+
this.name = 'ContentError';
|
|
113
|
+
this.format = format;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Content Error Codes
|
|
118
|
+
const CONTENT_ERROR_CODES = {
|
|
119
|
+
PARSE_FAILED: 'CONTENT_PARSE_FAILED',
|
|
120
|
+
INVALID_FORMAT: 'CONTENT_INVALID_FORMAT',
|
|
121
|
+
EMPTY_CONTENT: 'CONTENT_EMPTY',
|
|
122
|
+
SERIALIZATION_FAILED: 'CONTENT_SERIALIZATION_FAILED'
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
126
|
+
// PATH ERRORS
|
|
127
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
128
|
+
|
|
129
|
+
class PathError extends SecretKeyStoreError {
|
|
130
|
+
/**
|
|
131
|
+
* @param {string} message - Error message
|
|
132
|
+
* @param {string} code - Error code
|
|
133
|
+
* @param {string} [path] - The path that caused the error
|
|
134
|
+
* @param {Error} [cause] - Original error
|
|
135
|
+
*/
|
|
136
|
+
constructor(message, code, path, cause) {
|
|
137
|
+
super(message, code, cause);
|
|
138
|
+
this.name = 'PathError';
|
|
139
|
+
this.path = path;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Path Error Codes
|
|
144
|
+
const PATH_ERROR_CODES = {
|
|
145
|
+
NOT_FOUND: 'PATH_NOT_FOUND',
|
|
146
|
+
INVALID_PATTERN: 'PATH_INVALID_PATTERN',
|
|
147
|
+
ACCESS_DENIED: 'PATH_ACCESS_DENIED'
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
151
|
+
// ENCRYPTION ERRORS
|
|
152
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
153
|
+
|
|
154
|
+
class EncryptionError extends SecretKeyStoreError {
|
|
155
|
+
/**
|
|
156
|
+
* @param {string} message - Error message
|
|
157
|
+
* @param {string} code - Error code
|
|
158
|
+
* @param {string} [key] - The key that failed to encrypt
|
|
159
|
+
* @param {Error} [cause] - Original error
|
|
160
|
+
*/
|
|
161
|
+
constructor(message, code, key, cause) {
|
|
162
|
+
super(message, code, cause);
|
|
163
|
+
this.name = 'EncryptionError';
|
|
164
|
+
this.key = key;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Encryption Error Codes
|
|
169
|
+
const ENCRYPTION_ERROR_CODES = {
|
|
170
|
+
FAILED: 'ENCRYPTION_FAILED',
|
|
171
|
+
INVALID_VALUE: 'ENCRYPTION_INVALID_VALUE',
|
|
172
|
+
ALREADY_ENCRYPTED: 'ENCRYPTION_ALREADY_ENCRYPTED'
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
176
|
+
// DECRYPTION ERRORS
|
|
177
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
178
|
+
|
|
179
|
+
class DecryptionError extends SecretKeyStoreError {
|
|
180
|
+
/**
|
|
181
|
+
* @param {string} message - Error message
|
|
182
|
+
* @param {string} code - Error code
|
|
183
|
+
* @param {string} [key] - The key that failed to decrypt
|
|
184
|
+
* @param {Error} [cause] - Original error
|
|
185
|
+
*/
|
|
186
|
+
constructor(message, code, key, cause) {
|
|
187
|
+
super(message, code, cause);
|
|
188
|
+
this.name = 'DecryptionError';
|
|
189
|
+
this.key = key;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Decryption Error Codes
|
|
194
|
+
const DECRYPTION_ERROR_CODES = {
|
|
195
|
+
FAILED: 'DECRYPTION_FAILED',
|
|
196
|
+
INVALID_CIPHERTEXT: 'DECRYPTION_INVALID_CIPHERTEXT',
|
|
197
|
+
NOT_ENCRYPTED: 'DECRYPTION_NOT_ENCRYPTED'
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
201
|
+
// KEYSTORE ERRORS
|
|
202
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
203
|
+
|
|
204
|
+
class KeystoreError extends SecretKeyStoreError {
|
|
205
|
+
constructor(message, code, cause) {
|
|
206
|
+
super(message, code, cause);
|
|
207
|
+
this.name = 'KeystoreError';
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Keystore Error Codes
|
|
212
|
+
const KEYSTORE_ERROR_CODES = {
|
|
213
|
+
NOT_INITIALIZED: 'KEYSTORE_NOT_INITIALIZED',
|
|
214
|
+
ALREADY_INITIALIZED: 'KEYSTORE_ALREADY_INITIALIZED',
|
|
215
|
+
DESTROYED: 'KEYSTORE_DESTROYED',
|
|
216
|
+
SECRET_NOT_FOUND: 'SECRET_NOT_FOUND',
|
|
217
|
+
SECRET_EXPIRED: 'SECRET_EXPIRED',
|
|
218
|
+
ACCESS_LIMIT_EXCEEDED: 'SECRET_ACCESS_LIMIT_EXCEEDED',
|
|
219
|
+
INITIALIZATION_FAILED: 'KEYSTORE_INITIALIZATION_FAILED',
|
|
220
|
+
REFRESH_FAILED: 'KEYSTORE_REFRESH_FAILED'
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
224
|
+
// VALIDATION ERRORS
|
|
225
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
226
|
+
|
|
227
|
+
class ValidationError extends SecretKeyStoreError {
|
|
228
|
+
/**
|
|
229
|
+
* @param {string} message - Error message
|
|
230
|
+
* @param {string} code - Error code
|
|
231
|
+
* @param {string} [field] - The field that failed validation
|
|
232
|
+
* @param {Error} [cause] - Original error
|
|
233
|
+
*/
|
|
234
|
+
constructor(message, code, field, cause) {
|
|
235
|
+
super(message, code, cause);
|
|
236
|
+
this.name = 'ValidationError';
|
|
237
|
+
this.field = field;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Validation Error Codes
|
|
242
|
+
const VALIDATION_ERROR_CODES = {
|
|
243
|
+
REQUIRED_FIELD: 'VALIDATION_REQUIRED_FIELD',
|
|
244
|
+
INVALID_TYPE: 'VALIDATION_INVALID_TYPE',
|
|
245
|
+
INVALID_VALUE: 'VALIDATION_INVALID_VALUE',
|
|
246
|
+
INVALID_OPTIONS: 'VALIDATION_INVALID_OPTIONS',
|
|
247
|
+
KMS_KEY_REQUIRED: 'VALIDATION_KMS_KEY_REQUIRED',
|
|
248
|
+
PROCESS_ENV_LEAK: 'VALIDATION_PROCESS_ENV_LEAK'
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
252
|
+
// CONVENIENCE ERROR CLASSES
|
|
253
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
254
|
+
|
|
255
|
+
class InitializationError extends KeystoreError {
|
|
256
|
+
constructor(message, cause) {
|
|
257
|
+
super(message, KEYSTORE_ERROR_CODES.INITIALIZATION_FAILED, cause);
|
|
258
|
+
this.name = 'InitializationError';
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
class SecretNotFoundError extends KeystoreError {
|
|
263
|
+
constructor(key) {
|
|
264
|
+
super(`Secret not found: ${key}`, KEYSTORE_ERROR_CODES.SECRET_NOT_FOUND);
|
|
265
|
+
this.name = 'SecretNotFoundError';
|
|
266
|
+
this.secretKey = key;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
class NotInitializedError extends KeystoreError {
|
|
271
|
+
constructor() {
|
|
272
|
+
super(
|
|
273
|
+
'SecretKeyStore not initialized. Call initialize() first.',
|
|
274
|
+
KEYSTORE_ERROR_CODES.NOT_INITIALIZED
|
|
275
|
+
);
|
|
276
|
+
this.name = 'NotInitializedError';
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
281
|
+
// HELPER FUNCTIONS
|
|
282
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Check if an error is recoverable (can be retried)
|
|
286
|
+
* @param {Error} error - Error to check
|
|
287
|
+
* @returns {boolean}
|
|
288
|
+
*/
|
|
289
|
+
function isRecoverableError(error) {
|
|
290
|
+
if (!(error instanceof SecretKeyStoreError)) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const recoverableCodes = [
|
|
295
|
+
KMS_ERROR_CODES.KEY_DISABLED,
|
|
296
|
+
KMS_ERROR_CODES.THROTTLED,
|
|
297
|
+
KMS_ERROR_CODES.CONNECTION_ERROR,
|
|
298
|
+
ATTESTATION_ERROR_CODES.DOCUMENT_EXPIRED,
|
|
299
|
+
KEYSTORE_ERROR_CODES.SECRET_EXPIRED
|
|
300
|
+
];
|
|
301
|
+
|
|
302
|
+
return recoverableCodes.includes(error.code);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Create a KMS error from an AWS SDK error
|
|
307
|
+
* @param {Error} awsError - AWS SDK error
|
|
308
|
+
* @param {string} kmsKeyId - KMS key ID
|
|
309
|
+
* @param {string} operation - 'encrypt' or 'decrypt'
|
|
310
|
+
* @returns {KmsError}
|
|
311
|
+
*/
|
|
312
|
+
function createKmsErrorFromAws(awsError, kmsKeyId, operation = 'decrypt') {
|
|
313
|
+
const message = awsError.message || 'KMS operation failed';
|
|
314
|
+
const errorName = awsError.name || '';
|
|
315
|
+
|
|
316
|
+
let code;
|
|
317
|
+
if (errorName.includes('NotFoundException') || message.includes('not found')) {
|
|
318
|
+
code = KMS_ERROR_CODES.KEY_NOT_FOUND;
|
|
319
|
+
} else if (errorName.includes('DisabledException') || message.includes('disabled')) {
|
|
320
|
+
code = KMS_ERROR_CODES.KEY_DISABLED;
|
|
321
|
+
} else if (errorName.includes('AccessDeniedException') || message.includes('Access Denied')) {
|
|
322
|
+
code = KMS_ERROR_CODES.ACCESS_DENIED;
|
|
323
|
+
} else if (errorName.includes('InvalidCiphertextException') || message.includes('ciphertext')) {
|
|
324
|
+
code = KMS_ERROR_CODES.INVALID_CIPHERTEXT;
|
|
325
|
+
} else if (errorName.includes('ThrottlingException') || message.includes('throttl')) {
|
|
326
|
+
code = KMS_ERROR_CODES.THROTTLED;
|
|
327
|
+
} else if (operation === 'encrypt') {
|
|
328
|
+
code = KMS_ERROR_CODES.ENCRYPT_FAILED;
|
|
329
|
+
} else {
|
|
330
|
+
code = KMS_ERROR_CODES.DECRYPT_FAILED;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return new KmsError(message, code, kmsKeyId, awsError);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
337
|
+
// EXPORTS
|
|
338
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
339
|
+
|
|
340
|
+
module.exports = {
|
|
341
|
+
// Base class
|
|
342
|
+
SecretKeyStoreError,
|
|
343
|
+
|
|
344
|
+
// Specialized error classes
|
|
345
|
+
KmsError,
|
|
346
|
+
AttestationError,
|
|
347
|
+
ContentError,
|
|
348
|
+
PathError,
|
|
349
|
+
EncryptionError,
|
|
350
|
+
DecryptionError,
|
|
351
|
+
KeystoreError,
|
|
352
|
+
ValidationError,
|
|
353
|
+
|
|
354
|
+
// Convenience classes
|
|
355
|
+
InitializationError,
|
|
356
|
+
SecretNotFoundError,
|
|
357
|
+
NotInitializedError,
|
|
358
|
+
|
|
359
|
+
// Error codes
|
|
360
|
+
KMS_ERROR_CODES,
|
|
361
|
+
ATTESTATION_ERROR_CODES,
|
|
362
|
+
CONTENT_ERROR_CODES,
|
|
363
|
+
PATH_ERROR_CODES,
|
|
364
|
+
ENCRYPTION_ERROR_CODES,
|
|
365
|
+
DECRYPTION_ERROR_CODES,
|
|
366
|
+
KEYSTORE_ERROR_CODES,
|
|
367
|
+
VALIDATION_ERROR_CODES,
|
|
368
|
+
|
|
369
|
+
// Helper functions
|
|
370
|
+
isRecoverableError,
|
|
371
|
+
createKmsErrorFromAws
|
|
372
|
+
};
|