@pengzi/kms 1.1.1 → 1.3.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 +819 -22
- package/dist/client.d.ts +16 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +43 -2
- package/dist/client.js.map +1 -1
- package/dist/services/audit.service.d.ts +4 -0
- package/dist/services/audit.service.d.ts.map +1 -1
- package/dist/services/audit.service.js +16 -0
- package/dist/services/audit.service.js.map +1 -1
- package/dist/services/key.service.d.ts +14 -1
- package/dist/services/key.service.d.ts.map +1 -1
- package/dist/services/key.service.js +72 -13
- package/dist/services/key.service.js.map +1 -1
- package/dist/services/project.service.d.ts +14 -1
- package/dist/services/project.service.d.ts.map +1 -1
- package/dist/services/project.service.js +50 -0
- package/dist/services/project.service.js.map +1 -1
- package/dist/src/client.js +43 -2
- package/dist/src/services/audit.service.js +16 -0
- package/dist/src/services/key.service.js +72 -13
- package/dist/src/services/project.service.js +50 -0
- package/dist/types/client.types.d.ts +20 -0
- package/dist/types/client.types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ kms
|
|
|
65
65
|
选择: 密钥管理 → 获取密钥值
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
详细 CLI 使用说明请查看:[CLI
|
|
68
|
+
详细 CLI 使用说明请查看:[CLI 工具指南](./docs/guides/cli-guide.md)
|
|
69
69
|
|
|
70
70
|
### 方式 2: 使用 Node.js SDK
|
|
71
71
|
|
|
@@ -179,9 +179,330 @@ auditLogs.logs.forEach(log => {
|
|
|
179
179
|
});
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
+
## 完整使用指南
|
|
183
|
+
|
|
184
|
+
### 连接配置
|
|
185
|
+
|
|
186
|
+
#### MongoDB Atlas(推荐,自动 TLS)
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
const kms = new KMSClient({
|
|
190
|
+
connectionString: 'mongodb+srv://user:pass@cluster.mongodb.net',
|
|
191
|
+
databaseName: 'kms'
|
|
192
|
+
});
|
|
193
|
+
await kms.connect();
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### 自建 MongoDB + TLS
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
const kms = new KMSClient({
|
|
200
|
+
connectionString: 'mongodb://localhost:27017',
|
|
201
|
+
databaseName: 'kms',
|
|
202
|
+
connectionOptions: {
|
|
203
|
+
tls: true,
|
|
204
|
+
tlsCAFile: '/etc/ssl/mongodb/ca.pem'
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
await kms.connect();
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### 开发环境(本地 MongoDB)
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
const kms = new KMSClient({
|
|
214
|
+
connectionString: 'mongodb://localhost:27017',
|
|
215
|
+
databaseName: 'kms'
|
|
216
|
+
});
|
|
217
|
+
await kms.connect();
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 用户管理
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// 创建用户
|
|
224
|
+
const user = await kms.createUser(projectId, {
|
|
225
|
+
username: 'developer1',
|
|
226
|
+
password: 'UserPassword123!',
|
|
227
|
+
roles: ['developer'] // 可选: admin, operator, developer, readonly, auditor
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// 授予角色
|
|
231
|
+
await kms.grantRole(projectId, user.userId, 'operator');
|
|
232
|
+
|
|
233
|
+
// 撤销角色
|
|
234
|
+
await kms.revokeRole(projectId, user.userId, 'developer');
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 密钥管理完整流程
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// 1. 创建密钥
|
|
241
|
+
const key = await kms.createKey(projectId, masterPassword, {
|
|
242
|
+
keyName: 'production-db',
|
|
243
|
+
keyType: KeyType.MONGODB,
|
|
244
|
+
value: 'mongodb://user:pass@host:27017/db',
|
|
245
|
+
tags: ['production', 'database'],
|
|
246
|
+
description: '生产环境主数据库',
|
|
247
|
+
expiresAt: new Date('2025-12-31') // 可选:设置过期时间
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// 2. 列出密钥(分页)
|
|
251
|
+
const { keys, total } = await kms.listKeys(projectId, {
|
|
252
|
+
keyType: KeyType.MONGODB,
|
|
253
|
+
tags: ['production']
|
|
254
|
+
}, {
|
|
255
|
+
page: 1,
|
|
256
|
+
limit: 20
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// 3. 更新密钥
|
|
260
|
+
const updatedKey = await kms.updateKey(
|
|
261
|
+
projectId,
|
|
262
|
+
masterPassword,
|
|
263
|
+
key.keyId,
|
|
264
|
+
{
|
|
265
|
+
description: '更新的描述',
|
|
266
|
+
tags: ['production', 'database', 'important']
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
// 4. 删除密钥
|
|
271
|
+
await kms.deleteKey(projectId, key.keyId);
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### 审计日志查询
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// 查看最近日志
|
|
278
|
+
const recentLogs = await kms.getRecentLogs(projectId, 50);
|
|
279
|
+
|
|
280
|
+
// 高级查询
|
|
281
|
+
const auditLogs = await kms.getAuditLogs(projectId, {
|
|
282
|
+
page: 1,
|
|
283
|
+
limit: 20,
|
|
284
|
+
action: 'CREATE_KEY', // 可选:过滤操作类型
|
|
285
|
+
userId: 'user123', // 可选:过滤用户
|
|
286
|
+
startDate: new Date('2025-01-01'),
|
|
287
|
+
endDate: new Date('2025-12-31')
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
console.log('总记录数:', auditLogs.total);
|
|
291
|
+
console.log('当前页:', auditLogs.page);
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### 项目管理
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// 列出所有项目
|
|
298
|
+
const projects = await kms.listProjects();
|
|
299
|
+
|
|
300
|
+
// 获取项目详情
|
|
301
|
+
const project = await kms.getProject(projectId);
|
|
302
|
+
|
|
303
|
+
// 删除项目(会删除所有密钥)
|
|
304
|
+
await kms.deleteProject(projectId);
|
|
305
|
+
```
|
|
306
|
+
|
|
182
307
|
## 高级用法
|
|
183
308
|
|
|
184
|
-
### Express集成示例
|
|
309
|
+
### Express 集成示例
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
import express from 'express';
|
|
313
|
+
import { KMSClient, KeyType } from '@pengzi/kms';
|
|
314
|
+
|
|
315
|
+
const app = express();
|
|
316
|
+
app.use(express.json());
|
|
317
|
+
|
|
318
|
+
const kms = new KMSClient({
|
|
319
|
+
connectionString: process.env.MONGODB_URI,
|
|
320
|
+
databaseName: 'kms'
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
await kms.connect();
|
|
324
|
+
|
|
325
|
+
// 中间件:设置当前用户
|
|
326
|
+
app.use((req, res, next) => {
|
|
327
|
+
kms.setCurrentUser(req.user?.id || 'api-user');
|
|
328
|
+
next();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// API: 创建密钥
|
|
332
|
+
app.post('/api/keys', async (req, res) => {
|
|
333
|
+
try {
|
|
334
|
+
const { projectId, masterPassword, keyName, keyType, value } = req.body;
|
|
335
|
+
|
|
336
|
+
const key = await kms.createKey(projectId, masterPassword, {
|
|
337
|
+
keyName,
|
|
338
|
+
keyType,
|
|
339
|
+
value,
|
|
340
|
+
tags: ['api-created']
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
res.json({ success: true, keyId: key.keyId });
|
|
344
|
+
} catch (error) {
|
|
345
|
+
res.status(400).json({ error: error.message });
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// API: 获取密钥
|
|
350
|
+
app.get('/api/keys/:keyId', async (req, res) => {
|
|
351
|
+
try {
|
|
352
|
+
const { projectId, masterPassword } = req.headers;
|
|
353
|
+
const { keyId } = req.params;
|
|
354
|
+
|
|
355
|
+
const keyValue = await kms.getKey(
|
|
356
|
+
projectId,
|
|
357
|
+
masterPassword,
|
|
358
|
+
keyId
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
res.json({ value: keyValue.value });
|
|
362
|
+
} catch (error) {
|
|
363
|
+
res.status(404).json({ error: error.message });
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// API: 列出密钥
|
|
368
|
+
app.get('/api/keys', async (req, res) => {
|
|
369
|
+
try {
|
|
370
|
+
const { projectId } = req.query;
|
|
371
|
+
const { keys, total } = await kms.listKeys(projectId);
|
|
372
|
+
|
|
373
|
+
res.json({ keys, total });
|
|
374
|
+
} catch (error) {
|
|
375
|
+
res.status(500).json({ error: error.message });
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
app.listen(3000, () => {
|
|
380
|
+
console.log('KMS API server running on port 3000');
|
|
381
|
+
});
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### 环境变量配置
|
|
385
|
+
|
|
386
|
+
创建 `.env` 文件:
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
# MongoDB 连接
|
|
390
|
+
MONGODB_URI=mongodb+srv://user:pass@cluster.mongodb.net
|
|
391
|
+
KMS_DATABASE=kms
|
|
392
|
+
|
|
393
|
+
# 或者使用加密连接
|
|
394
|
+
KMS_ENCRYPTED_CONNECTION={"encrypted":"..."}
|
|
395
|
+
|
|
396
|
+
# 私钥(如果使用加密连接)
|
|
397
|
+
KMS_PRIVATE_KEY_FILE=/path/to/private-key.pem
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
使用环境变量:
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
import dotenv from 'dotenv';
|
|
404
|
+
dotenv.config();
|
|
405
|
+
|
|
406
|
+
const kms = new KMSClient({
|
|
407
|
+
connectionString: process.env.MONGODB_URI,
|
|
408
|
+
databaseName: process.env.KMS_DATABASE || 'kms'
|
|
409
|
+
});
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### 使用加密连接字符串
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
import {
|
|
416
|
+
loadEncryptedConfig,
|
|
417
|
+
createClientFromEncryptedConfig
|
|
418
|
+
} from '@pengzi/kms';
|
|
419
|
+
|
|
420
|
+
// 从配置文件加载加密的连接字符串
|
|
421
|
+
const config = loadEncryptedConfig('./config/encrypted-db.json');
|
|
422
|
+
|
|
423
|
+
// 创建客户端
|
|
424
|
+
const kms = await createClientFromEncryptedConfig(config);
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### 错误处理最佳实践
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
import {
|
|
431
|
+
KMSClient,
|
|
432
|
+
KeyNotFoundError,
|
|
433
|
+
AuthenticationError,
|
|
434
|
+
ForbiddenError,
|
|
435
|
+
ValidationError,
|
|
436
|
+
ProjectNotFoundError
|
|
437
|
+
} from '@pengzi/kms';
|
|
438
|
+
|
|
439
|
+
async function getKeySafely(
|
|
440
|
+
kms: KMSClient,
|
|
441
|
+
projectId: string,
|
|
442
|
+
masterPassword: string,
|
|
443
|
+
keyId: string
|
|
444
|
+
) {
|
|
445
|
+
try {
|
|
446
|
+
const keyValue = await kms.getKey(projectId, masterPassword, keyId);
|
|
447
|
+
return { success: true, value: keyValue.value };
|
|
448
|
+
} catch (error) {
|
|
449
|
+
if (error instanceof KeyNotFoundError) {
|
|
450
|
+
return { success: false, error: '密钥不存在' };
|
|
451
|
+
} else if (error instanceof AuthenticationError) {
|
|
452
|
+
return { success: false, error: '主密码错误' };
|
|
453
|
+
} else if (error instanceof ForbiddenError) {
|
|
454
|
+
return { success: false, error: '权限不足' };
|
|
455
|
+
} else if (error instanceof ValidationError) {
|
|
456
|
+
return { success: false, error: '数据验证失败' };
|
|
457
|
+
} else if (error instanceof ProjectNotFoundError) {
|
|
458
|
+
return { success: false, error: '项目不存在' };
|
|
459
|
+
} else {
|
|
460
|
+
return { success: false, error: '未知错误' };
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// 使用
|
|
466
|
+
const result = await getKeySafely(kms, projectId, masterPassword, keyId);
|
|
467
|
+
if (result.success) {
|
|
468
|
+
console.log('密钥值:', result.value);
|
|
469
|
+
} else {
|
|
470
|
+
console.error('错误:', result.error);
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### TypeScript 支持
|
|
475
|
+
|
|
476
|
+
KMS 提供完整的 TypeScript 类型定义:
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
import {
|
|
480
|
+
KMSClient,
|
|
481
|
+
KeyType,
|
|
482
|
+
Role,
|
|
483
|
+
KeyStatus,
|
|
484
|
+
Permission
|
|
485
|
+
} from '@pengzi/kms';
|
|
486
|
+
|
|
487
|
+
// 类型安全的密钥创建
|
|
488
|
+
const keyData: CreateKeyData = {
|
|
489
|
+
keyName: 'my-redis',
|
|
490
|
+
keyType: KeyType.REDIS,
|
|
491
|
+
value: 'redis://localhost:6379',
|
|
492
|
+
tags: ['cache'],
|
|
493
|
+
description: 'Redis 缓存'
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// 类型安全的项目创建
|
|
497
|
+
const projectOptions: CreateProjectOptions = {
|
|
498
|
+
projectName: 'my-app',
|
|
499
|
+
masterPassword: 'StrongPassword123!',
|
|
500
|
+
metadata: {
|
|
501
|
+
environment: 'production',
|
|
502
|
+
team: 'backend'
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
```
|
|
185
506
|
|
|
186
507
|
```typescript
|
|
187
508
|
import express from 'express';
|
|
@@ -227,24 +548,56 @@ app.listen(3000);
|
|
|
227
548
|
```
|
|
228
549
|
|
|
229
550
|
更多示例请查看:
|
|
230
|
-
- [
|
|
231
|
-
- [
|
|
232
|
-
- [
|
|
551
|
+
- [📊 可视化流程指南](./docs/kms-visual-guide.html) - 交互式完整流程说明 ⭐ 推荐
|
|
552
|
+
- [快速入门](./docs/getting-started/quickstart.md)
|
|
553
|
+
- [CLI 工具指南](./docs/guides/cli-guide.md)
|
|
554
|
+
- [API 使用示例](./docs/guides/api-usage.md)
|
|
555
|
+
|
|
556
|
+
## 文档
|
|
233
557
|
|
|
234
|
-
|
|
558
|
+
完整文档请查看:[文档导航](./docs/README.md)
|
|
235
559
|
|
|
236
|
-
|
|
560
|
+
### 核心文档
|
|
561
|
+
- [📊 可视化流程指南](./docs/kms-visual-guide.html) - 交互式完整流程说明 ⭐ 推荐
|
|
562
|
+
- [安装指南](./docs/getting-started/installation.md) - 详细的安装步骤
|
|
563
|
+
- [5分钟快速入门](./docs/getting-started/quickstart.md) - 快速上手指南
|
|
564
|
+
- [CLI 工具指南](./docs/guides/cli-guide.md) - 交互式命令行工具
|
|
565
|
+
- [TLS/SSL 配置流程](./docs/guides/tls-workflow.md) - TLS 配置完整流程 ⭐ 推荐
|
|
566
|
+
- [API 参考](./docs/api/reference.md) - 完整的 API 文档
|
|
237
567
|
|
|
238
568
|
## 安全最佳实践
|
|
239
569
|
|
|
240
570
|
为了确保系统的安全性,请遵循以下最佳实践:
|
|
241
571
|
|
|
242
|
-
1.
|
|
572
|
+
1. **使用 TLS/SSL 连接** ⚠️ 重要
|
|
573
|
+
- 生产环境**必须**使用 TLS/SSL 加密连接
|
|
574
|
+
- MongoDB Atlas 默认启用 TLS,连接字符串使用 `mongodb+srv://`
|
|
575
|
+
- 自建 MongoDB 需配置 TLS 证书
|
|
576
|
+
```javascript
|
|
577
|
+
// MongoDB Atlas(自动启用 TLS)
|
|
578
|
+
const kms = new KMSClient({
|
|
579
|
+
connectionString: 'mongodb+srv://user:pass@cluster.mongodb.net/kms',
|
|
580
|
+
databaseName: 'kms'
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// 自建 MongoDB + TLS
|
|
584
|
+
const kms = new KMSClient({
|
|
585
|
+
connectionString: 'mongodb://localhost:27017/kms',
|
|
586
|
+
databaseName: 'kms',
|
|
587
|
+
connectionOptions: {
|
|
588
|
+
tls: true,
|
|
589
|
+
tlsCAFile: '/path/to/ca.pem'
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
```
|
|
593
|
+
详细说明请查看:[TLS 连接指南](./docs/TLS_GUIDE.md)
|
|
594
|
+
|
|
595
|
+
2. **主密码安全**
|
|
243
596
|
- 使用至少12个字符的强密码
|
|
244
597
|
- 包含大小写字母、数字和特殊字符
|
|
245
598
|
- 定期轮换主密码(建议每180天)
|
|
246
599
|
|
|
247
|
-
|
|
600
|
+
3. **密钥轮换**
|
|
248
601
|
- 定期轮换数据库连接凭证(建议每90天)
|
|
249
602
|
- 设置密钥过期时间
|
|
250
603
|
|
|
@@ -263,7 +616,7 @@ app.listen(3000);
|
|
|
263
616
|
- 监控失败的登录尝试
|
|
264
617
|
- 设置安全告警
|
|
265
618
|
|
|
266
|
-
完整的安全指南请查看:[
|
|
619
|
+
完整的安全指南请查看:[安全最佳实践](./docs/security/best-practices.md)
|
|
267
620
|
|
|
268
621
|
## 支持的密钥类型
|
|
269
622
|
|
|
@@ -283,36 +636,430 @@ app.listen(3000);
|
|
|
283
636
|
| `readonly` | 只读用户 | 列出密钥 |
|
|
284
637
|
| `auditor` | 审计员 | 查看审计日志 |
|
|
285
638
|
|
|
639
|
+
## 实际应用场景
|
|
640
|
+
|
|
641
|
+
### 场景 1: 微服务架构中的密钥管理
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
// 服务 A: 创建密钥
|
|
645
|
+
const serviceA = async () => {
|
|
646
|
+
const kms = new KMSClient({
|
|
647
|
+
connectionString: process.env.MONGODB_URI,
|
|
648
|
+
databaseName: 'kms'
|
|
649
|
+
});
|
|
650
|
+
await kms.connect();
|
|
651
|
+
|
|
652
|
+
const project = await kms.createProject('microservices-app', masterPassword);
|
|
653
|
+
|
|
654
|
+
// 为每个服务创建密钥
|
|
655
|
+
await kms.createKey(project.projectId, masterPassword, {
|
|
656
|
+
keyName: 'user-service-db',
|
|
657
|
+
keyType: KeyType.MONGODB,
|
|
658
|
+
value: process.env.USER_DB_URL,
|
|
659
|
+
tags: ['user-service', 'database']
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
await kms.createKey(project.projectId, masterPassword, {
|
|
663
|
+
keyName: 'order-service-db',
|
|
664
|
+
keyType: KeyType.MONGODB,
|
|
665
|
+
value: process.env.ORDER_DB_URL,
|
|
666
|
+
tags: ['order-service', 'database']
|
|
667
|
+
});
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
// 服务 B: 获取密钥
|
|
671
|
+
const serviceB = async () => {
|
|
672
|
+
const kms = new KMSClient({
|
|
673
|
+
connectionString: process.env.MONGODB_URI,
|
|
674
|
+
databaseName: 'kms'
|
|
675
|
+
});
|
|
676
|
+
await kms.connect();
|
|
677
|
+
|
|
678
|
+
// 只获取自己需要的密钥
|
|
679
|
+
const { keys } = await kms.listKeys(projectId, {
|
|
680
|
+
tags: ['user-service']
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
keys.forEach(async (key) => {
|
|
684
|
+
const keyValue = await kms.getKey(projectId, masterPassword, key.keyId);
|
|
685
|
+
// 使用连接字符串初始化数据库连接
|
|
686
|
+
await connectToDatabase(keyValue.value);
|
|
687
|
+
});
|
|
688
|
+
};
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
### 场景 2: 多环境部署
|
|
692
|
+
|
|
693
|
+
```typescript
|
|
694
|
+
const environments = ['development', 'staging', 'production'];
|
|
695
|
+
|
|
696
|
+
for (const env of environments) {
|
|
697
|
+
const projectName = `myapp-${env}`;
|
|
698
|
+
const project = await kms.createProject(projectName, masterPassword);
|
|
699
|
+
|
|
700
|
+
// 为每个环境创建相应的密钥
|
|
701
|
+
await kms.createKey(project.projectId, masterPassword, {
|
|
702
|
+
keyName: 'database',
|
|
703
|
+
keyType: KeyType.MONGODB,
|
|
704
|
+
value: envDatabases[env],
|
|
705
|
+
tags: [env, 'database']
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
### 场景 3: 密钥轮换
|
|
711
|
+
|
|
712
|
+
```typescript
|
|
713
|
+
async function rotateKey(projectId: string, keyId: string, masterPassword: string) {
|
|
714
|
+
const kms = new KMSClient({ /* config */ });
|
|
715
|
+
await kms.connect();
|
|
716
|
+
|
|
717
|
+
// 1. 获取旧密钥值(如果需要)
|
|
718
|
+
const oldValue = await kms.getKey(projectId, masterPassword, keyId);
|
|
719
|
+
|
|
720
|
+
// 2. 更新为新值
|
|
721
|
+
const newValue = generateNewConnectionString();
|
|
722
|
+
await kms.updateKey(projectId, masterPassword, keyId, {
|
|
723
|
+
value: newValue
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
// 3. 通知相关服务(使用消息队列)
|
|
727
|
+
await notifyServicesKeyRotated(keyId, newValue);
|
|
728
|
+
|
|
729
|
+
console.log(`密钥 ${keyId} 已轮换`);
|
|
730
|
+
}
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
### 场景 4: 访问控制
|
|
734
|
+
|
|
735
|
+
```typescript
|
|
736
|
+
// 为不同团队创建不同的项目
|
|
737
|
+
const frontendTeam = await kms.createProject('frontend-app', masterPassword);
|
|
738
|
+
const backendTeam = await kms.createProject('backend-app', masterPassword);
|
|
739
|
+
|
|
740
|
+
// 创建不同权限的用户
|
|
741
|
+
const frontendDev = await kms.createUser(frontendTeam.projectId, {
|
|
742
|
+
username: 'frontend-dev',
|
|
743
|
+
password: 'Password123!',
|
|
744
|
+
roles: ['developer'] // 只读权限
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
const backendAdmin = await kms.createUser(backendTeam.projectId, {
|
|
748
|
+
username: 'backend-admin',
|
|
749
|
+
password: 'AdminPass123!',
|
|
750
|
+
roles: ['admin'] // 完全权限
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
// 前端开发者尝试访问后端项目(会失败)
|
|
754
|
+
try {
|
|
755
|
+
await kms.createKey(backendTeam.projectId, masterPassword, {
|
|
756
|
+
keyName: 'test-key',
|
|
757
|
+
keyType: KeyType.CUSTOM,
|
|
758
|
+
value: 'test-value'
|
|
759
|
+
});
|
|
760
|
+
} catch (error) {
|
|
761
|
+
console.error('权限被拒绝');
|
|
762
|
+
}
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
## CLI 工具使用
|
|
766
|
+
|
|
767
|
+
### 安装和启动
|
|
768
|
+
|
|
769
|
+
```bash
|
|
770
|
+
# 全局安装
|
|
771
|
+
npm install -g @pengzi/kms
|
|
772
|
+
|
|
773
|
+
# 启动 CLI
|
|
774
|
+
kms
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
### 配置文件
|
|
778
|
+
|
|
779
|
+
创建 `~/.kms/config.json`:
|
|
780
|
+
|
|
781
|
+
```json
|
|
782
|
+
{
|
|
783
|
+
"connectionString": "mongodb://localhost:27017",
|
|
784
|
+
"databaseName": "kms"
|
|
785
|
+
}
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
### 常用命令
|
|
789
|
+
|
|
790
|
+
```bash
|
|
791
|
+
# 创建项目
|
|
792
|
+
选择: 1 (项目管理) → 1 (创建新项目)
|
|
793
|
+
|
|
794
|
+
# 创建密钥
|
|
795
|
+
选择: 2 (密钥管理) → 1 (创建密钥)
|
|
796
|
+
|
|
797
|
+
# 获取密钥值
|
|
798
|
+
选择: 2 (密钥管理) → 3 (获取密钥值)
|
|
799
|
+
|
|
800
|
+
# 查看审计日志
|
|
801
|
+
选择: 4 (审计日志) → 1 (查看最近日志)
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
详细的 CLI 使用说明请查看:[CLI 工具指南](./docs/guides/cli-guide.md)
|
|
805
|
+
|
|
286
806
|
## 错误处理
|
|
287
807
|
|
|
288
808
|
```typescript
|
|
289
809
|
import {
|
|
290
|
-
|
|
291
|
-
ProjectNotFoundError,
|
|
810
|
+
KMSClient,
|
|
292
811
|
KeyNotFoundError,
|
|
293
812
|
AuthenticationError,
|
|
294
813
|
ForbiddenError,
|
|
295
|
-
ValidationError
|
|
296
|
-
|
|
814
|
+
ValidationError,
|
|
815
|
+
ProjectNotFoundError
|
|
816
|
+
} from '@pengzi/kms';
|
|
297
817
|
|
|
298
818
|
try {
|
|
299
819
|
const key = await kms.getKey(projectId, masterPassword, keyId);
|
|
300
820
|
} catch (error) {
|
|
301
821
|
if (error instanceof KeyNotFoundError) {
|
|
302
|
-
console.error('
|
|
822
|
+
console.error('密钥不存在');
|
|
303
823
|
} else if (error instanceof AuthenticationError) {
|
|
304
|
-
console.error('
|
|
824
|
+
console.error('主密码错误或用户未认证');
|
|
305
825
|
} else if (error instanceof ForbiddenError) {
|
|
306
|
-
console.error('
|
|
826
|
+
console.error('权限不足');
|
|
827
|
+
} else if (error instanceof ValidationError) {
|
|
828
|
+
console.error('数据验证失败:', error.message);
|
|
829
|
+
} else if (error instanceof ProjectNotFoundError) {
|
|
830
|
+
console.error('项目不存在');
|
|
307
831
|
} else {
|
|
308
|
-
console.error('
|
|
832
|
+
console.error('未知错误:', error.message);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
## 最佳实践
|
|
838
|
+
|
|
839
|
+
### 1. 密钥命名规范
|
|
840
|
+
|
|
841
|
+
```typescript
|
|
842
|
+
// 好的命名 ✅
|
|
843
|
+
const goodKeyNames = [
|
|
844
|
+
'production-mongodb-primary',
|
|
845
|
+
'staging-redis-cache',
|
|
846
|
+
'dev-postgresql-analytics',
|
|
847
|
+
'stripe-api-key'
|
|
848
|
+
];
|
|
849
|
+
|
|
850
|
+
// 不好的命名 ❌
|
|
851
|
+
const badKeyNames = [
|
|
852
|
+
'db', // 太模糊
|
|
853
|
+
'key1', // 无意义
|
|
854
|
+
'test', // 不明确
|
|
855
|
+
'a' // 太短
|
|
856
|
+
];
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
### 2. 标签使用
|
|
860
|
+
|
|
861
|
+
```typescript
|
|
862
|
+
// 使用有意义的标签
|
|
863
|
+
await kms.createKey(projectId, masterPassword, {
|
|
864
|
+
keyName: 'mongodb-primary',
|
|
865
|
+
keyType: KeyType.MONGODB,
|
|
866
|
+
value: connectionString,
|
|
867
|
+
tags: [
|
|
868
|
+
'production', // 环境
|
|
869
|
+
'database', // 类型
|
|
870
|
+
'mongodb', // 技术
|
|
871
|
+
'primary', // 用途
|
|
872
|
+
'critical' // 重要级别
|
|
873
|
+
]
|
|
874
|
+
});
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
### 3. 主密码管理
|
|
878
|
+
|
|
879
|
+
```typescript
|
|
880
|
+
// ❌ 不要硬编码主密码
|
|
881
|
+
const masterPassword = 'password123';
|
|
882
|
+
|
|
883
|
+
// ❌ 不要将主密码存储在代码仓库
|
|
884
|
+
const masterPassword = fs.readFileSync('password.txt');
|
|
885
|
+
|
|
886
|
+
// ✅ 使用环境变量
|
|
887
|
+
const masterPassword = process.env.MASTER_PASSWORD;
|
|
888
|
+
|
|
889
|
+
// ✅ 或使用密钥管理服务
|
|
890
|
+
const masterPassword = await getKeyFromVault();
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
### 4. 连接池配置
|
|
894
|
+
|
|
895
|
+
```typescript
|
|
896
|
+
const kms = new KMSClient({
|
|
897
|
+
connectionString: 'mongodb://localhost:27017',
|
|
898
|
+
databaseName: 'kms',
|
|
899
|
+
connectionOptions: {
|
|
900
|
+
maxPoolSize: 50, // 最大连接数
|
|
901
|
+
minPoolSize: 10, // 最小连接数
|
|
902
|
+
connectTimeoutMS: 10000, // 连接超时
|
|
903
|
+
socketTimeoutMS: 30000, // Socket 超时
|
|
904
|
+
serverSelectionTimeoutMS: 5000 // 服务器选择超时
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
### 5. 错误处理和重试
|
|
910
|
+
|
|
911
|
+
```typescript
|
|
912
|
+
async function getKeyWithRetry(
|
|
913
|
+
kms: KMSClient,
|
|
914
|
+
projectId: string,
|
|
915
|
+
masterPassword: string,
|
|
916
|
+
keyId: string,
|
|
917
|
+
maxRetries = 3
|
|
918
|
+
) {
|
|
919
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
920
|
+
try {
|
|
921
|
+
return await kms.getKey(projectId, masterPassword, keyId);
|
|
922
|
+
} catch (error) {
|
|
923
|
+
if (i === maxRetries - 1) throw error;
|
|
924
|
+
|
|
925
|
+
// 等待后重试
|
|
926
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
## 性能优化
|
|
933
|
+
|
|
934
|
+
### 1. 批量操作
|
|
935
|
+
|
|
936
|
+
```typescript
|
|
937
|
+
// 批量创建密钥
|
|
938
|
+
const keysToCreate = [
|
|
939
|
+
{ name: 'db1', type: KeyType.MONGODB, value: '...' },
|
|
940
|
+
{ name: 'db2', type: KeyType.MONGODB, value: '...' },
|
|
941
|
+
{ name: 'cache', type: KeyType.REDIS, value: '...' }
|
|
942
|
+
];
|
|
943
|
+
|
|
944
|
+
const promises = keysToCreate.map(keyData =>
|
|
945
|
+
kms.createKey(projectId, masterPassword, keyData)
|
|
946
|
+
);
|
|
947
|
+
|
|
948
|
+
const results = await Promise.all(promises);
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
### 2. 分页查询
|
|
952
|
+
|
|
953
|
+
```typescript
|
|
954
|
+
// 使用分页减少内存使用
|
|
955
|
+
const pageSize = 100;
|
|
956
|
+
let page = 1;
|
|
957
|
+
let hasMore = true;
|
|
958
|
+
|
|
959
|
+
while (hasMore) {
|
|
960
|
+
const { keys, total } = await kms.listKeys(projectId, {}, {
|
|
961
|
+
page,
|
|
962
|
+
limit: pageSize
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
// 处理当前页的密钥
|
|
966
|
+
keys.forEach(key => {
|
|
967
|
+
// 处理密钥
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
// 检查是否还有更多数据
|
|
971
|
+
hasMore = page * pageSize < total;
|
|
972
|
+
page++;
|
|
973
|
+
}
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
### 3. 连接复用
|
|
977
|
+
|
|
978
|
+
```typescript
|
|
979
|
+
// ❌ 不好的做法:每次操作都创建新连接
|
|
980
|
+
async function badExample() {
|
|
981
|
+
const kms1 = new KMSClient({ /* config */ });
|
|
982
|
+
await kms1.connect();
|
|
983
|
+
await kms1.createKey(...);
|
|
984
|
+
await kms1.disconnect();
|
|
985
|
+
|
|
986
|
+
const kms2 = new KMSClient({ /* config */ });
|
|
987
|
+
await kms2.connect();
|
|
988
|
+
await kms2.getKey(...);
|
|
989
|
+
await kms2.disconnect();
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// ✅ 好的做法:复用连接
|
|
993
|
+
class KMSManager {
|
|
994
|
+
private client: KMSClient;
|
|
995
|
+
|
|
996
|
+
constructor() {
|
|
997
|
+
this.client = new KMSClient({ /* config */ });
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
async init() {
|
|
1001
|
+
await this.client.connect();
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
async createKey(...args: any[]) {
|
|
1005
|
+
return await this.client.createKey(...args);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
async destroy() {
|
|
1009
|
+
await this.client.disconnect();
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
## 安全注意事项
|
|
1015
|
+
|
|
1016
|
+
### 敏感信息处理
|
|
1017
|
+
|
|
1018
|
+
```typescript
|
|
1019
|
+
// ❌ 不要在日志中记录密钥值
|
|
1020
|
+
console.log('密钥值:', keyValue.value);
|
|
1021
|
+
|
|
1022
|
+
// ❌ 不要在错误消息中暴露密钥值
|
|
1023
|
+
throw new Error(`密钥 ${keyValue.value} 创建失败`);
|
|
1024
|
+
|
|
1025
|
+
// ✅ 只记录必要信息
|
|
1026
|
+
console.log('密钥 ID:', key.keyId);
|
|
1027
|
+
console.log('密钥名称:', key.keyName);
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
### 审计和监控
|
|
1031
|
+
|
|
1032
|
+
```typescript
|
|
1033
|
+
// 定期检查审计日志
|
|
1034
|
+
async function securityAudit(kms: KMSClient, projectId: string) {
|
|
1035
|
+
const logs = await kms.getAuditLogs(projectId, {
|
|
1036
|
+
limit: 1000
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
// 检查异常活动
|
|
1040
|
+
const suspiciousActivities = logs.logs.filter(log => {
|
|
1041
|
+
return !log.details.success ||
|
|
1042
|
+
log.action === 'PERMISSION_DENIED' ||
|
|
1043
|
+
log.action === 'LOGIN_FAILED';
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
if (suspiciousActivities.length > 0) {
|
|
1047
|
+
console.warn('发现可疑活动:', suspiciousActivities);
|
|
1048
|
+
// 发送告警
|
|
1049
|
+
await sendAlert(suspiciousActivities);
|
|
309
1050
|
}
|
|
310
1051
|
}
|
|
311
1052
|
```
|
|
312
1053
|
|
|
313
1054
|
## 开发
|
|
314
1055
|
|
|
1056
|
+
### 本地开发
|
|
1057
|
+
|
|
315
1058
|
```bash
|
|
1059
|
+
# 克隆项目
|
|
1060
|
+
git clone https://github.com/pengzi/kms.git
|
|
1061
|
+
cd kms
|
|
1062
|
+
|
|
316
1063
|
# 安装依赖
|
|
317
1064
|
npm install
|
|
318
1065
|
|
|
@@ -329,6 +1076,32 @@ npm run lint
|
|
|
329
1076
|
npm run format
|
|
330
1077
|
```
|
|
331
1078
|
|
|
1079
|
+
### 运行测试
|
|
1080
|
+
|
|
1081
|
+
```bash
|
|
1082
|
+
# 单元测试
|
|
1083
|
+
npm test
|
|
1084
|
+
|
|
1085
|
+
# 测试覆盖率
|
|
1086
|
+
npm run test:coverage
|
|
1087
|
+
|
|
1088
|
+
# 集成测试
|
|
1089
|
+
cd test-kms
|
|
1090
|
+
node simple-test.js
|
|
1091
|
+
```
|
|
1092
|
+
|
|
1093
|
+
### 贡献指南
|
|
1094
|
+
|
|
1095
|
+
我们欢迎所有形式的贡献!
|
|
1096
|
+
|
|
1097
|
+
1. Fork 项目
|
|
1098
|
+
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
|
|
1099
|
+
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
|
1100
|
+
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
|
1101
|
+
5. 创建 Pull Request
|
|
1102
|
+
|
|
1103
|
+
详细贡献指南请查看:[贡献指南](./docs/development/contributing.md)
|
|
1104
|
+
|
|
332
1105
|
## 项目结构
|
|
333
1106
|
|
|
334
1107
|
```
|
|
@@ -352,10 +1125,34 @@ kms/
|
|
|
352
1125
|
|
|
353
1126
|
## 许可证
|
|
354
1127
|
|
|
355
|
-
MIT
|
|
1128
|
+
MIT License
|
|
1129
|
+
|
|
1130
|
+
Copyright (c) 2025 pzdemons
|
|
1131
|
+
|
|
1132
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
1133
|
+
|
|
1134
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
1135
|
+
|
|
1136
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
356
1137
|
|
|
357
1138
|
## 联系方式
|
|
358
1139
|
|
|
359
|
-
-
|
|
360
|
-
-
|
|
361
|
-
-
|
|
1140
|
+
- **作者**: pzdemons
|
|
1141
|
+
- **包名**: @pengzi/kms
|
|
1142
|
+
- **GitHub**: [pengzi/kms](https://github.com/pengzi/kms)
|
|
1143
|
+
- **问题反馈**: [GitHub Issues](https://github.com/pengzi/kms/issues)
|
|
1144
|
+
- **安全问题**: 请通过私有渠道报告
|
|
1145
|
+
|
|
1146
|
+
## 相关资源
|
|
1147
|
+
|
|
1148
|
+
- [完整文档](./docs/README.md)
|
|
1149
|
+
- [API 参考](./docs/api/reference.md)
|
|
1150
|
+
- [更新日志](./docs/about/changelog.md)
|
|
1151
|
+
- [测试报告](./docs/about/test-reports/)
|
|
1152
|
+
|
|
1153
|
+
---
|
|
1154
|
+
|
|
1155
|
+
**当前版本**: v1.2.0
|
|
1156
|
+
**最后更新**: 2025-05-15
|
|
1157
|
+
**Node.js**: >= 18.0.0
|
|
1158
|
+
**MongoDB**: >= 4.4
|