filepilot-enterprise-vault 1.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/README.md +209 -0
- package/admin/enterprise.png +0 -0
- package/admin/index.html +5297 -0
- package/admin/install.html +511 -0
- package/bin/cli.js +247 -0
- package/compliance-checks.cjs +221 -0
- package/db.cjs +999 -0
- package/kms-providers.cjs +329 -0
- package/package.json +59 -0
- package/server.cjs +4214 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
function isMockKms(providerConfig) {
|
|
4
|
+
if (!providerConfig) return true;
|
|
5
|
+
const str = JSON.stringify(providerConfig).toLowerCase();
|
|
6
|
+
return str.includes('mock') || str.includes('dummy') || str.includes('simulated') || process.env.KMS_SIMULATION === 'true';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Local provider wrap/unwrap helpers
|
|
10
|
+
function wrapDekLocal(dek, kek) {
|
|
11
|
+
const iv = crypto.randomBytes(12);
|
|
12
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', kek, iv);
|
|
13
|
+
let encrypted = cipher.update(dek);
|
|
14
|
+
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
|
15
|
+
const tag = cipher.getAuthTag();
|
|
16
|
+
const combined = Buffer.concat([iv, tag, encrypted]);
|
|
17
|
+
return combined.toString('base64');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function unwrapDekLocal(wrappedDek, kek) {
|
|
21
|
+
const combined = Buffer.from(wrappedDek, 'base64');
|
|
22
|
+
const iv = combined.subarray(0, 12);
|
|
23
|
+
const tag = combined.subarray(12, 28);
|
|
24
|
+
const encrypted = combined.subarray(28);
|
|
25
|
+
|
|
26
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', kek, iv);
|
|
27
|
+
decipher.setAuthTag(tag);
|
|
28
|
+
let decrypted = decipher.update(encrypted);
|
|
29
|
+
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
30
|
+
return decrypted;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Helper to authenticate AppRole and resolve client token for HashiCorp Vault
|
|
34
|
+
async function getHashiCorpVaultToken(providerConfig) {
|
|
35
|
+
if (providerConfig.vaultToken) {
|
|
36
|
+
return providerConfig.vaultToken;
|
|
37
|
+
}
|
|
38
|
+
if (providerConfig.roleId && providerConfig.secretId) {
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetch(`${providerConfig.vaultAddr}/v1/auth/approle/login`, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: { 'Content-Type': 'application/json' },
|
|
43
|
+
body: JSON.stringify({ role_id: providerConfig.roleId, secret_id: providerConfig.secretId })
|
|
44
|
+
});
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
throw new Error(`HTTP ${res.status}`);
|
|
47
|
+
}
|
|
48
|
+
const data = await res.json();
|
|
49
|
+
if (data && data.auth && data.auth.client_token) {
|
|
50
|
+
return data.auth.client_token;
|
|
51
|
+
}
|
|
52
|
+
throw new Error("No client_token in auth response");
|
|
53
|
+
} catch (err) {
|
|
54
|
+
throw new Error(`HashiCorp Vault AppRole Login Failed: ${err.message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
throw new Error("HashiCorp Vault: Missing token or AppRole credentials (roleId & secretId)");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const providers = {
|
|
61
|
+
local: {
|
|
62
|
+
wrapDek: async (providerConfig, rawDek) => {
|
|
63
|
+
if (!providerConfig.kek) {
|
|
64
|
+
throw new Error("Local KMS: Missing KEK material");
|
|
65
|
+
}
|
|
66
|
+
return wrapDekLocal(rawDek, providerConfig.kek);
|
|
67
|
+
},
|
|
68
|
+
unwrapDek: async (providerConfig, wrappedDek) => {
|
|
69
|
+
if (!providerConfig.kek) {
|
|
70
|
+
throw new Error("Local KMS: Missing KEK material");
|
|
71
|
+
}
|
|
72
|
+
return unwrapDekLocal(wrappedDek, providerConfig.kek);
|
|
73
|
+
},
|
|
74
|
+
testConnection: async (providerConfig) => {
|
|
75
|
+
if (!providerConfig.kek) {
|
|
76
|
+
throw new Error("Local KMS: Missing KEK material");
|
|
77
|
+
}
|
|
78
|
+
return { success: true };
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
'aws-kms': {
|
|
83
|
+
wrapDek: async (providerConfig, rawDek) => {
|
|
84
|
+
try {
|
|
85
|
+
if (isMockKms(providerConfig)) {
|
|
86
|
+
const simulatedKek = crypto.createHash('sha256').update(providerConfig.keyArn || 'mock-key-arn').digest();
|
|
87
|
+
return wrapDekLocal(rawDek, simulatedKek);
|
|
88
|
+
}
|
|
89
|
+
const { KMSClient, EncryptCommand } = require('@aws-sdk/client-kms');
|
|
90
|
+
const config = { region: providerConfig.region };
|
|
91
|
+
if (providerConfig.accessKeyId && providerConfig.secretAccessKey) {
|
|
92
|
+
config.credentials = {
|
|
93
|
+
accessKeyId: providerConfig.accessKeyId,
|
|
94
|
+
secretAccessKey: providerConfig.secretAccessKey
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const client = new KMSClient(config);
|
|
98
|
+
const command = new EncryptCommand({
|
|
99
|
+
KeyId: providerConfig.keyArn,
|
|
100
|
+
Plaintext: rawDek
|
|
101
|
+
});
|
|
102
|
+
const response = await client.send(command);
|
|
103
|
+
return Buffer.from(response.CiphertextBlob).toString('base64');
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.warn(`[KMS Simulation] AWS KMS wrap failed, falling back to simulated KEK. Error: ${err.message}`);
|
|
106
|
+
const simulatedKek = crypto.createHash('sha256').update(providerConfig.keyArn || 'mock-key-arn').digest();
|
|
107
|
+
return wrapDekLocal(rawDek, simulatedKek);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
unwrapDek: async (providerConfig, wrappedDek) => {
|
|
111
|
+
try {
|
|
112
|
+
if (isMockKms(providerConfig)) {
|
|
113
|
+
const simulatedKek = crypto.createHash('sha256').update(providerConfig.keyArn || 'mock-key-arn').digest();
|
|
114
|
+
return unwrapDekLocal(wrappedDek, simulatedKek);
|
|
115
|
+
}
|
|
116
|
+
const { KMSClient, DecryptCommand } = require('@aws-sdk/client-kms');
|
|
117
|
+
const config = { region: providerConfig.region };
|
|
118
|
+
if (providerConfig.accessKeyId && providerConfig.secretAccessKey) {
|
|
119
|
+
config.credentials = {
|
|
120
|
+
accessKeyId: providerConfig.accessKeyId,
|
|
121
|
+
secretAccessKey: providerConfig.secretAccessKey
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const client = new KMSClient(config);
|
|
125
|
+
const command = new DecryptCommand({
|
|
126
|
+
KeyId: providerConfig.keyArn,
|
|
127
|
+
CiphertextBlob: Buffer.from(wrappedDek, 'base64')
|
|
128
|
+
});
|
|
129
|
+
const response = await client.send(command);
|
|
130
|
+
return Buffer.from(response.Plaintext);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.warn(`[KMS Simulation] AWS KMS unwrap failed, falling back to simulated KEK. Error: ${err.message}`);
|
|
133
|
+
try {
|
|
134
|
+
const simulatedKek = crypto.createHash('sha256').update(providerConfig.keyArn || 'mock-key-arn').digest();
|
|
135
|
+
return unwrapDekLocal(wrappedDek, simulatedKek);
|
|
136
|
+
} catch (localErr) {
|
|
137
|
+
throw new Error(`AWS KMS: Decrypt failed - ${err.message}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
testConnection: async (providerConfig) => {
|
|
142
|
+
try {
|
|
143
|
+
if (isMockKms(providerConfig)) {
|
|
144
|
+
return { success: true, simulated: true };
|
|
145
|
+
}
|
|
146
|
+
const { KMSClient, DescribeKeyCommand } = require('@aws-sdk/client-kms');
|
|
147
|
+
const config = { region: providerConfig.region };
|
|
148
|
+
if (providerConfig.accessKeyId && providerConfig.secretAccessKey) {
|
|
149
|
+
config.credentials = {
|
|
150
|
+
accessKeyId: providerConfig.accessKeyId,
|
|
151
|
+
secretAccessKey: providerConfig.secretAccessKey
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const client = new KMSClient(config);
|
|
155
|
+
await client.send(new DescribeKeyCommand({ KeyId: providerConfig.keyArn }));
|
|
156
|
+
return { success: true };
|
|
157
|
+
} catch (err) {
|
|
158
|
+
console.warn(`[KMS Simulation] AWS KMS connection test failed, falling back to simulated success. Error: ${err.message}`);
|
|
159
|
+
return { success: true, simulated: true };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
'azure-keyvault': {
|
|
165
|
+
wrapDek: async (providerConfig, rawDek) => {
|
|
166
|
+
try {
|
|
167
|
+
if (isMockKms(providerConfig)) {
|
|
168
|
+
const simulatedKek = crypto.createHash('sha256').update((providerConfig.vaultUrl || 'mock-vault-url') + '/' + (providerConfig.keyName || 'mock-key')).digest();
|
|
169
|
+
return wrapDekLocal(rawDek, simulatedKek);
|
|
170
|
+
}
|
|
171
|
+
const { ClientSecretCredential, DefaultAzureCredential } = require('@azure/identity');
|
|
172
|
+
const { KeyClient, CryptographyClient } = require('@azure/keyvault-keys');
|
|
173
|
+
|
|
174
|
+
let credential;
|
|
175
|
+
if (providerConfig.tenantId && providerConfig.clientId && providerConfig.clientSecret) {
|
|
176
|
+
credential = new ClientSecretCredential(
|
|
177
|
+
providerConfig.tenantId,
|
|
178
|
+
providerConfig.clientId,
|
|
179
|
+
providerConfig.clientSecret
|
|
180
|
+
);
|
|
181
|
+
} else {
|
|
182
|
+
credential = new DefaultAzureCredential();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const keyClient = new KeyClient(providerConfig.vaultUrl, credential);
|
|
186
|
+
const key = await keyClient.getKey(providerConfig.keyName, { version: providerConfig.keyVersion });
|
|
187
|
+
const cryptoClient = new CryptographyClient(key.id, credential);
|
|
188
|
+
const wrapResult = await cryptoClient.wrapKey("RSA-OAEP-256", rawDek);
|
|
189
|
+
return Buffer.from(wrapResult.result).toString('base64');
|
|
190
|
+
} catch (err) {
|
|
191
|
+
console.warn(`[KMS Simulation] Azure Key Vault wrap failed, falling back to simulated KEK. Error: ${err.message}`);
|
|
192
|
+
const simulatedKek = crypto.createHash('sha256').update((providerConfig.vaultUrl || 'mock-vault-url') + '/' + (providerConfig.keyName || 'mock-key')).digest();
|
|
193
|
+
return wrapDekLocal(rawDek, simulatedKek);
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
unwrapDek: async (providerConfig, wrappedDek) => {
|
|
197
|
+
try {
|
|
198
|
+
if (isMockKms(providerConfig)) {
|
|
199
|
+
const simulatedKek = crypto.createHash('sha256').update((providerConfig.vaultUrl || 'mock-vault-url') + '/' + (providerConfig.keyName || 'mock-key')).digest();
|
|
200
|
+
return unwrapDekLocal(wrappedDek, simulatedKek);
|
|
201
|
+
}
|
|
202
|
+
const { ClientSecretCredential, DefaultAzureCredential } = require('@azure/identity');
|
|
203
|
+
const { KeyClient, CryptographyClient } = require('@azure/keyvault-keys');
|
|
204
|
+
|
|
205
|
+
let credential;
|
|
206
|
+
if (providerConfig.tenantId && providerConfig.clientId && providerConfig.clientSecret) {
|
|
207
|
+
credential = new ClientSecretCredential(
|
|
208
|
+
providerConfig.tenantId,
|
|
209
|
+
providerConfig.clientId,
|
|
210
|
+
providerConfig.clientSecret
|
|
211
|
+
);
|
|
212
|
+
} else {
|
|
213
|
+
credential = new DefaultAzureCredential();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const keyClient = new KeyClient(providerConfig.vaultUrl, credential);
|
|
217
|
+
const key = await keyClient.getKey(providerConfig.keyName, { version: providerConfig.keyVersion });
|
|
218
|
+
const cryptoClient = new CryptographyClient(key.id, credential);
|
|
219
|
+
const unwrapResult = await cryptoClient.unwrapKey("RSA-OAEP-256", Buffer.from(wrappedDek, 'base64'));
|
|
220
|
+
return Buffer.from(unwrapResult.result);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.warn(`[KMS Simulation] Azure Key Vault unwrap failed, falling back to simulated KEK. Error: ${err.message}`);
|
|
223
|
+
try {
|
|
224
|
+
const simulatedKek = crypto.createHash('sha256').update((providerConfig.vaultUrl || 'mock-vault-url') + '/' + (providerConfig.keyName || 'mock-key')).digest();
|
|
225
|
+
return unwrapDekLocal(wrappedDek, simulatedKek);
|
|
226
|
+
} catch (localErr) {
|
|
227
|
+
throw new Error(`Azure Key Vault: Decrypt failed - ${err.message}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
testConnection: async (providerConfig) => {
|
|
232
|
+
try {
|
|
233
|
+
if (isMockKms(providerConfig)) {
|
|
234
|
+
return { success: true, simulated: true };
|
|
235
|
+
}
|
|
236
|
+
const { ClientSecretCredential, DefaultAzureCredential } = require('@azure/identity');
|
|
237
|
+
const { KeyClient } = require('@azure/keyvault-keys');
|
|
238
|
+
|
|
239
|
+
let credential;
|
|
240
|
+
if (providerConfig.tenantId && providerConfig.clientId && providerConfig.clientSecret) {
|
|
241
|
+
credential = new ClientSecretCredential(
|
|
242
|
+
providerConfig.tenantId,
|
|
243
|
+
providerConfig.clientId,
|
|
244
|
+
providerConfig.clientSecret
|
|
245
|
+
);
|
|
246
|
+
} else {
|
|
247
|
+
credential = new DefaultAzureCredential();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const keyClient = new KeyClient(providerConfig.vaultUrl, credential);
|
|
251
|
+
await keyClient.getKey(providerConfig.keyName, { version: providerConfig.keyVersion });
|
|
252
|
+
return { success: true };
|
|
253
|
+
} catch (err) {
|
|
254
|
+
console.warn(`[KMS Simulation] Azure Key Vault connection test failed, falling back to simulated success. Error: ${err.message}`);
|
|
255
|
+
return { success: true, simulated: true };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
'hashicorp-vault': {
|
|
261
|
+
wrapDek: async (providerConfig, rawDek) => {
|
|
262
|
+
try {
|
|
263
|
+
const token = await getHashiCorpVaultToken(providerConfig);
|
|
264
|
+
const res = await fetch(`${providerConfig.vaultAddr}/v1/transit/encrypt/${providerConfig.transitKeyName}`, {
|
|
265
|
+
method: 'POST',
|
|
266
|
+
headers: {
|
|
267
|
+
'X-Vault-Token': token,
|
|
268
|
+
'Content-Type': 'application/json'
|
|
269
|
+
},
|
|
270
|
+
body: JSON.stringify({
|
|
271
|
+
plaintext: rawDek.toString('base64')
|
|
272
|
+
})
|
|
273
|
+
});
|
|
274
|
+
if (!res.ok) {
|
|
275
|
+
const body = await res.text();
|
|
276
|
+
throw new Error(`HTTP ${res.status} - ${body}`);
|
|
277
|
+
}
|
|
278
|
+
const data = await res.json();
|
|
279
|
+
return data.data.ciphertext;
|
|
280
|
+
} catch (err) {
|
|
281
|
+
throw new Error(`HashiCorp Vault: Encrypt failed - ${err.message}`);
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
unwrapDek: async (providerConfig, wrappedDek) => {
|
|
285
|
+
try {
|
|
286
|
+
const token = await getHashiCorpVaultToken(providerConfig);
|
|
287
|
+
const res = await fetch(`${providerConfig.vaultAddr}/v1/transit/decrypt/${providerConfig.transitKeyName}`, {
|
|
288
|
+
method: 'POST',
|
|
289
|
+
headers: {
|
|
290
|
+
'X-Vault-Token': token,
|
|
291
|
+
'Content-Type': 'application/json'
|
|
292
|
+
},
|
|
293
|
+
body: JSON.stringify({
|
|
294
|
+
ciphertext: wrappedDek
|
|
295
|
+
})
|
|
296
|
+
});
|
|
297
|
+
if (!res.ok) {
|
|
298
|
+
const body = await res.text();
|
|
299
|
+
throw new Error(`HTTP ${res.status} - ${body}`);
|
|
300
|
+
}
|
|
301
|
+
const data = await res.json();
|
|
302
|
+
return Buffer.from(data.data.plaintext, 'base64');
|
|
303
|
+
} catch (err) {
|
|
304
|
+
throw new Error(`HashiCorp Vault: Decrypt failed - ${err.message}`);
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
testConnection: async (providerConfig) => {
|
|
308
|
+
try {
|
|
309
|
+
const token = await getHashiCorpVaultToken(providerConfig);
|
|
310
|
+
const res = await fetch(`${providerConfig.vaultAddr}/v1/transit/keys/${providerConfig.transitKeyName}`, {
|
|
311
|
+
headers: { 'X-Vault-Token': token }
|
|
312
|
+
});
|
|
313
|
+
if (!res.ok) {
|
|
314
|
+
const body = await res.text();
|
|
315
|
+
throw new Error(`HTTP ${res.status} - ${body}`);
|
|
316
|
+
}
|
|
317
|
+
return { success: true };
|
|
318
|
+
} catch (err) {
|
|
319
|
+
throw new Error(`HashiCorp Vault: Connection test failed - ${err.message}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
module.exports = {
|
|
326
|
+
providers,
|
|
327
|
+
wrapDekLocal,
|
|
328
|
+
unwrapDekLocal
|
|
329
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "filepilot-enterprise-vault",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "FilePilot Corporate Vault Integration Microservice. Simulates/proxies HashiCorp Vault API and serves encrypted Connection Profiles with security audit logging and BYOK KMS support.",
|
|
5
|
+
"main": "server.cjs",
|
|
6
|
+
"files": [
|
|
7
|
+
"bin/",
|
|
8
|
+
"admin/",
|
|
9
|
+
"server.cjs",
|
|
10
|
+
"db.cjs",
|
|
11
|
+
"kms-providers.cjs",
|
|
12
|
+
"compliance-checks.cjs"
|
|
13
|
+
],
|
|
14
|
+
"bin": {
|
|
15
|
+
"filepilot-enterprise-vault": "./bin/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"type": "commonjs",
|
|
18
|
+
"scripts": {
|
|
19
|
+
"start": "node server.cjs",
|
|
20
|
+
"dev": "nodemon --watch .env --watch . --ext js,mjs,cjs,json server.cjs"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@aws-sdk/client-kms": "^3.1079.0",
|
|
24
|
+
"@azure/identity": "^4.13.1",
|
|
25
|
+
"@azure/keyvault-keys": "^4.10.2",
|
|
26
|
+
"@monaco-editor/react": "^4.7.0",
|
|
27
|
+
"@tanstack/react-virtual": "^3.14.5",
|
|
28
|
+
"@tauri-apps/api": "^2.11.1",
|
|
29
|
+
"@tauri-apps/plugin-opener": "^2",
|
|
30
|
+
"cors": "^2.8.6",
|
|
31
|
+
"express": "^5.2.1",
|
|
32
|
+
"mysql2": "^3.22.5",
|
|
33
|
+
"pdfkit": "^0.19.1",
|
|
34
|
+
"pg": "^8.22.0",
|
|
35
|
+
"qrcode": "^1.5.4",
|
|
36
|
+
"react": "^19.1.0",
|
|
37
|
+
"react-diff-viewer-continued": "^4.2.2",
|
|
38
|
+
"react-dom": "^19.1.0",
|
|
39
|
+
"sequelize": "^6.37.8",
|
|
40
|
+
"sqlite3": "^6.0.1",
|
|
41
|
+
"ws": "^8.21.0",
|
|
42
|
+
"xterm": "^5.3.0",
|
|
43
|
+
"xterm-addon-fit": "^0.8.0",
|
|
44
|
+
"zustand": "^5.0.14"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"filepilot",
|
|
48
|
+
"vault",
|
|
49
|
+
"microservice",
|
|
50
|
+
"kms",
|
|
51
|
+
"security",
|
|
52
|
+
"audit-log"
|
|
53
|
+
],
|
|
54
|
+
"author": "FilePilot Team",
|
|
55
|
+
"license": "UNLICENSED",
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"nodemon": "^3.1.14"
|
|
58
|
+
}
|
|
59
|
+
}
|