@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
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @faizahmed/secret-keystore - Object-Based Operations
|
|
3
|
+
*
|
|
4
|
+
* Functions for encrypting/decrypting nested objects using path patterns.
|
|
5
|
+
* Supports ** pattern matching for any-depth selection.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { encryptKMSValue, decryptKMSValue, isAlreadyEncrypted } = require('./kms');
|
|
9
|
+
const { getAllPaths, filterPaths, getByPath, setByPath } = require('./path-matcher');
|
|
10
|
+
const { validateKmsKeyId, buildEncryptOptions, buildDecryptOptions } = require('./options');
|
|
11
|
+
const {
|
|
12
|
+
EncryptionError,
|
|
13
|
+
DecryptionError,
|
|
14
|
+
ENCRYPTION_ERROR_CODES,
|
|
15
|
+
DECRYPTION_ERROR_CODES
|
|
16
|
+
} = require('./errors');
|
|
17
|
+
|
|
18
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
19
|
+
// ENCRYPT OBJECT
|
|
20
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Encrypt values at selected paths in a nested object using AWS KMS
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} obj - Source object
|
|
26
|
+
* @param {string} kmsKeyId - KMS key ID (required)
|
|
27
|
+
* @param {Object} [options] - Options
|
|
28
|
+
* @param {Object} [options.aws] - AWS options
|
|
29
|
+
* @param {string[]} [options.paths] - Explicit paths to encrypt
|
|
30
|
+
* @param {string[]} [options.patterns] - Patterns (** only) to match
|
|
31
|
+
* @param {Object} [options.exclude] - Exclusions
|
|
32
|
+
* @param {Object} [options.skip] - Skip options
|
|
33
|
+
* @param {boolean} [options.continueOnError] - Continue on errors
|
|
34
|
+
* @param {Object} [options.output] - Output format options
|
|
35
|
+
* @returns {Promise<Object>} Result with encrypted object
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const result = await encryptKMSObject(config, kmsKeyId, {
|
|
39
|
+
* patterns: ['**.password', '**.secret_key'],
|
|
40
|
+
* exclude: { paths: ['kms.key_id'] }
|
|
41
|
+
* });
|
|
42
|
+
*/
|
|
43
|
+
async function encryptKMSObject(obj, kmsKeyId, options = {}) {
|
|
44
|
+
validateKmsKeyId(kmsKeyId);
|
|
45
|
+
|
|
46
|
+
const opts = buildEncryptOptions(options);
|
|
47
|
+
const logger = opts.logger;
|
|
48
|
+
|
|
49
|
+
// Deep clone the object
|
|
50
|
+
const resultObject = structuredClone(obj);
|
|
51
|
+
|
|
52
|
+
// Get all paths and filter based on selection
|
|
53
|
+
const allPaths = getAllPaths(obj);
|
|
54
|
+
const selectedPaths = filterPaths(allPaths, {
|
|
55
|
+
paths: options.paths,
|
|
56
|
+
patterns: options.patterns,
|
|
57
|
+
exclude: options.exclude
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
logger?.debug?.(
|
|
61
|
+
`[encryptKMSObject] Selected ${selectedPaths.length} paths from ${allPaths.length} total`
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const result = {
|
|
65
|
+
object: resultObject,
|
|
66
|
+
encrypted: [],
|
|
67
|
+
skipped: [],
|
|
68
|
+
failed: []
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const skipEmpty = opts.skip?.empty !== false;
|
|
72
|
+
const skipAlreadyEncrypted = opts.skip?.alreadyEncrypted !== false;
|
|
73
|
+
const continueOnError = opts.continueOnError === true;
|
|
74
|
+
|
|
75
|
+
for (const path of selectedPaths) {
|
|
76
|
+
const value = getByPath(obj, path);
|
|
77
|
+
|
|
78
|
+
// Only encrypt string values
|
|
79
|
+
if (typeof value !== 'string') {
|
|
80
|
+
logger?.debug?.(`[encryptKMSObject] Skipping non-string at ${path}`);
|
|
81
|
+
result.skipped.push(path);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Skip empty values
|
|
86
|
+
if (skipEmpty && (!value || value.trim() === '')) {
|
|
87
|
+
logger?.debug?.(`[encryptKMSObject] Skipping empty value at ${path}`);
|
|
88
|
+
result.skipped.push(path);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Skip already encrypted
|
|
93
|
+
if (skipAlreadyEncrypted && isAlreadyEncrypted(value)) {
|
|
94
|
+
logger?.debug?.(`[encryptKMSObject] Skipping already encrypted at ${path}`);
|
|
95
|
+
result.skipped.push(path);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const encrypted = await encryptKMSValue(value, kmsKeyId, opts);
|
|
101
|
+
setByPath(resultObject, path, encrypted);
|
|
102
|
+
result.encrypted.push(path);
|
|
103
|
+
logger?.info?.(`[encryptKMSObject] Encrypted: ${path}`);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (continueOnError) {
|
|
106
|
+
result.failed.push({ path, error });
|
|
107
|
+
logger?.warn?.(`[encryptKMSObject] Failed to encrypt ${path}: ${error.message}`);
|
|
108
|
+
} else {
|
|
109
|
+
throw new EncryptionError(
|
|
110
|
+
`Failed to encrypt path: ${path}`,
|
|
111
|
+
ENCRYPTION_ERROR_CODES.FAILED,
|
|
112
|
+
path,
|
|
113
|
+
error
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
logger?.info?.(
|
|
120
|
+
`[encryptKMSObject] Complete: ${result.encrypted.length} encrypted, ${result.skipped.length} skipped, ${result.failed.length} failed`
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
127
|
+
// DECRYPT OBJECT
|
|
128
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Decrypt values at selected paths in a nested object using AWS KMS
|
|
132
|
+
*
|
|
133
|
+
* @param {Object} obj - Source object with encrypted values
|
|
134
|
+
* @param {string} kmsKeyId - KMS key ID (required)
|
|
135
|
+
* @param {Object} [options] - Options
|
|
136
|
+
* @param {Object} [options.aws] - AWS options
|
|
137
|
+
* @param {Object} [options.attestation] - Attestation options
|
|
138
|
+
* @param {string[]} [options.paths] - Explicit paths to decrypt
|
|
139
|
+
* @param {string[]} [options.patterns] - Patterns (** only) to match
|
|
140
|
+
* @param {Object} [options.exclude] - Exclusions
|
|
141
|
+
* @param {Object} [options.skip] - Skip options
|
|
142
|
+
* @param {boolean} [options.continueOnError] - Continue on errors
|
|
143
|
+
* @returns {Promise<Object>} Result with decrypted object
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* const result = await decryptKMSObject(encryptedConfig, kmsKeyId, {
|
|
147
|
+
* attestation: { enabled: true, document: getAttestationDoc }
|
|
148
|
+
* });
|
|
149
|
+
*/
|
|
150
|
+
async function decryptKMSObject(obj, kmsKeyId, options = {}) {
|
|
151
|
+
validateKmsKeyId(kmsKeyId);
|
|
152
|
+
|
|
153
|
+
const opts = buildDecryptOptions(options);
|
|
154
|
+
const logger = opts.logger;
|
|
155
|
+
|
|
156
|
+
// Deep clone the object
|
|
157
|
+
const resultObject = structuredClone(obj);
|
|
158
|
+
|
|
159
|
+
// Get all paths and filter based on selection
|
|
160
|
+
const allPaths = getAllPaths(obj);
|
|
161
|
+
const selectedPaths = filterPaths(allPaths, {
|
|
162
|
+
paths: options.paths,
|
|
163
|
+
patterns: options.patterns,
|
|
164
|
+
exclude: options.exclude
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
logger?.debug?.(
|
|
168
|
+
`[decryptKMSObject] Selected ${selectedPaths.length} paths from ${allPaths.length} total`
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const result = {
|
|
172
|
+
object: resultObject,
|
|
173
|
+
decrypted: [],
|
|
174
|
+
skipped: [],
|
|
175
|
+
failed: []
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const skipUnencrypted = opts.skip?.unencrypted !== false;
|
|
179
|
+
const continueOnError = opts.continueOnError === true;
|
|
180
|
+
|
|
181
|
+
for (const path of selectedPaths) {
|
|
182
|
+
const value = getByPath(obj, path);
|
|
183
|
+
|
|
184
|
+
// Only decrypt string values
|
|
185
|
+
if (typeof value !== 'string') {
|
|
186
|
+
logger?.debug?.(`[decryptKMSObject] Skipping non-string at ${path}`);
|
|
187
|
+
result.skipped.push(path);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Skip unencrypted values
|
|
192
|
+
if (skipUnencrypted && !isAlreadyEncrypted(value)) {
|
|
193
|
+
logger?.debug?.(`[decryptKMSObject] Skipping unencrypted at ${path}`);
|
|
194
|
+
result.skipped.push(path);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const decrypted = await decryptKMSValue(value, kmsKeyId, opts);
|
|
200
|
+
setByPath(resultObject, path, decrypted);
|
|
201
|
+
result.decrypted.push(path);
|
|
202
|
+
logger?.info?.(`[decryptKMSObject] Decrypted: ${path}`);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (continueOnError) {
|
|
205
|
+
result.failed.push({ path, error });
|
|
206
|
+
logger?.warn?.(`[decryptKMSObject] Failed to decrypt ${path}: ${error.message}`);
|
|
207
|
+
} else {
|
|
208
|
+
throw new DecryptionError(
|
|
209
|
+
`Failed to decrypt path: ${path}`,
|
|
210
|
+
DECRYPTION_ERROR_CODES.FAILED,
|
|
211
|
+
path,
|
|
212
|
+
error
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
logger?.info?.(
|
|
219
|
+
`[decryptKMSObject] Complete: ${result.decrypted.length} decrypted, ${result.skipped.length} skipped, ${result.failed.length} failed`
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
226
|
+
// EXPORTS
|
|
227
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
228
|
+
|
|
229
|
+
module.exports = {
|
|
230
|
+
encryptKMSObject,
|
|
231
|
+
decryptKMSObject
|
|
232
|
+
};
|