@shipfox/api-secrets 2.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/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +116 -0
- package/LICENSE +21 -0
- package/README.md +207 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -0
- package/dist/core/crypto.d.ts +25 -0
- package/dist/core/crypto.d.ts.map +1 -0
- package/dist/core/crypto.js +85 -0
- package/dist/core/crypto.js.map +1 -0
- package/dist/core/dek-manager.d.ts +16 -0
- package/dist/core/dek-manager.d.ts.map +1 -0
- package/dist/core/dek-manager.js +85 -0
- package/dist/core/dek-manager.js.map +1 -0
- package/dist/core/errors.d.ts +53 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +86 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/fingerprint.d.ts +2 -0
- package/dist/core/fingerprint.d.ts.map +1 -0
- package/dist/core/fingerprint.js +9 -0
- package/dist/core/fingerprint.js.map +1 -0
- package/dist/core/index.d.ts +30 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +97 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/key-provider.d.ts +17 -0
- package/dist/core/key-provider.d.ts.map +1 -0
- package/dist/core/key-provider.js +45 -0
- package/dist/core/key-provider.js.map +1 -0
- package/dist/core/local-secret-store.d.ts +19 -0
- package/dist/core/local-secret-store.d.ts.map +1 -0
- package/dist/core/local-secret-store.js +40 -0
- package/dist/core/local-secret-store.js.map +1 -0
- package/dist/core/management.d.ts +37 -0
- package/dist/core/management.d.ts.map +1 -0
- package/dist/core/management.js +403 -0
- package/dist/core/management.js.map +1 -0
- package/dist/core/rotate-kek.d.ts +10 -0
- package/dist/core/rotate-kek.d.ts.map +1 -0
- package/dist/core/rotate-kek.js +94 -0
- package/dist/core/rotate-kek.js.map +1 -0
- package/dist/core/secret-store.d.ts +33 -0
- package/dist/core/secret-store.d.ts.map +1 -0
- package/dist/core/secret-store.js +209 -0
- package/dist/core/secret-store.js.map +1 -0
- package/dist/core/store-resolver.d.ts +18 -0
- package/dist/core/store-resolver.d.ts.map +1 -0
- package/dist/core/store-resolver.js +10 -0
- package/dist/core/store-resolver.js.map +1 -0
- package/dist/core/store-validation.d.ts +11 -0
- package/dist/core/store-validation.d.ts.map +1 -0
- package/dist/core/store-validation.js +30 -0
- package/dist/core/store-validation.js.map +1 -0
- package/dist/core/value-cipher.d.ts +10 -0
- package/dist/core/value-cipher.d.ts.map +1 -0
- package/dist/core/value-cipher.js +22 -0
- package/dist/core/value-cipher.js.map +1 -0
- package/dist/core/variable-store.d.ts +24 -0
- package/dist/core/variable-store.d.ts.map +1 -0
- package/dist/core/variable-store.js +194 -0
- package/dist/core/variable-store.js.map +1 -0
- package/dist/db/cap.d.ts +8 -0
- package/dist/db/cap.d.ts.map +1 -0
- package/dist/db/cap.js +39 -0
- package/dist/db/cap.js.map +1 -0
- package/dist/db/data-keys.d.ts +24 -0
- package/dist/db/data-keys.d.ts.map +1 -0
- package/dist/db/data-keys.js +61 -0
- package/dist/db/data-keys.js.map +1 -0
- package/dist/db/db.d.ts +1254 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +24 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/index.d.ts +13 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +15 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/management.d.ts +50 -0
- package/dist/db/management.d.ts.map +1 -0
- package/dist/db/management.js +127 -0
- package/dist/db/management.js.map +1 -0
- package/dist/db/schema/common.d.ts +3 -0
- package/dist/db/schema/common.d.ts.map +1 -0
- package/dist/db/schema/common.js +8 -0
- package/dist/db/schema/common.js.map +1 -0
- package/dist/db/schema/data-keys.d.ts +103 -0
- package/dist/db/schema/data-keys.d.ts.map +1 -0
- package/dist/db/schema/data-keys.js +24 -0
- package/dist/db/schema/data-keys.js.map +1 -0
- package/dist/db/schema/outbox.d.ts +195 -0
- package/dist/db/schema/outbox.d.ts.map +1 -0
- package/dist/db/schema/outbox.js +5 -0
- package/dist/db/schema/outbox.js.map +1 -0
- package/dist/db/schema/values.d.ts +193 -0
- package/dist/db/schema/values.d.ts.map +1 -0
- package/dist/db/schema/values.js +43 -0
- package/dist/db/schema/values.js.map +1 -0
- package/dist/db/schema/variables.d.ts +175 -0
- package/dist/db/schema/variables.d.ts.map +1 -0
- package/dist/db/schema/variables.js +41 -0
- package/dist/db/schema/variables.js.map +1 -0
- package/dist/db/scope.d.ts +20 -0
- package/dist/db/scope.d.ts.map +1 -0
- package/dist/db/scope.js +19 -0
- package/dist/db/scope.js.map +1 -0
- package/dist/db/values.d.ts +33 -0
- package/dist/db/values.d.ts.map +1 -0
- package/dist/db/values.js +119 -0
- package/dist/db/values.js.map +1 -0
- package/dist/db/variables.d.ts +32 -0
- package/dist/db/variables.d.ts.map +1 -0
- package/dist/db/variables.js +117 -0
- package/dist/db/variables.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/index.d.ts +3 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +4 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +39 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +151 -0
- package/dist/metrics/instance.js.map +1 -0
- package/dist/metrics/service.d.ts +2 -0
- package/dist/metrics/service.d.ts.map +1 -0
- package/dist/metrics/service.js +21 -0
- package/dist/metrics/service.js.map +1 -0
- package/dist/presentation/dto/index.d.ts +2 -0
- package/dist/presentation/dto/index.d.ts.map +1 -0
- package/dist/presentation/dto/index.js +3 -0
- package/dist/presentation/dto/index.js.map +1 -0
- package/dist/presentation/dto/management.d.ts +7 -0
- package/dist/presentation/dto/management.d.ts.map +1 -0
- package/dist/presentation/dto/management.js +32 -0
- package/dist/presentation/dto/management.js.map +1 -0
- package/dist/presentation/e2eRoutes/create-secret.d.ts +2 -0
- package/dist/presentation/e2eRoutes/create-secret.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/create-secret.js +34 -0
- package/dist/presentation/e2eRoutes/create-secret.js.map +1 -0
- package/dist/presentation/e2eRoutes/create-variable.d.ts +2 -0
- package/dist/presentation/e2eRoutes/create-variable.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/create-variable.js +34 -0
- package/dist/presentation/e2eRoutes/create-variable.js.map +1 -0
- package/dist/presentation/e2eRoutes/index.d.ts +3 -0
- package/dist/presentation/e2eRoutes/index.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/index.js +11 -0
- package/dist/presentation/e2eRoutes/index.js.map +1 -0
- package/dist/presentation/routes/auth.d.ts +16 -0
- package/dist/presentation/routes/auth.d.ts.map +1 -0
- package/dist/presentation/routes/auth.js +42 -0
- package/dist/presentation/routes/auth.js.map +1 -0
- package/dist/presentation/routes/batch-secrets.d.ts +2 -0
- package/dist/presentation/routes/batch-secrets.d.ts.map +1 -0
- package/dist/presentation/routes/batch-secrets.js +44 -0
- package/dist/presentation/routes/batch-secrets.js.map +1 -0
- package/dist/presentation/routes/batch-variables.d.ts +2 -0
- package/dist/presentation/routes/batch-variables.d.ts.map +1 -0
- package/dist/presentation/routes/batch-variables.js +44 -0
- package/dist/presentation/routes/batch-variables.js.map +1 -0
- package/dist/presentation/routes/cursor.d.ts +3 -0
- package/dist/presentation/routes/cursor.d.ts.map +1 -0
- package/dist/presentation/routes/cursor.js +19 -0
- package/dist/presentation/routes/cursor.js.map +1 -0
- package/dist/presentation/routes/delete-secret.d.ts +2 -0
- package/dist/presentation/routes/delete-secret.d.ts.map +1 -0
- package/dist/presentation/routes/delete-secret.js +40 -0
- package/dist/presentation/routes/delete-secret.js.map +1 -0
- package/dist/presentation/routes/delete-variable.d.ts +2 -0
- package/dist/presentation/routes/delete-variable.d.ts.map +1 -0
- package/dist/presentation/routes/delete-variable.js +40 -0
- package/dist/presentation/routes/delete-variable.js.map +1 -0
- package/dist/presentation/routes/errors.d.ts +2 -0
- package/dist/presentation/routes/errors.d.ts.map +1 -0
- package/dist/presentation/routes/errors.js +73 -0
- package/dist/presentation/routes/errors.js.map +1 -0
- package/dist/presentation/routes/get-variable.d.ts +2 -0
- package/dist/presentation/routes/get-variable.d.ts.map +1 -0
- package/dist/presentation/routes/get-variable.js +42 -0
- package/dist/presentation/routes/get-variable.js.map +1 -0
- package/dist/presentation/routes/index.d.ts +3 -0
- package/dist/presentation/routes/index.d.ts.map +1 -0
- package/dist/presentation/routes/index.js +29 -0
- package/dist/presentation/routes/index.js.map +1 -0
- package/dist/presentation/routes/list-secrets.d.ts +2 -0
- package/dist/presentation/routes/list-secrets.d.ts.map +1 -0
- package/dist/presentation/routes/list-secrets.js +50 -0
- package/dist/presentation/routes/list-secrets.js.map +1 -0
- package/dist/presentation/routes/list-variables.d.ts +2 -0
- package/dist/presentation/routes/list-variables.d.ts.map +1 -0
- package/dist/presentation/routes/list-variables.js +50 -0
- package/dist/presentation/routes/list-variables.js.map +1 -0
- package/dist/presentation/routes/put-secret.d.ts +2 -0
- package/dist/presentation/routes/put-secret.d.ts.map +1 -0
- package/dist/presentation/routes/put-secret.js +52 -0
- package/dist/presentation/routes/put-secret.js.map +1 -0
- package/dist/presentation/routes/put-variable.d.ts +2 -0
- package/dist/presentation/routes/put-variable.d.ts.map +1 -0
- package/dist/presentation/routes/put-variable.js +52 -0
- package/dist/presentation/routes/put-variable.js.map +1 -0
- package/dist/presentation/routes/warnings.d.ts +5 -0
- package/dist/presentation/routes/warnings.d.ts.map +1 -0
- package/dist/presentation/routes/warnings.js +16 -0
- package/dist/presentation/routes/warnings.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/drizzle/0000_lowly_ogun.sql +59 -0
- package/drizzle/meta/0000_snapshot.json +547 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +7 -0
- package/package.json +77 -0
- package/src/config.test.ts +41 -0
- package/src/config.ts +41 -0
- package/src/core/crypto.test.ts +115 -0
- package/src/core/crypto.ts +105 -0
- package/src/core/dek-manager.test.ts +120 -0
- package/src/core/dek-manager.ts +98 -0
- package/src/core/errors.ts +100 -0
- package/src/core/fingerprint.test.ts +27 -0
- package/src/core/fingerprint.ts +14 -0
- package/src/core/index.ts +153 -0
- package/src/core/key-provider.test.ts +44 -0
- package/src/core/key-provider.ts +70 -0
- package/src/core/local-secret-store.ts +49 -0
- package/src/core/management.test.ts +37 -0
- package/src/core/management.ts +503 -0
- package/src/core/rotate-kek.test.ts +183 -0
- package/src/core/rotate-kek.ts +100 -0
- package/src/core/secret-store.test.ts +301 -0
- package/src/core/secret-store.ts +251 -0
- package/src/core/store-resolver.test.ts +20 -0
- package/src/core/store-resolver.ts +28 -0
- package/src/core/store-validation.ts +43 -0
- package/src/core/value-cipher.ts +26 -0
- package/src/core/variable-store.test.ts +106 -0
- package/src/core/variable-store.ts +232 -0
- package/src/db/cap.ts +58 -0
- package/src/db/data-keys.ts +99 -0
- package/src/db/db.ts +26 -0
- package/src/db/index.ts +45 -0
- package/src/db/management.ts +252 -0
- package/src/db/schema/common.ts +8 -0
- package/src/db/schema/data-keys.ts +31 -0
- package/src/db/schema/outbox.ts +4 -0
- package/src/db/schema/values.ts +74 -0
- package/src/db/schema/variables.ts +71 -0
- package/src/db/scope.ts +42 -0
- package/src/db/values.ts +191 -0
- package/src/db/variables.ts +187 -0
- package/src/index.test.ts +11 -0
- package/src/index.ts +50 -0
- package/src/metrics/index.ts +19 -0
- package/src/metrics/instance.ts +219 -0
- package/src/metrics/service.ts +19 -0
- package/src/presentation/dto/index.ts +1 -0
- package/src/presentation/dto/management.ts +36 -0
- package/src/presentation/e2eRoutes/create-secret.ts +31 -0
- package/src/presentation/e2eRoutes/create-variable.ts +34 -0
- package/src/presentation/e2eRoutes/index.test.ts +94 -0
- package/src/presentation/e2eRoutes/index.ts +8 -0
- package/src/presentation/routes/auth.ts +50 -0
- package/src/presentation/routes/batch-secrets.ts +33 -0
- package/src/presentation/routes/batch-variables.ts +33 -0
- package/src/presentation/routes/cursor.ts +23 -0
- package/src/presentation/routes/delete-secret.ts +26 -0
- package/src/presentation/routes/delete-variable.ts +26 -0
- package/src/presentation/routes/errors.ts +63 -0
- package/src/presentation/routes/get-variable.ts +32 -0
- package/src/presentation/routes/index.ts +29 -0
- package/src/presentation/routes/list-secrets.ts +41 -0
- package/src/presentation/routes/list-variables.ts +41 -0
- package/src/presentation/routes/management.test.ts +452 -0
- package/src/presentation/routes/put-secret.ts +39 -0
- package/src/presentation/routes/put-variable.ts +39 -0
- package/src/presentation/routes/warnings.ts +23 -0
- package/test/env.ts +8 -0
- package/test/globalSetup.ts +18 -0
- package/test/setup.ts +17 -0
- package/tsconfig.build.json +10 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +7 -0
- package/tsconfig.test.json +8 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { SECRET_CREATED, SECRET_DELETED, SECRET_UPDATED, VARIABLE_CREATED, VARIABLE_DELETED, VARIABLE_UPDATED } from '@shipfox/api-secrets-dto';
|
|
2
|
+
import { writeOutboxEvent } from '@shipfox/node-outbox';
|
|
3
|
+
import { db, deleteSecretManagementRows, deleteVariableManagementRows, getSecretManagementRow, getVariableManagementRow, listExistingSecretManagementKeys, listExistingVariableManagementKeys, listSecretManagementRows, listVariableManagementRows, lockWorkspaceEntries, secretsOutbox, upsertSecretValueRows, upsertSecretVariableRows } from '#db/index.js';
|
|
4
|
+
import { normalizedProjectId } from '#db/scope.js';
|
|
5
|
+
import { classifySecretsOperationError, operationScope, recordSecretsEntriesMutated, recordSecretsOperation } from '#metrics/instance.js';
|
|
6
|
+
import { SecretBatchDuplicateKeyError, SecretNotFoundError, VariableNotFoundError } from './errors.js';
|
|
7
|
+
import { fingerprintSecretValue } from './fingerprint.js';
|
|
8
|
+
import { assertWorkspaceCap, validateSecretKeys, validateValueBytes } from './store-validation.js';
|
|
9
|
+
import { encryptSecretValue } from './value-cipher.js';
|
|
10
|
+
export function createSecretsManagementApi(params) {
|
|
11
|
+
return {
|
|
12
|
+
async listSecrets (input) {
|
|
13
|
+
const startedAt = Date.now();
|
|
14
|
+
const scope = operationScope(input);
|
|
15
|
+
try {
|
|
16
|
+
const result = await listSecretManagementRows(input);
|
|
17
|
+
recordSecretsOperation({
|
|
18
|
+
resource: 'secret',
|
|
19
|
+
operation: 'list',
|
|
20
|
+
surface: 'management',
|
|
21
|
+
scope,
|
|
22
|
+
outcome: 'success',
|
|
23
|
+
durationMs: Date.now() - startedAt
|
|
24
|
+
});
|
|
25
|
+
return result;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
recordSecretsOperation({
|
|
28
|
+
resource: 'secret',
|
|
29
|
+
operation: 'list',
|
|
30
|
+
surface: 'management',
|
|
31
|
+
scope,
|
|
32
|
+
outcome: classifySecretsOperationError(error),
|
|
33
|
+
durationMs: Date.now() - startedAt
|
|
34
|
+
});
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
async listVariables (input) {
|
|
39
|
+
const startedAt = Date.now();
|
|
40
|
+
const scope = operationScope(input);
|
|
41
|
+
try {
|
|
42
|
+
const result = await listVariableManagementRows(input);
|
|
43
|
+
recordSecretsOperation({
|
|
44
|
+
resource: 'variable',
|
|
45
|
+
operation: 'list',
|
|
46
|
+
surface: 'management',
|
|
47
|
+
scope,
|
|
48
|
+
outcome: 'success',
|
|
49
|
+
durationMs: Date.now() - startedAt
|
|
50
|
+
});
|
|
51
|
+
return result;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
recordSecretsOperation({
|
|
54
|
+
resource: 'variable',
|
|
55
|
+
operation: 'list',
|
|
56
|
+
surface: 'management',
|
|
57
|
+
scope,
|
|
58
|
+
outcome: classifySecretsOperationError(error),
|
|
59
|
+
durationMs: Date.now() - startedAt
|
|
60
|
+
});
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
async getVariable (input) {
|
|
65
|
+
const startedAt = Date.now();
|
|
66
|
+
const scope = operationScope(input);
|
|
67
|
+
try {
|
|
68
|
+
validateSecretKeys([
|
|
69
|
+
input.key
|
|
70
|
+
]);
|
|
71
|
+
const variable = await getVariableManagementRow(input);
|
|
72
|
+
if (!variable) throw new VariableNotFoundError(input.key);
|
|
73
|
+
recordSecretsOperation({
|
|
74
|
+
resource: 'variable',
|
|
75
|
+
operation: 'get',
|
|
76
|
+
surface: 'management',
|
|
77
|
+
scope,
|
|
78
|
+
outcome: 'success',
|
|
79
|
+
durationMs: Date.now() - startedAt
|
|
80
|
+
});
|
|
81
|
+
return variable;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
recordSecretsOperation({
|
|
84
|
+
resource: 'variable',
|
|
85
|
+
operation: 'get',
|
|
86
|
+
surface: 'management',
|
|
87
|
+
scope,
|
|
88
|
+
outcome: classifySecretsOperationError(error),
|
|
89
|
+
durationMs: Date.now() - startedAt
|
|
90
|
+
});
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
async setSecrets (input) {
|
|
95
|
+
const startedAt = Date.now();
|
|
96
|
+
const scope = operationScope(input);
|
|
97
|
+
try {
|
|
98
|
+
const entries = normalizeEntries(input.entries);
|
|
99
|
+
validateSecretKeys(entries.map((entry)=>entry.key));
|
|
100
|
+
validateValueBytes(entries.map((entry)=>entry.value));
|
|
101
|
+
if (entries.length === 0) {
|
|
102
|
+
recordSecretsOperation({
|
|
103
|
+
resource: 'secret',
|
|
104
|
+
operation: 'set',
|
|
105
|
+
surface: 'management',
|
|
106
|
+
scope,
|
|
107
|
+
outcome: 'success',
|
|
108
|
+
durationMs: Date.now() - startedAt
|
|
109
|
+
});
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
const projectId = normalizedProjectId(input);
|
|
113
|
+
const dek = await params.dekManager.getPlaintextDek(input.workspaceId);
|
|
114
|
+
const rows = await writeManagementEntries({
|
|
115
|
+
resource: 'secret',
|
|
116
|
+
input,
|
|
117
|
+
keys: entries.map((entry)=>entry.key),
|
|
118
|
+
listExistingKeys: listExistingSecretManagementKeys,
|
|
119
|
+
writeRows: async (tx)=>{
|
|
120
|
+
await upsertSecretValueRows(entries.map((entry)=>({
|
|
121
|
+
workspaceId: input.workspaceId,
|
|
122
|
+
projectId,
|
|
123
|
+
namespace: '',
|
|
124
|
+
key: entry.key,
|
|
125
|
+
ciphertext: encryptSecretValue({
|
|
126
|
+
dek,
|
|
127
|
+
workspaceId: input.workspaceId,
|
|
128
|
+
scope: {
|
|
129
|
+
projectId
|
|
130
|
+
},
|
|
131
|
+
namespace: '',
|
|
132
|
+
key: entry.key,
|
|
133
|
+
value: entry.value
|
|
134
|
+
}),
|
|
135
|
+
fingerprint: fingerprintSecretValue(entry.value, dek),
|
|
136
|
+
lastEditedBy: input.actorId
|
|
137
|
+
})), tx);
|
|
138
|
+
const rows = await Promise.all(entries.map((entry)=>getSecretManagementRow({
|
|
139
|
+
workspaceId: input.workspaceId,
|
|
140
|
+
projectId,
|
|
141
|
+
key: entry.key
|
|
142
|
+
}, tx)));
|
|
143
|
+
return rows.map(assertFound);
|
|
144
|
+
},
|
|
145
|
+
eventTypes: {
|
|
146
|
+
created: SECRET_CREATED,
|
|
147
|
+
updated: SECRET_UPDATED
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
recordSecretsOperation({
|
|
151
|
+
resource: 'secret',
|
|
152
|
+
operation: 'set',
|
|
153
|
+
surface: 'management',
|
|
154
|
+
scope,
|
|
155
|
+
outcome: 'success',
|
|
156
|
+
durationMs: Date.now() - startedAt
|
|
157
|
+
});
|
|
158
|
+
return rows;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
recordSecretsOperation({
|
|
161
|
+
resource: 'secret',
|
|
162
|
+
operation: 'set',
|
|
163
|
+
surface: 'management',
|
|
164
|
+
scope,
|
|
165
|
+
outcome: classifySecretsOperationError(error),
|
|
166
|
+
durationMs: Date.now() - startedAt
|
|
167
|
+
});
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
async setVariables (input) {
|
|
172
|
+
const startedAt = Date.now();
|
|
173
|
+
const scope = operationScope(input);
|
|
174
|
+
try {
|
|
175
|
+
const entries = normalizeEntries(input.entries);
|
|
176
|
+
validateSecretKeys(entries.map((entry)=>entry.key));
|
|
177
|
+
validateValueBytes(entries.map((entry)=>entry.value));
|
|
178
|
+
if (entries.length === 0) {
|
|
179
|
+
recordSecretsOperation({
|
|
180
|
+
resource: 'variable',
|
|
181
|
+
operation: 'set',
|
|
182
|
+
surface: 'management',
|
|
183
|
+
scope,
|
|
184
|
+
outcome: 'success',
|
|
185
|
+
durationMs: Date.now() - startedAt
|
|
186
|
+
});
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
const projectId = normalizedProjectId(input);
|
|
190
|
+
const rows = await writeManagementEntries({
|
|
191
|
+
resource: 'variable',
|
|
192
|
+
input,
|
|
193
|
+
keys: entries.map((entry)=>entry.key),
|
|
194
|
+
listExistingKeys: listExistingVariableManagementKeys,
|
|
195
|
+
writeRows: async (tx)=>{
|
|
196
|
+
await upsertSecretVariableRows(entries.map((entry)=>({
|
|
197
|
+
workspaceId: input.workspaceId,
|
|
198
|
+
projectId,
|
|
199
|
+
namespace: '',
|
|
200
|
+
key: entry.key,
|
|
201
|
+
value: entry.value,
|
|
202
|
+
lastEditedBy: input.actorId
|
|
203
|
+
})), tx);
|
|
204
|
+
const rows = await Promise.all(entries.map((entry)=>getVariableManagementRow({
|
|
205
|
+
workspaceId: input.workspaceId,
|
|
206
|
+
projectId,
|
|
207
|
+
key: entry.key
|
|
208
|
+
}, tx)));
|
|
209
|
+
return rows.map(assertFound);
|
|
210
|
+
},
|
|
211
|
+
eventTypes: {
|
|
212
|
+
created: VARIABLE_CREATED,
|
|
213
|
+
updated: VARIABLE_UPDATED
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
recordSecretsOperation({
|
|
217
|
+
resource: 'variable',
|
|
218
|
+
operation: 'set',
|
|
219
|
+
surface: 'management',
|
|
220
|
+
scope,
|
|
221
|
+
outcome: 'success',
|
|
222
|
+
durationMs: Date.now() - startedAt
|
|
223
|
+
});
|
|
224
|
+
return rows;
|
|
225
|
+
} catch (error) {
|
|
226
|
+
recordSecretsOperation({
|
|
227
|
+
resource: 'variable',
|
|
228
|
+
operation: 'set',
|
|
229
|
+
surface: 'management',
|
|
230
|
+
scope,
|
|
231
|
+
outcome: classifySecretsOperationError(error),
|
|
232
|
+
durationMs: Date.now() - startedAt
|
|
233
|
+
});
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
async deleteSecret (input) {
|
|
238
|
+
const startedAt = Date.now();
|
|
239
|
+
const scope = operationScope(input);
|
|
240
|
+
try {
|
|
241
|
+
validateSecretKeys([
|
|
242
|
+
input.key
|
|
243
|
+
]);
|
|
244
|
+
const deleted = await deleteManagementEntries({
|
|
245
|
+
resource: 'secret',
|
|
246
|
+
input,
|
|
247
|
+
keys: [
|
|
248
|
+
input.key
|
|
249
|
+
],
|
|
250
|
+
deleteRows: deleteSecretManagementRows,
|
|
251
|
+
eventType: SECRET_DELETED
|
|
252
|
+
});
|
|
253
|
+
if (deleted === 0) throw new SecretNotFoundError(input.key);
|
|
254
|
+
recordSecretsOperation({
|
|
255
|
+
resource: 'secret',
|
|
256
|
+
operation: 'delete',
|
|
257
|
+
surface: 'management',
|
|
258
|
+
scope,
|
|
259
|
+
outcome: 'success',
|
|
260
|
+
durationMs: Date.now() - startedAt
|
|
261
|
+
});
|
|
262
|
+
} catch (error) {
|
|
263
|
+
recordSecretsOperation({
|
|
264
|
+
resource: 'secret',
|
|
265
|
+
operation: 'delete',
|
|
266
|
+
surface: 'management',
|
|
267
|
+
scope,
|
|
268
|
+
outcome: classifySecretsOperationError(error),
|
|
269
|
+
durationMs: Date.now() - startedAt
|
|
270
|
+
});
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
async deleteVariable (input) {
|
|
275
|
+
const startedAt = Date.now();
|
|
276
|
+
const scope = operationScope(input);
|
|
277
|
+
try {
|
|
278
|
+
validateSecretKeys([
|
|
279
|
+
input.key
|
|
280
|
+
]);
|
|
281
|
+
const deleted = await deleteManagementEntries({
|
|
282
|
+
resource: 'variable',
|
|
283
|
+
input,
|
|
284
|
+
keys: [
|
|
285
|
+
input.key
|
|
286
|
+
],
|
|
287
|
+
deleteRows: deleteVariableManagementRows,
|
|
288
|
+
eventType: VARIABLE_DELETED
|
|
289
|
+
});
|
|
290
|
+
if (deleted === 0) throw new VariableNotFoundError(input.key);
|
|
291
|
+
recordSecretsOperation({
|
|
292
|
+
resource: 'variable',
|
|
293
|
+
operation: 'delete',
|
|
294
|
+
surface: 'management',
|
|
295
|
+
scope,
|
|
296
|
+
outcome: 'success',
|
|
297
|
+
durationMs: Date.now() - startedAt
|
|
298
|
+
});
|
|
299
|
+
} catch (error) {
|
|
300
|
+
recordSecretsOperation({
|
|
301
|
+
resource: 'variable',
|
|
302
|
+
operation: 'delete',
|
|
303
|
+
surface: 'management',
|
|
304
|
+
scope,
|
|
305
|
+
outcome: classifySecretsOperationError(error),
|
|
306
|
+
durationMs: Date.now() - startedAt
|
|
307
|
+
});
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
async function writeManagementEntries(params) {
|
|
314
|
+
let createdCount = 0;
|
|
315
|
+
let updatedCount = 0;
|
|
316
|
+
const rows = await db().transaction(async (tx)=>{
|
|
317
|
+
await lockWorkspaceEntries(params.input.workspaceId, tx);
|
|
318
|
+
const existingKeys = await params.listExistingKeys({
|
|
319
|
+
workspaceId: params.input.workspaceId,
|
|
320
|
+
projectId: normalizedProjectId(params.input),
|
|
321
|
+
keys: params.keys
|
|
322
|
+
}, tx);
|
|
323
|
+
createdCount = params.keys.length - existingKeys.size;
|
|
324
|
+
updatedCount = existingKeys.size;
|
|
325
|
+
await assertWorkspaceCap({
|
|
326
|
+
workspaceId: params.input.workspaceId,
|
|
327
|
+
namespace: '',
|
|
328
|
+
incomingEntries: createdCount,
|
|
329
|
+
tx
|
|
330
|
+
});
|
|
331
|
+
const rows = await params.writeRows(tx);
|
|
332
|
+
await Promise.all(params.keys.map((key)=>writeOutboxEvent(tx, secretsOutbox, {
|
|
333
|
+
type: existingKeys.has(key) ? params.eventTypes.updated : params.eventTypes.created,
|
|
334
|
+
payload: managementEventPayload(params.input, key)
|
|
335
|
+
})));
|
|
336
|
+
return rows;
|
|
337
|
+
});
|
|
338
|
+
recordSecretsEntriesMutated({
|
|
339
|
+
resource: params.resource,
|
|
340
|
+
operation: 'set',
|
|
341
|
+
effect: 'created',
|
|
342
|
+
surface: 'management',
|
|
343
|
+
count: createdCount
|
|
344
|
+
});
|
|
345
|
+
recordSecretsEntriesMutated({
|
|
346
|
+
resource: params.resource,
|
|
347
|
+
operation: 'set',
|
|
348
|
+
effect: 'updated',
|
|
349
|
+
surface: 'management',
|
|
350
|
+
count: updatedCount
|
|
351
|
+
});
|
|
352
|
+
return rows;
|
|
353
|
+
}
|
|
354
|
+
async function deleteManagementEntries(params) {
|
|
355
|
+
const deleted = await db().transaction(async (tx)=>{
|
|
356
|
+
await lockWorkspaceEntries(params.input.workspaceId, tx);
|
|
357
|
+
const rows = await params.deleteRows({
|
|
358
|
+
workspaceId: params.input.workspaceId,
|
|
359
|
+
projectId: normalizedProjectId(params.input),
|
|
360
|
+
keys: params.keys
|
|
361
|
+
}, tx);
|
|
362
|
+
await Promise.all(rows.map((row)=>writeOutboxEvent(tx, secretsOutbox, {
|
|
363
|
+
type: params.eventType,
|
|
364
|
+
payload: managementEventPayload(params.input, row.key)
|
|
365
|
+
})));
|
|
366
|
+
return rows.length;
|
|
367
|
+
});
|
|
368
|
+
recordSecretsEntriesMutated({
|
|
369
|
+
resource: params.resource,
|
|
370
|
+
operation: 'delete',
|
|
371
|
+
effect: 'deleted',
|
|
372
|
+
surface: 'management',
|
|
373
|
+
count: deleted
|
|
374
|
+
});
|
|
375
|
+
return deleted;
|
|
376
|
+
}
|
|
377
|
+
function normalizeEntries(entries) {
|
|
378
|
+
const valuesByKey = new Map();
|
|
379
|
+
for (const entry of entries){
|
|
380
|
+
if (valuesByKey.has(entry.key)) throw new SecretBatchDuplicateKeyError(entry.key);
|
|
381
|
+
valuesByKey.set(entry.key, entry.value);
|
|
382
|
+
}
|
|
383
|
+
return [
|
|
384
|
+
...valuesByKey
|
|
385
|
+
].map(([key, value])=>({
|
|
386
|
+
key,
|
|
387
|
+
value
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
function managementEventPayload(input, key) {
|
|
391
|
+
return {
|
|
392
|
+
actorId: input.actorId,
|
|
393
|
+
workspaceId: input.workspaceId,
|
|
394
|
+
projectId: normalizedProjectId(input),
|
|
395
|
+
key
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function assertFound(row) {
|
|
399
|
+
if (!row) throw new Error('Management write returned no row');
|
|
400
|
+
return row;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
//# sourceMappingURL=management.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/management.ts"],"sourcesContent":["import {\n SECRET_CREATED,\n SECRET_DELETED,\n SECRET_UPDATED,\n type SecretsEventMap,\n VARIABLE_CREATED,\n VARIABLE_DELETED,\n VARIABLE_UPDATED,\n} from '@shipfox/api-secrets-dto';\nimport {writeOutboxEvent} from '@shipfox/node-outbox';\nimport {\n db,\n deleteSecretManagementRows,\n deleteVariableManagementRows,\n getSecretManagementRow,\n getVariableManagementRow,\n listExistingSecretManagementKeys,\n listExistingVariableManagementKeys,\n listSecretManagementRows,\n listVariableManagementRows,\n lockWorkspaceEntries,\n type SecretManagementRow,\n type StoreScope,\n secretsOutbox,\n type Tx,\n upsertSecretValueRows,\n upsertSecretVariableRows,\n} from '#db/index.js';\nimport type {SecretVariable} from '#db/schema/variables.js';\nimport {normalizedProjectId} from '#db/scope.js';\nimport {\n classifySecretsOperationError,\n operationScope,\n recordSecretsEntriesMutated,\n recordSecretsOperation,\n type SecretsMetricResource,\n} from '#metrics/instance.js';\nimport type {DekManager} from './dek-manager.js';\nimport {\n SecretBatchDuplicateKeyError,\n SecretNotFoundError,\n VariableNotFoundError,\n} from './errors.js';\nimport {fingerprintSecretValue} from './fingerprint.js';\nimport {assertWorkspaceCap, validateSecretKeys, validateValueBytes} from './store-validation.js';\nimport {encryptSecretValue} from './value-cipher.js';\n\nexport interface ManagementEntry {\n key: string;\n value: string;\n}\n\nexport interface ManagementWriteParams extends StoreScope {\n workspaceId: string;\n entries: ManagementEntry[];\n actorId: string;\n}\n\nexport interface ManagementKeyParams extends StoreScope {\n workspaceId: string;\n key: string;\n actorId: string;\n}\n\nexport interface ManagementListParams extends StoreScope {\n workspaceId: string;\n limit: number;\n cursor?: string | undefined;\n}\n\nexport function createSecretsManagementApi(params: {dekManager: DekManager}) {\n return {\n async listSecrets(input: ManagementListParams) {\n const startedAt = Date.now();\n const scope = operationScope(input);\n try {\n const result = await listSecretManagementRows(input);\n recordSecretsOperation({\n resource: 'secret',\n operation: 'list',\n surface: 'management',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n return result;\n } catch (error) {\n recordSecretsOperation({\n resource: 'secret',\n operation: 'list',\n surface: 'management',\n scope,\n outcome: classifySecretsOperationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n },\n async listVariables(input: ManagementListParams) {\n const startedAt = Date.now();\n const scope = operationScope(input);\n try {\n const result = await listVariableManagementRows(input);\n recordSecretsOperation({\n resource: 'variable',\n operation: 'list',\n surface: 'management',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n return result;\n } catch (error) {\n recordSecretsOperation({\n resource: 'variable',\n operation: 'list',\n surface: 'management',\n scope,\n outcome: classifySecretsOperationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n },\n async getVariable(\n input: StoreScope & {workspaceId: string; key: string},\n ): Promise<SecretVariable> {\n const startedAt = Date.now();\n const scope = operationScope(input);\n try {\n validateSecretKeys([input.key]);\n\n const variable = await getVariableManagementRow(input);\n if (!variable) throw new VariableNotFoundError(input.key);\n recordSecretsOperation({\n resource: 'variable',\n operation: 'get',\n surface: 'management',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n return variable;\n } catch (error) {\n recordSecretsOperation({\n resource: 'variable',\n operation: 'get',\n surface: 'management',\n scope,\n outcome: classifySecretsOperationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n },\n async setSecrets(input: ManagementWriteParams): Promise<SecretManagementRow[]> {\n const startedAt = Date.now();\n const scope = operationScope(input);\n try {\n const entries = normalizeEntries(input.entries);\n validateSecretKeys(entries.map((entry) => entry.key));\n validateValueBytes(entries.map((entry) => entry.value));\n if (entries.length === 0) {\n recordSecretsOperation({\n resource: 'secret',\n operation: 'set',\n surface: 'management',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n return [];\n }\n\n const projectId = normalizedProjectId(input);\n const dek = await params.dekManager.getPlaintextDek(input.workspaceId);\n const rows = await writeManagementEntries({\n resource: 'secret',\n input,\n keys: entries.map((entry) => entry.key),\n listExistingKeys: listExistingSecretManagementKeys,\n writeRows: async (tx) => {\n await upsertSecretValueRows(\n entries.map((entry) => ({\n workspaceId: input.workspaceId,\n projectId,\n namespace: '',\n key: entry.key,\n ciphertext: encryptSecretValue({\n dek,\n workspaceId: input.workspaceId,\n scope: {projectId},\n namespace: '',\n key: entry.key,\n value: entry.value,\n }),\n fingerprint: fingerprintSecretValue(entry.value, dek),\n lastEditedBy: input.actorId,\n })),\n tx,\n );\n const rows = await Promise.all(\n entries.map((entry) =>\n getSecretManagementRow(\n {workspaceId: input.workspaceId, projectId, key: entry.key},\n tx,\n ),\n ),\n );\n return rows.map(assertFound);\n },\n eventTypes: {created: SECRET_CREATED, updated: SECRET_UPDATED},\n });\n recordSecretsOperation({\n resource: 'secret',\n operation: 'set',\n surface: 'management',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n return rows;\n } catch (error) {\n recordSecretsOperation({\n resource: 'secret',\n operation: 'set',\n surface: 'management',\n scope,\n outcome: classifySecretsOperationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n },\n async setVariables(input: ManagementWriteParams): Promise<SecretVariable[]> {\n const startedAt = Date.now();\n const scope = operationScope(input);\n try {\n const entries = normalizeEntries(input.entries);\n validateSecretKeys(entries.map((entry) => entry.key));\n validateValueBytes(entries.map((entry) => entry.value));\n if (entries.length === 0) {\n recordSecretsOperation({\n resource: 'variable',\n operation: 'set',\n surface: 'management',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n return [];\n }\n\n const projectId = normalizedProjectId(input);\n const rows = await writeManagementEntries({\n resource: 'variable',\n input,\n keys: entries.map((entry) => entry.key),\n listExistingKeys: listExistingVariableManagementKeys,\n writeRows: async (tx) => {\n await upsertSecretVariableRows(\n entries.map((entry) => ({\n workspaceId: input.workspaceId,\n projectId,\n namespace: '',\n key: entry.key,\n value: entry.value,\n lastEditedBy: input.actorId,\n })),\n tx,\n );\n const rows = await Promise.all(\n entries.map((entry) =>\n getVariableManagementRow(\n {workspaceId: input.workspaceId, projectId, key: entry.key},\n tx,\n ),\n ),\n );\n return rows.map(assertFound);\n },\n eventTypes: {created: VARIABLE_CREATED, updated: VARIABLE_UPDATED},\n });\n recordSecretsOperation({\n resource: 'variable',\n operation: 'set',\n surface: 'management',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n return rows;\n } catch (error) {\n recordSecretsOperation({\n resource: 'variable',\n operation: 'set',\n surface: 'management',\n scope,\n outcome: classifySecretsOperationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n },\n async deleteSecret(input: ManagementKeyParams): Promise<void> {\n const startedAt = Date.now();\n const scope = operationScope(input);\n try {\n validateSecretKeys([input.key]);\n\n const deleted = await deleteManagementEntries({\n resource: 'secret',\n input,\n keys: [input.key],\n deleteRows: deleteSecretManagementRows,\n eventType: SECRET_DELETED,\n });\n if (deleted === 0) throw new SecretNotFoundError(input.key);\n recordSecretsOperation({\n resource: 'secret',\n operation: 'delete',\n surface: 'management',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n } catch (error) {\n recordSecretsOperation({\n resource: 'secret',\n operation: 'delete',\n surface: 'management',\n scope,\n outcome: classifySecretsOperationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n },\n async deleteVariable(input: ManagementKeyParams): Promise<void> {\n const startedAt = Date.now();\n const scope = operationScope(input);\n try {\n validateSecretKeys([input.key]);\n\n const deleted = await deleteManagementEntries({\n resource: 'variable',\n input,\n keys: [input.key],\n deleteRows: deleteVariableManagementRows,\n eventType: VARIABLE_DELETED,\n });\n if (deleted === 0) throw new VariableNotFoundError(input.key);\n recordSecretsOperation({\n resource: 'variable',\n operation: 'delete',\n surface: 'management',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n } catch (error) {\n recordSecretsOperation({\n resource: 'variable',\n operation: 'delete',\n surface: 'management',\n scope,\n outcome: classifySecretsOperationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n },\n };\n}\n\nasync function writeManagementEntries<Row extends {key: string}>(params: {\n resource: SecretsMetricResource;\n input: ManagementWriteParams;\n keys: string[];\n listExistingKeys: (\n params: StoreScope & {workspaceId: string; keys: string[]},\n tx: Tx,\n ) => Promise<Set<string>>;\n writeRows: (tx: Tx) => Promise<Row[]>;\n eventTypes: {\n created: typeof SECRET_CREATED | typeof VARIABLE_CREATED;\n updated: typeof SECRET_UPDATED | typeof VARIABLE_UPDATED;\n };\n}): Promise<Row[]> {\n let createdCount = 0;\n let updatedCount = 0;\n const rows = await db().transaction(async (tx) => {\n await lockWorkspaceEntries(params.input.workspaceId, tx);\n const existingKeys = await params.listExistingKeys(\n {\n workspaceId: params.input.workspaceId,\n projectId: normalizedProjectId(params.input),\n keys: params.keys,\n },\n tx,\n );\n createdCount = params.keys.length - existingKeys.size;\n updatedCount = existingKeys.size;\n await assertWorkspaceCap({\n workspaceId: params.input.workspaceId,\n namespace: '',\n incomingEntries: createdCount,\n tx,\n });\n\n const rows = await params.writeRows(tx);\n await Promise.all(\n params.keys.map((key) =>\n writeOutboxEvent<SecretsEventMap>(tx, secretsOutbox, {\n type: existingKeys.has(key) ? params.eventTypes.updated : params.eventTypes.created,\n payload: managementEventPayload(params.input, key),\n }),\n ),\n );\n return rows;\n });\n recordSecretsEntriesMutated({\n resource: params.resource,\n operation: 'set',\n effect: 'created',\n surface: 'management',\n count: createdCount,\n });\n recordSecretsEntriesMutated({\n resource: params.resource,\n operation: 'set',\n effect: 'updated',\n surface: 'management',\n count: updatedCount,\n });\n return rows;\n}\n\nasync function deleteManagementEntries<Row extends {key: string}>(params: {\n resource: SecretsMetricResource;\n input: ManagementKeyParams;\n keys: string[];\n deleteRows: (\n params: StoreScope & {workspaceId: string; keys: string[]},\n tx: Tx,\n ) => Promise<Row[]>;\n eventType: typeof SECRET_DELETED | typeof VARIABLE_DELETED;\n}): Promise<number> {\n const deleted = await db().transaction(async (tx) => {\n await lockWorkspaceEntries(params.input.workspaceId, tx);\n const rows = await params.deleteRows(\n {\n workspaceId: params.input.workspaceId,\n projectId: normalizedProjectId(params.input),\n keys: params.keys,\n },\n tx,\n );\n await Promise.all(\n rows.map((row) =>\n writeOutboxEvent<SecretsEventMap>(tx, secretsOutbox, {\n type: params.eventType,\n payload: managementEventPayload(params.input, row.key),\n }),\n ),\n );\n return rows.length;\n });\n recordSecretsEntriesMutated({\n resource: params.resource,\n operation: 'delete',\n effect: 'deleted',\n surface: 'management',\n count: deleted,\n });\n return deleted;\n}\n\nfunction normalizeEntries(entries: ManagementEntry[]): ManagementEntry[] {\n const valuesByKey = new Map<string, string>();\n for (const entry of entries) {\n if (valuesByKey.has(entry.key)) throw new SecretBatchDuplicateKeyError(entry.key);\n valuesByKey.set(entry.key, entry.value);\n }\n return [...valuesByKey].map(([key, value]) => ({key, value}));\n}\n\nfunction managementEventPayload(\n input: Pick<ManagementWriteParams, 'workspaceId' | 'projectId' | 'actorId'>,\n key: string,\n) {\n return {\n actorId: input.actorId,\n workspaceId: input.workspaceId,\n projectId: normalizedProjectId(input),\n key,\n };\n}\n\nfunction assertFound<Row>(row: Row | undefined): Row {\n if (!row) throw new Error('Management write returned no row');\n return row;\n}\n"],"names":["SECRET_CREATED","SECRET_DELETED","SECRET_UPDATED","VARIABLE_CREATED","VARIABLE_DELETED","VARIABLE_UPDATED","writeOutboxEvent","db","deleteSecretManagementRows","deleteVariableManagementRows","getSecretManagementRow","getVariableManagementRow","listExistingSecretManagementKeys","listExistingVariableManagementKeys","listSecretManagementRows","listVariableManagementRows","lockWorkspaceEntries","secretsOutbox","upsertSecretValueRows","upsertSecretVariableRows","normalizedProjectId","classifySecretsOperationError","operationScope","recordSecretsEntriesMutated","recordSecretsOperation","SecretBatchDuplicateKeyError","SecretNotFoundError","VariableNotFoundError","fingerprintSecretValue","assertWorkspaceCap","validateSecretKeys","validateValueBytes","encryptSecretValue","createSecretsManagementApi","params","listSecrets","input","startedAt","Date","now","scope","result","resource","operation","surface","outcome","durationMs","error","listVariables","getVariable","key","variable","setSecrets","entries","normalizeEntries","map","entry","value","length","projectId","dek","dekManager","getPlaintextDek","workspaceId","rows","writeManagementEntries","keys","listExistingKeys","writeRows","tx","namespace","ciphertext","fingerprint","lastEditedBy","actorId","Promise","all","assertFound","eventTypes","created","updated","setVariables","deleteSecret","deleted","deleteManagementEntries","deleteRows","eventType","deleteVariable","createdCount","updatedCount","transaction","existingKeys","size","incomingEntries","type","has","payload","managementEventPayload","effect","count","row","valuesByKey","Map","set","Error"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { KeyProvider } from './key-provider.js';
|
|
2
|
+
export interface RotateWorkspaceDataKeysResult {
|
|
3
|
+
rotated: number;
|
|
4
|
+
skipped: number;
|
|
5
|
+
}
|
|
6
|
+
export interface RotateWorkspaceDataKeysOptions {
|
|
7
|
+
workspaceIds?: string[] | undefined;
|
|
8
|
+
}
|
|
9
|
+
export declare function rotateWorkspaceDataKeysWithProvider(keyProvider: KeyProvider, options?: RotateWorkspaceDataKeysOptions): Promise<RotateWorkspaceDataKeysResult>;
|
|
10
|
+
//# sourceMappingURL=rotate-kek.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rotate-kek.d.ts","sourceRoot":"","sources":["../../src/core/rotate-kek.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAInD,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,8BAA8B;IAC7C,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CACrC;AAED,wBAAsB,mCAAmC,CACvD,WAAW,EAAE,WAAW,EACxB,OAAO,GAAE,8BAAmC,GAC3C,OAAO,CAAC,6BAA6B,CAAC,CAqExC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { listDataKeysPage, listDataKeyVersions, updateDataKeyWrapCas } from '#db/index.js';
|
|
2
|
+
import { classifyKekRotationError, recordSecretsKekRotation } from '#metrics/instance.js';
|
|
3
|
+
import { KekVersionStrandedError } from './errors.js';
|
|
4
|
+
const PAGE_SIZE = 100;
|
|
5
|
+
export async function rotateWorkspaceDataKeysWithProvider(keyProvider, options = {}) {
|
|
6
|
+
const startedAt = Date.now();
|
|
7
|
+
try {
|
|
8
|
+
const knownVersions = [
|
|
9
|
+
keyProvider.currentKeyVersion,
|
|
10
|
+
keyProvider.previousKeyVersion
|
|
11
|
+
].filter((version)=>Boolean(version));
|
|
12
|
+
const unknownVersions = await listDataKeyVersions(knownVersions, {
|
|
13
|
+
workspaceIds: options.workspaceIds
|
|
14
|
+
});
|
|
15
|
+
if (unknownVersions.length > 0) throw new KekVersionStrandedError(unknownVersions[0]);
|
|
16
|
+
let rotated = 0;
|
|
17
|
+
let skipped = 0;
|
|
18
|
+
let skippedCurrent = 0;
|
|
19
|
+
let skippedRace = 0;
|
|
20
|
+
let afterWorkspaceId;
|
|
21
|
+
while(true){
|
|
22
|
+
const page = await listDataKeysPage({
|
|
23
|
+
afterWorkspaceId,
|
|
24
|
+
limit: PAGE_SIZE,
|
|
25
|
+
workspaceIds: options.workspaceIds
|
|
26
|
+
});
|
|
27
|
+
if (page.length === 0) break;
|
|
28
|
+
for (const row of page){
|
|
29
|
+
afterWorkspaceId = row.workspaceId;
|
|
30
|
+
if (row.kekVersion === keyProvider.currentKeyVersion) {
|
|
31
|
+
skipped += 1;
|
|
32
|
+
skippedCurrent += 1;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const plaintextDek = keyProvider.unwrapDek(row.workspaceId, row.wrappedDek, row.kekVersion);
|
|
36
|
+
try {
|
|
37
|
+
const wrapped = keyProvider.wrapDek(row.workspaceId, plaintextDek);
|
|
38
|
+
const updated = await updateDataKeyWrapCas({
|
|
39
|
+
workspaceId: row.workspaceId,
|
|
40
|
+
oldKekVersion: row.kekVersion,
|
|
41
|
+
wrappedDek: wrapped.wrappedDek,
|
|
42
|
+
kekVersion: wrapped.kekVersion
|
|
43
|
+
});
|
|
44
|
+
if (updated) rotated += 1;
|
|
45
|
+
else {
|
|
46
|
+
skipped += 1;
|
|
47
|
+
skippedRace += 1;
|
|
48
|
+
}
|
|
49
|
+
} finally{
|
|
50
|
+
plaintextDek.fill(0);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
recordSecretsKekRotation({
|
|
55
|
+
outcome: 'rotated',
|
|
56
|
+
count: rotated
|
|
57
|
+
});
|
|
58
|
+
recordSecretsKekRotation({
|
|
59
|
+
outcome: 'skipped_current',
|
|
60
|
+
count: skippedCurrent
|
|
61
|
+
});
|
|
62
|
+
recordSecretsKekRotation({
|
|
63
|
+
outcome: 'skipped_race',
|
|
64
|
+
count: skippedRace
|
|
65
|
+
});
|
|
66
|
+
recordSecretsKekRotation({
|
|
67
|
+
outcome: rotationDurationOutcome({
|
|
68
|
+
rotated,
|
|
69
|
+
skippedCurrent,
|
|
70
|
+
skippedRace
|
|
71
|
+
}),
|
|
72
|
+
count: 0,
|
|
73
|
+
durationMs: Date.now() - startedAt
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
rotated,
|
|
77
|
+
skipped
|
|
78
|
+
};
|
|
79
|
+
} catch (error) {
|
|
80
|
+
recordSecretsKekRotation({
|
|
81
|
+
outcome: classifyKekRotationError(error),
|
|
82
|
+
durationMs: Date.now() - startedAt
|
|
83
|
+
});
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function rotationDurationOutcome(params) {
|
|
88
|
+
if (params.rotated > 0) return 'rotated';
|
|
89
|
+
if (params.skippedRace > 0) return 'skipped_race';
|
|
90
|
+
if (params.skippedCurrent === 0) return 'none';
|
|
91
|
+
return 'skipped_current';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//# sourceMappingURL=rotate-kek.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/rotate-kek.ts"],"sourcesContent":["import {listDataKeysPage, listDataKeyVersions, updateDataKeyWrapCas} from '#db/index.js';\nimport {classifyKekRotationError, recordSecretsKekRotation} from '#metrics/instance.js';\nimport {KekVersionStrandedError} from './errors.js';\nimport type {KeyProvider} from './key-provider.js';\n\nconst PAGE_SIZE = 100;\n\nexport interface RotateWorkspaceDataKeysResult {\n rotated: number;\n skipped: number;\n}\n\nexport interface RotateWorkspaceDataKeysOptions {\n workspaceIds?: string[] | undefined;\n}\n\nexport async function rotateWorkspaceDataKeysWithProvider(\n keyProvider: KeyProvider,\n options: RotateWorkspaceDataKeysOptions = {},\n): Promise<RotateWorkspaceDataKeysResult> {\n const startedAt = Date.now();\n try {\n const knownVersions = [keyProvider.currentKeyVersion, keyProvider.previousKeyVersion].filter(\n (version): version is string => Boolean(version),\n );\n const unknownVersions = await listDataKeyVersions(knownVersions, {\n workspaceIds: options.workspaceIds,\n });\n if (unknownVersions.length > 0) throw new KekVersionStrandedError(unknownVersions[0] as string);\n\n let rotated = 0;\n let skipped = 0;\n let skippedCurrent = 0;\n let skippedRace = 0;\n let afterWorkspaceId: string | undefined;\n\n while (true) {\n const page = await listDataKeysPage({\n afterWorkspaceId,\n limit: PAGE_SIZE,\n workspaceIds: options.workspaceIds,\n });\n if (page.length === 0) break;\n\n for (const row of page) {\n afterWorkspaceId = row.workspaceId;\n if (row.kekVersion === keyProvider.currentKeyVersion) {\n skipped += 1;\n skippedCurrent += 1;\n continue;\n }\n\n const plaintextDek = keyProvider.unwrapDek(row.workspaceId, row.wrappedDek, row.kekVersion);\n try {\n const wrapped = keyProvider.wrapDek(row.workspaceId, plaintextDek);\n const updated = await updateDataKeyWrapCas({\n workspaceId: row.workspaceId,\n oldKekVersion: row.kekVersion,\n wrappedDek: wrapped.wrappedDek,\n kekVersion: wrapped.kekVersion,\n });\n if (updated) rotated += 1;\n else {\n skipped += 1;\n skippedRace += 1;\n }\n } finally {\n plaintextDek.fill(0);\n }\n }\n }\n\n recordSecretsKekRotation({outcome: 'rotated', count: rotated});\n recordSecretsKekRotation({outcome: 'skipped_current', count: skippedCurrent});\n recordSecretsKekRotation({outcome: 'skipped_race', count: skippedRace});\n recordSecretsKekRotation({\n outcome: rotationDurationOutcome({rotated, skippedCurrent, skippedRace}),\n count: 0,\n durationMs: Date.now() - startedAt,\n });\n return {rotated, skipped};\n } catch (error) {\n recordSecretsKekRotation({\n outcome: classifyKekRotationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n}\n\nfunction rotationDurationOutcome(params: {\n rotated: number;\n skippedCurrent: number;\n skippedRace: number;\n}) {\n if (params.rotated > 0) return 'rotated';\n if (params.skippedRace > 0) return 'skipped_race';\n if (params.skippedCurrent === 0) return 'none';\n return 'skipped_current';\n}\n"],"names":["listDataKeysPage","listDataKeyVersions","updateDataKeyWrapCas","classifyKekRotationError","recordSecretsKekRotation","KekVersionStrandedError","PAGE_SIZE","rotateWorkspaceDataKeysWithProvider","keyProvider","options","startedAt","Date","now","knownVersions","currentKeyVersion","previousKeyVersion","filter","version","Boolean","unknownVersions","workspaceIds","length","rotated","skipped","skippedCurrent","skippedRace","afterWorkspaceId","page","limit","row","workspaceId","kekVersion","plaintextDek","unwrapDek","wrappedDek","wrapped","wrapDek","updated","oldKekVersion","fill","outcome","count","rotationDurationOutcome","durationMs","error","params"],"mappings":"AAAA,SAAQA,gBAAgB,EAAEC,mBAAmB,EAAEC,oBAAoB,QAAO,eAAe;AACzF,SAAQC,wBAAwB,EAAEC,wBAAwB,QAAO,uBAAuB;AACxF,SAAQC,uBAAuB,QAAO,cAAc;AAGpD,MAAMC,YAAY;AAWlB,OAAO,eAAeC,oCACpBC,WAAwB,EACxBC,UAA0C,CAAC,CAAC;IAE5C,MAAMC,YAAYC,KAAKC,GAAG;IAC1B,IAAI;QACF,MAAMC,gBAAgB;YAACL,YAAYM,iBAAiB;YAAEN,YAAYO,kBAAkB;SAAC,CAACC,MAAM,CAC1F,CAACC,UAA+BC,QAAQD;QAE1C,MAAME,kBAAkB,MAAMlB,oBAAoBY,eAAe;YAC/DO,cAAcX,QAAQW,YAAY;QACpC;QACA,IAAID,gBAAgBE,MAAM,GAAG,GAAG,MAAM,IAAIhB,wBAAwBc,eAAe,CAAC,EAAE;QAEpF,IAAIG,UAAU;QACd,IAAIC,UAAU;QACd,IAAIC,iBAAiB;QACrB,IAAIC,cAAc;QAClB,IAAIC;QAEJ,MAAO,KAAM;YACX,MAAMC,OAAO,MAAM3B,iBAAiB;gBAClC0B;gBACAE,OAAOtB;gBACPc,cAAcX,QAAQW,YAAY;YACpC;YACA,IAAIO,KAAKN,MAAM,KAAK,GAAG;YAEvB,KAAK,MAAMQ,OAAOF,KAAM;gBACtBD,mBAAmBG,IAAIC,WAAW;gBAClC,IAAID,IAAIE,UAAU,KAAKvB,YAAYM,iBAAiB,EAAE;oBACpDS,WAAW;oBACXC,kBAAkB;oBAClB;gBACF;gBAEA,MAAMQ,eAAexB,YAAYyB,SAAS,CAACJ,IAAIC,WAAW,EAAED,IAAIK,UAAU,EAAEL,IAAIE,UAAU;gBAC1F,IAAI;oBACF,MAAMI,UAAU3B,YAAY4B,OAAO,CAACP,IAAIC,WAAW,EAAEE;oBACrD,MAAMK,UAAU,MAAMnC,qBAAqB;wBACzC4B,aAAaD,IAAIC,WAAW;wBAC5BQ,eAAeT,IAAIE,UAAU;wBAC7BG,YAAYC,QAAQD,UAAU;wBAC9BH,YAAYI,QAAQJ,UAAU;oBAChC;oBACA,IAAIM,SAASf,WAAW;yBACnB;wBACHC,WAAW;wBACXE,eAAe;oBACjB;gBACF,SAAU;oBACRO,aAAaO,IAAI,CAAC;gBACpB;YACF;QACF;QAEAnC,yBAAyB;YAACoC,SAAS;YAAWC,OAAOnB;QAAO;QAC5DlB,yBAAyB;YAACoC,SAAS;YAAmBC,OAAOjB;QAAc;QAC3EpB,yBAAyB;YAACoC,SAAS;YAAgBC,OAAOhB;QAAW;QACrErB,yBAAyB;YACvBoC,SAASE,wBAAwB;gBAACpB;gBAASE;gBAAgBC;YAAW;YACtEgB,OAAO;YACPE,YAAYhC,KAAKC,GAAG,KAAKF;QAC3B;QACA,OAAO;YAACY;YAASC;QAAO;IAC1B,EAAE,OAAOqB,OAAO;QACdxC,yBAAyB;YACvBoC,SAASrC,yBAAyByC;YAClCD,YAAYhC,KAAKC,GAAG,KAAKF;QAC3B;QACA,MAAMkC;IACR;AACF;AAEA,SAASF,wBAAwBG,MAIhC;IACC,IAAIA,OAAOvB,OAAO,GAAG,GAAG,OAAO;IAC/B,IAAIuB,OAAOpB,WAAW,GAAG,GAAG,OAAO;IACnC,IAAIoB,OAAOrB,cAAc,KAAK,GAAG,OAAO;IACxC,OAAO;AACT"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type StoreScope } from '#db/index.js';
|
|
2
|
+
import type { DekManager } from './dek-manager.js';
|
|
3
|
+
import type { SecretStoreProvider } from './store-resolver.js';
|
|
4
|
+
export interface SetSecretsParams extends StoreScope {
|
|
5
|
+
workspaceId: string;
|
|
6
|
+
namespace?: string | undefined;
|
|
7
|
+
values: Record<string, string>;
|
|
8
|
+
editedBy?: string | null | undefined;
|
|
9
|
+
}
|
|
10
|
+
export interface DeleteSecretsParams extends StoreScope {
|
|
11
|
+
workspaceId: string;
|
|
12
|
+
namespace?: string | undefined;
|
|
13
|
+
keys?: string[] | undefined;
|
|
14
|
+
}
|
|
15
|
+
export declare function createSecretStoreApi(params: {
|
|
16
|
+
dekManager: DekManager;
|
|
17
|
+
resolveSecretStore: (name?: string | undefined) => SecretStoreProvider;
|
|
18
|
+
}): {
|
|
19
|
+
getSecret(input: StoreScope & {
|
|
20
|
+
workspaceId: string;
|
|
21
|
+
namespace?: string | undefined;
|
|
22
|
+
key: string;
|
|
23
|
+
store?: string;
|
|
24
|
+
}): Promise<string | null>;
|
|
25
|
+
getSecretsByNamespace(input: StoreScope & {
|
|
26
|
+
workspaceId: string;
|
|
27
|
+
namespace?: string | undefined;
|
|
28
|
+
store?: string;
|
|
29
|
+
}): Promise<Record<string, string>>;
|
|
30
|
+
setSecrets(input: SetSecretsParams): Promise<void>;
|
|
31
|
+
deleteSecrets(input: DeleteSecretsParams): Promise<number>;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=secret-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-store.d.ts","sourceRoot":"","sources":["../../src/core/secret-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,UAAU,EAEhB,MAAM,cAAc,CAAC;AAQtB,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,qBAAqB,CAAC;AAS7D,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACtC;AAED,MAAM,WAAW,mBAAoB,SAAQ,UAAU;IACrD,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CAC7B;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAC3C,UAAU,EAAE,UAAU,CAAC;IACvB,kBAAkB,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,KAAK,mBAAmB,CAAC;CACxE;qBAGY,UAAU,GAAG;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAC/B,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB;iCA+BM,UAAU,GAAG;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAC;sBA+BnE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;yBA+F7B,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;EA2CnE"}
|