@griffin-app/griffin-executor 0.1.4 → 0.1.6
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/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +41 -24
- package/dist/executor.js.map +1 -1
- package/dist/executor.test.js +80 -80
- package/dist/executor.test.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/secrets/index.d.ts +1 -1
- package/dist/secrets/index.d.ts.map +1 -1
- package/dist/secrets/index.js +1 -1
- package/dist/secrets/index.js.map +1 -1
- package/dist/secrets/providers/hub.d.ts +2 -1
- package/dist/secrets/providers/hub.d.ts.map +1 -1
- package/dist/secrets/providers/hub.js +8 -2
- package/dist/secrets/providers/hub.js.map +1 -1
- package/dist/secrets/resolver.d.ts +8 -6
- package/dist/secrets/resolver.d.ts.map +1 -1
- package/dist/secrets/resolver.js +78 -38
- package/dist/secrets/resolver.js.map +1 -1
- package/dist/secrets/secrets.test.js +12 -42
- package/dist/secrets/secrets.test.js.map +1 -1
- package/dist/secrets/types.d.ts +3 -2
- package/dist/secrets/types.d.ts.map +1 -1
- package/dist/secrets/types.js.map +1 -1
- package/package.json +3 -3
- package/src/executor.test.ts +80 -80
- package/src/executor.ts +46 -31
- package/src/index.ts +0 -1
- package/src/secrets/index.ts +0 -1
- package/src/secrets/providers/hub.ts +8 -1
- package/src/secrets/resolver.ts +109 -45
- package/src/secrets/secrets.test.ts +11 -47
- package/src/secrets/types.ts +1 -2
- package/dist/secrets/providers/aws.d.ts +0 -61
- package/dist/secrets/providers/aws.d.ts.map +0 -1
- package/dist/secrets/providers/aws.js +0 -96
- package/dist/secrets/providers/aws.js.map +0 -1
- package/dist/secrets/providers/vault.d.ts +0 -75
- package/dist/secrets/providers/vault.d.ts.map +0 -1
- package/dist/secrets/providers/vault.js +0 -143
- package/dist/secrets/providers/vault.js.map +0 -1
- package/dist/secrets/registry.d.ts +0 -39
- package/dist/secrets/registry.d.ts.map +0 -1
- package/dist/secrets/registry.js +0 -134
- package/dist/secrets/registry.js.map +0 -1
- package/dist/test-plan-types.d.ts +0 -43
- package/dist/test-plan-types.d.ts.map +0 -1
- package/dist/test-plan-types.js +0 -2
- package/dist/test-plan-types.js.map +0 -1
- package/src/secrets/providers/vault.ts +0 -257
package/src/secrets/resolver.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Secret resolution utilities for test monitors.
|
|
3
3
|
*/
|
|
4
|
-
import { type MonitorV1 } from "@griffin-app/griffin-hub-sdk";
|
|
4
|
+
import { SecretOrString, type MonitorV1 } from "@griffin-app/griffin-hub-sdk";
|
|
5
5
|
import type { SecretProvider } from "./types.js";
|
|
6
6
|
import type { SecretRef, SecretRefData } from "./types.js";
|
|
7
7
|
import {
|
|
@@ -10,6 +10,29 @@ import {
|
|
|
10
10
|
SecretResolutionError,
|
|
11
11
|
} from "./types.js";
|
|
12
12
|
|
|
13
|
+
interface TemplateRef {
|
|
14
|
+
$template: {
|
|
15
|
+
strings: string[];
|
|
16
|
+
values: unknown[];
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isTemplateRef(value: unknown): value is TemplateRef {
|
|
21
|
+
if (typeof value !== "object" || value === null) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const obj = value as Record<string, unknown>;
|
|
25
|
+
if (
|
|
26
|
+
!("$template" in obj) ||
|
|
27
|
+
typeof obj.$template !== "object" ||
|
|
28
|
+
obj.$template === null
|
|
29
|
+
) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const tmpl = obj.$template as Record<string, unknown>;
|
|
33
|
+
return Array.isArray(tmpl.strings) && Array.isArray(tmpl.values);
|
|
34
|
+
}
|
|
35
|
+
|
|
13
36
|
/**
|
|
14
37
|
* Collected secret references and literals from a monitor.
|
|
15
38
|
*/
|
|
@@ -26,6 +49,10 @@ interface CollectedSecrets {
|
|
|
26
49
|
path: (string | number)[];
|
|
27
50
|
value: string;
|
|
28
51
|
}>;
|
|
52
|
+
/** Paths to templates containing secrets (for partial resolution) */
|
|
53
|
+
templatePaths: Array<{
|
|
54
|
+
path: (string | number)[];
|
|
55
|
+
}>;
|
|
29
56
|
}
|
|
30
57
|
|
|
31
58
|
/**
|
|
@@ -60,6 +87,20 @@ function collectSecretsFromValue(
|
|
|
60
87
|
return;
|
|
61
88
|
}
|
|
62
89
|
|
|
90
|
+
if (isTemplateRef(value)) {
|
|
91
|
+
let hasSecrets = false;
|
|
92
|
+
for (const val of value.$template.values) {
|
|
93
|
+
if (isSecretRef(val)) {
|
|
94
|
+
collected.refs.push(val.$secret);
|
|
95
|
+
hasSecrets = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (hasSecrets) {
|
|
99
|
+
collected.templatePaths.push({ path: [...currentPath] });
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
63
104
|
if (Array.isArray(value)) {
|
|
64
105
|
for (let i = 0; i < value.length; i++) {
|
|
65
106
|
collectSecretsFromValue(value[i], [...currentPath, i], collected);
|
|
@@ -85,6 +126,7 @@ export function collectSecretsFromMonitor(
|
|
|
85
126
|
refs: [],
|
|
86
127
|
paths: [],
|
|
87
128
|
literalPaths: [],
|
|
129
|
+
templatePaths: [],
|
|
88
130
|
};
|
|
89
131
|
|
|
90
132
|
for (let nodeIndex = 0; nodeIndex < monitor.nodes.length; nodeIndex++) {
|
|
@@ -95,7 +137,17 @@ export function collectSecretsFromMonitor(
|
|
|
95
137
|
continue;
|
|
96
138
|
}
|
|
97
139
|
|
|
98
|
-
//
|
|
140
|
+
// Scan path and base (may contain templates with secrets)
|
|
141
|
+
collectSecretsFromValue(
|
|
142
|
+
node.path,
|
|
143
|
+
["nodes", nodeIndex, "path"],
|
|
144
|
+
collected,
|
|
145
|
+
);
|
|
146
|
+
collectSecretsFromValue(
|
|
147
|
+
node.base,
|
|
148
|
+
["nodes", nodeIndex, "base"],
|
|
149
|
+
collected,
|
|
150
|
+
);
|
|
99
151
|
|
|
100
152
|
// Scan headers
|
|
101
153
|
if (node.headers) {
|
|
@@ -182,15 +234,21 @@ function deepClone<T>(value: T): T {
|
|
|
182
234
|
export async function resolveSecretsInMonitor(
|
|
183
235
|
monitor: MonitorV1,
|
|
184
236
|
provider: SecretProvider | null,
|
|
185
|
-
): Promise<MonitorV1> {
|
|
237
|
+
): Promise<{ monitor: MonitorV1; hadSecrets: boolean }> {
|
|
186
238
|
// Collect all secret references and string literals
|
|
187
239
|
const collected = collectSecretsFromMonitor(monitor);
|
|
188
240
|
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
241
|
+
if (
|
|
242
|
+
collected.refs.length === 0 &&
|
|
243
|
+
collected.literalPaths.length === 0 &&
|
|
244
|
+
collected.templatePaths.length === 0
|
|
245
|
+
) {
|
|
246
|
+
// No secrets, literals, or templates to resolve
|
|
247
|
+
return { monitor, hadSecrets: false };
|
|
192
248
|
}
|
|
193
249
|
|
|
250
|
+
const hadSecrets = collected.refs.length > 0 || collected.templatePaths.length > 0;
|
|
251
|
+
|
|
194
252
|
// Resolve secrets when we have refs (provider required)
|
|
195
253
|
if (collected.refs.length > 0 && !provider) {
|
|
196
254
|
throw new SecretResolutionError(
|
|
@@ -218,56 +276,62 @@ export async function resolveSecretsInMonitor(
|
|
|
218
276
|
setAtPath(resolvedMonitor, path, value);
|
|
219
277
|
}
|
|
220
278
|
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
* Check if a monitor contains any secret references or string literals that need resolution.
|
|
226
|
-
* Useful for short-circuiting resolution when no secrets or literals are present.
|
|
227
|
-
*/
|
|
228
|
-
export function planHasSecrets(monitor: MonitorV1): boolean {
|
|
229
|
-
for (const node of monitor.nodes) {
|
|
230
|
-
if (node.type !== "HTTP_REQUEST") {
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Check headers
|
|
235
|
-
if (node.headers) {
|
|
236
|
-
for (const headerValue of Object.values(node.headers)) {
|
|
237
|
-
if (isSecretRef(headerValue) || isStringLiteral(headerValue)) {
|
|
238
|
-
return true;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
279
|
+
// Resolve templates that contain secrets
|
|
280
|
+
for (const { path } of collected.templatePaths) {
|
|
281
|
+
const tmpl = getAtPath(resolvedMonitor, path) as TemplateRef;
|
|
282
|
+
if (!isTemplateRef(tmpl)) continue;
|
|
242
283
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
return true;
|
|
246
|
-
}
|
|
284
|
+
const resolved = await resolveTemplateSecrets(tmpl, provider!);
|
|
285
|
+
setAtPath(resolvedMonitor, path, resolved);
|
|
247
286
|
}
|
|
248
287
|
|
|
249
|
-
return
|
|
288
|
+
return { monitor: resolvedMonitor, hadSecrets };
|
|
250
289
|
}
|
|
251
290
|
|
|
252
291
|
/**
|
|
253
|
-
*
|
|
292
|
+
* Get a value at a path in an object.
|
|
254
293
|
*/
|
|
255
|
-
function
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (isSecretRef(value) || isStringLiteral(value)) {
|
|
261
|
-
return true;
|
|
294
|
+
function getAtPath(obj: unknown, path: (string | number)[]): unknown {
|
|
295
|
+
let current: any = obj;
|
|
296
|
+
for (const key of path) {
|
|
297
|
+
if (current === undefined || current === null) return undefined;
|
|
298
|
+
current = current[key];
|
|
262
299
|
}
|
|
300
|
+
return current;
|
|
301
|
+
}
|
|
263
302
|
|
|
264
|
-
|
|
265
|
-
|
|
303
|
+
/**
|
|
304
|
+
* Resolve secrets in a template using partial application.
|
|
305
|
+
* Returns a plain string if all values resolved, or a reduced $template.
|
|
306
|
+
*/
|
|
307
|
+
async function resolveTemplateSecrets(
|
|
308
|
+
tmpl: TemplateRef,
|
|
309
|
+
provider: SecretProvider,
|
|
310
|
+
): Promise<string | TemplateRef> {
|
|
311
|
+
const { strings, values } = tmpl.$template;
|
|
312
|
+
const newStrings: string[] = [strings[0]];
|
|
313
|
+
const newValues: unknown[] = [];
|
|
314
|
+
|
|
315
|
+
for (let i = 0; i < values.length; i++) {
|
|
316
|
+
const val = values[i];
|
|
317
|
+
if (isSecretRef(val)) {
|
|
318
|
+
const resolved = await provider.resolve(val.$secret.ref, {
|
|
319
|
+
version: val.$secret.version,
|
|
320
|
+
field: val.$secret.field,
|
|
321
|
+
});
|
|
322
|
+
newStrings[newStrings.length - 1] += resolved + strings[i + 1];
|
|
323
|
+
} else if (isStringLiteral(val)) {
|
|
324
|
+
newStrings[newStrings.length - 1] += val.$literal + strings[i + 1];
|
|
325
|
+
} else {
|
|
326
|
+
newValues.push(val);
|
|
327
|
+
newStrings.push(strings[i + 1]);
|
|
328
|
+
}
|
|
266
329
|
}
|
|
267
330
|
|
|
268
|
-
if (
|
|
269
|
-
return
|
|
331
|
+
if (newValues.length === 0) {
|
|
332
|
+
return newStrings[0];
|
|
270
333
|
}
|
|
271
334
|
|
|
272
|
-
return
|
|
335
|
+
return { $template: { strings: newStrings, values: newValues } };
|
|
273
336
|
}
|
|
337
|
+
|
|
@@ -5,7 +5,6 @@ import { SecretResolutionError } from "./types.js";
|
|
|
5
5
|
import {
|
|
6
6
|
resolveSecretsInMonitor,
|
|
7
7
|
collectSecretsFromMonitor,
|
|
8
|
-
planHasSecrets,
|
|
9
8
|
} from "./resolver.js";
|
|
10
9
|
import { MonitorV1 } from "@griffin-app/griffin-hub-sdk";
|
|
11
10
|
|
|
@@ -104,8 +103,8 @@ describe("Monitor Secret Resolution", () => {
|
|
|
104
103
|
id: "endpoint-1",
|
|
105
104
|
type: "HTTP_REQUEST",
|
|
106
105
|
method: "GET",
|
|
107
|
-
path: "/api/test",
|
|
108
|
-
base: "https://api.example.com",
|
|
106
|
+
path: { $literal: "/api/test" },
|
|
107
|
+
base: { $literal: "https://api.example.com" },
|
|
109
108
|
response_format: "JSON",
|
|
110
109
|
headers,
|
|
111
110
|
body,
|
|
@@ -117,41 +116,6 @@ describe("Monitor Secret Resolution", () => {
|
|
|
117
116
|
],
|
|
118
117
|
}) as MonitorV1;
|
|
119
118
|
|
|
120
|
-
describe("planHasSecrets", () => {
|
|
121
|
-
it("should return false for monitors without secrets or literals", () => {
|
|
122
|
-
const monitor = createTestMonitor({ "Content-Type": "application/json" });
|
|
123
|
-
expect(planHasSecrets(monitor)).toBe(false);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("should return true for monitors with secret refs in headers", () => {
|
|
127
|
-
const monitor = createTestMonitor({
|
|
128
|
-
Authorization: createSecretRef("API_KEY"),
|
|
129
|
-
});
|
|
130
|
-
expect(planHasSecrets(monitor)).toBe(true);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("should return true for monitors with secret refs in body", () => {
|
|
134
|
-
const monitor = createTestMonitor(undefined, {
|
|
135
|
-
token: createSecretRef("TOKEN"),
|
|
136
|
-
});
|
|
137
|
-
expect(planHasSecrets(monitor)).toBe(true);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("should return true for monitors with string literals in headers", () => {
|
|
141
|
-
const monitor = createTestMonitor({
|
|
142
|
-
"Content-Type": createStringLiteral("application/json"),
|
|
143
|
-
});
|
|
144
|
-
expect(planHasSecrets(monitor)).toBe(true);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("should return true for monitors with string literals in body", () => {
|
|
148
|
-
const monitor = createTestMonitor(undefined, {
|
|
149
|
-
type: createStringLiteral("test"),
|
|
150
|
-
});
|
|
151
|
-
expect(planHasSecrets(monitor)).toBe(true);
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
119
|
describe("collectSecretsFromMonitor", () => {
|
|
156
120
|
it("should collect secrets from headers", () => {
|
|
157
121
|
const monitor = createTestMonitor({
|
|
@@ -202,7 +166,7 @@ describe("Monitor Secret Resolution", () => {
|
|
|
202
166
|
const collected = collectSecretsFromMonitor(monitor);
|
|
203
167
|
|
|
204
168
|
expect(collected.refs).toHaveLength(0);
|
|
205
|
-
expect(collected.literalPaths).toHaveLength(
|
|
169
|
+
expect(collected.literalPaths).toHaveLength(4);
|
|
206
170
|
expect(collected.literalPaths).toContainEqual({
|
|
207
171
|
path: ["nodes", 0, "headers", "Content-Type"],
|
|
208
172
|
value: "application/json",
|
|
@@ -223,7 +187,7 @@ describe("Monitor Secret Resolution", () => {
|
|
|
223
187
|
|
|
224
188
|
expect(collected.refs).toHaveLength(1);
|
|
225
189
|
expect(collected.refs).toContainEqual({ ref: "API_KEY" });
|
|
226
|
-
expect(collected.literalPaths).toHaveLength(
|
|
190
|
+
expect(collected.literalPaths).toHaveLength(3);
|
|
227
191
|
expect(collected.literalPaths).toContainEqual({
|
|
228
192
|
path: ["nodes", 0, "headers", "Content-Type"],
|
|
229
193
|
value: "application/json",
|
|
@@ -242,7 +206,7 @@ describe("Monitor Secret Resolution", () => {
|
|
|
242
206
|
env: { API_KEY: "Bearer secret-token" },
|
|
243
207
|
});
|
|
244
208
|
|
|
245
|
-
const resolved = await resolveSecretsInMonitor(monitor, provider);
|
|
209
|
+
const { monitor: resolved } = await resolveSecretsInMonitor(monitor, provider);
|
|
246
210
|
|
|
247
211
|
const endpoint = resolved.nodes[0];
|
|
248
212
|
if (endpoint.type !== "HTTP_REQUEST") {
|
|
@@ -262,7 +226,7 @@ describe("Monitor Secret Resolution", () => {
|
|
|
262
226
|
env: { TOKEN: "resolved-token" },
|
|
263
227
|
});
|
|
264
228
|
|
|
265
|
-
const resolved = await resolveSecretsInMonitor(monitor, provider);
|
|
229
|
+
const { monitor: resolved } = await resolveSecretsInMonitor(monitor, provider);
|
|
266
230
|
|
|
267
231
|
const endpoint = resolved.nodes[0];
|
|
268
232
|
if (endpoint.type !== "HTTP_REQUEST") {
|
|
@@ -281,7 +245,7 @@ describe("Monitor Secret Resolution", () => {
|
|
|
281
245
|
env: { API_KEY: "secret" },
|
|
282
246
|
});
|
|
283
247
|
|
|
284
|
-
const resolved = await resolveSecretsInMonitor(monitor, provider);
|
|
248
|
+
const { monitor: resolved, hadSecrets } = await resolveSecretsInMonitor(monitor, provider);
|
|
285
249
|
|
|
286
250
|
// Original should still have secret ref
|
|
287
251
|
const originalEndpoint = monitor.nodes[0];
|
|
@@ -328,7 +292,7 @@ describe("Monitor Secret Resolution", () => {
|
|
|
328
292
|
Accept: createStringLiteral("application/xml"),
|
|
329
293
|
});
|
|
330
294
|
|
|
331
|
-
const resolved = await resolveSecretsInMonitor(monitor, null);
|
|
295
|
+
const { monitor: resolved } = await resolveSecretsInMonitor(monitor, null);
|
|
332
296
|
|
|
333
297
|
const endpoint = resolved.nodes[0];
|
|
334
298
|
if (endpoint.type !== "HTTP_REQUEST") {
|
|
@@ -344,7 +308,7 @@ describe("Monitor Secret Resolution", () => {
|
|
|
344
308
|
data: "plain-value",
|
|
345
309
|
});
|
|
346
310
|
|
|
347
|
-
const resolved = await resolveSecretsInMonitor(monitor, null);
|
|
311
|
+
const { monitor: resolved } = await resolveSecretsInMonitor(monitor, null);
|
|
348
312
|
|
|
349
313
|
const endpoint = resolved.nodes[0];
|
|
350
314
|
if (endpoint.type !== "HTTP_REQUEST") {
|
|
@@ -364,7 +328,7 @@ describe("Monitor Secret Resolution", () => {
|
|
|
364
328
|
env: { API_KEY: "Bearer secret-token" },
|
|
365
329
|
});
|
|
366
330
|
|
|
367
|
-
const resolved = await resolveSecretsInMonitor(monitor, provider);
|
|
331
|
+
const { monitor: resolved } = await resolveSecretsInMonitor(monitor, provider);
|
|
368
332
|
|
|
369
333
|
const endpoint = resolved.nodes[0];
|
|
370
334
|
if (endpoint.type !== "HTTP_REQUEST") {
|
|
@@ -379,7 +343,7 @@ describe("Monitor Secret Resolution", () => {
|
|
|
379
343
|
"Content-Type": createStringLiteral("application/json"),
|
|
380
344
|
});
|
|
381
345
|
|
|
382
|
-
const resolved = await resolveSecretsInMonitor(monitor, null);
|
|
346
|
+
const { monitor: resolved } = await resolveSecretsInMonitor(monitor, null);
|
|
383
347
|
|
|
384
348
|
// Original should still have literal wrapper
|
|
385
349
|
const originalEndpoint = monitor.nodes[0];
|
package/src/secrets/types.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { StringLiteral } from "@griffin-app/griffin-hub-sdk";
|
|
2
1
|
|
|
3
2
|
/**
|
|
4
3
|
* Data structure for a secret reference as it appears in a monitor.
|
|
@@ -96,7 +95,7 @@ export function isSecretRef(value: unknown): value is SecretRef {
|
|
|
96
95
|
/**
|
|
97
96
|
* Type guard to check if a value is a string literal.
|
|
98
97
|
*/
|
|
99
|
-
export function isStringLiteral(value: unknown): value is
|
|
98
|
+
export function isStringLiteral(value: unknown): value is { $literal: string } {
|
|
100
99
|
if (typeof value !== "object" || value === null) {
|
|
101
100
|
return false;
|
|
102
101
|
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AWS Secrets Manager secret provider.
|
|
3
|
-
*
|
|
4
|
-
* Resolves secrets from a single consolidated AWS Secrets Manager secret
|
|
5
|
-
* whose value is a JSON KV map. The secret is identified by `secretId`
|
|
6
|
-
* (e.g. "orgId/environment") and individual secret names are keys in that map.
|
|
7
|
-
*
|
|
8
|
-
* Usage in DSL:
|
|
9
|
-
* secret("aws:MY_SECRET")
|
|
10
|
-
*/
|
|
11
|
-
import type { SecretProvider, SecretResolveOptions } from "../types.js";
|
|
12
|
-
/**
|
|
13
|
-
* Interface for AWS Secrets Manager client.
|
|
14
|
-
* This allows dependency injection of the actual AWS SDK client.
|
|
15
|
-
*/
|
|
16
|
-
export interface AwsSecretsManagerClient {
|
|
17
|
-
getSecretValue(params: {
|
|
18
|
-
SecretId: string;
|
|
19
|
-
VersionStage?: string;
|
|
20
|
-
}): Promise<{
|
|
21
|
-
SecretString?: string;
|
|
22
|
-
SecretBinary?: Uint8Array;
|
|
23
|
-
}>;
|
|
24
|
-
}
|
|
25
|
-
export interface AwsSecretsManagerProviderOptions {
|
|
26
|
-
/**
|
|
27
|
-
* AWS Secrets Manager client instance.
|
|
28
|
-
* Should be pre-configured with region and credentials.
|
|
29
|
-
*/
|
|
30
|
-
client: AwsSecretsManagerClient;
|
|
31
|
-
/**
|
|
32
|
-
* The full Secrets Manager secret ID (e.g. "orgId/environment").
|
|
33
|
-
* All secrets for this org+env are stored as a JSON KV map in this single secret.
|
|
34
|
-
*/
|
|
35
|
-
secretId: string;
|
|
36
|
-
/**
|
|
37
|
-
* Default version stage to use if not specified.
|
|
38
|
-
* Defaults to "AWSCURRENT".
|
|
39
|
-
*/
|
|
40
|
-
defaultVersionStage?: string;
|
|
41
|
-
}
|
|
42
|
-
export declare class AwsSecretsManagerProvider implements SecretProvider {
|
|
43
|
-
readonly name = "aws";
|
|
44
|
-
private readonly client;
|
|
45
|
-
private readonly secretId;
|
|
46
|
-
private readonly defaultVersionStage;
|
|
47
|
-
private cache;
|
|
48
|
-
private readonly cacheTtlMs;
|
|
49
|
-
constructor(options: AwsSecretsManagerProviderOptions);
|
|
50
|
-
resolve(ref: string, options?: SecretResolveOptions): Promise<string>;
|
|
51
|
-
/**
|
|
52
|
-
* Fetch and cache the entire KV map from the consolidated SM secret.
|
|
53
|
-
*/
|
|
54
|
-
private fetchKvMap;
|
|
55
|
-
validate(): Promise<void>;
|
|
56
|
-
/**
|
|
57
|
-
* Clear the cache. Useful for testing or forced refresh.
|
|
58
|
-
*/
|
|
59
|
-
clearCache(): void;
|
|
60
|
-
}
|
|
61
|
-
//# sourceMappingURL=aws.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"aws.d.ts","sourceRoot":"","sources":["../../../src/secrets/providers/aws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGxE;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,cAAc,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,YAAY,CAAC,EAAE,UAAU,CAAC;KAC3B,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,gCAAgC;IAC/C;;;OAGG;IACH,MAAM,EAAE,uBAAuB,CAAC;IAEhC;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,qBAAa,yBAA0B,YAAW,cAAc;IAC9D,QAAQ,CAAC,IAAI,SAAS;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAG7C,OAAO,CAAC,KAAK,CACN;IACP,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiB;gBAEhC,OAAO,EAAE,gCAAgC;IAM/C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC;IAgB3E;;OAEG;YACW,UAAU;IAoElB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B;;OAEG;IACH,UAAU,IAAI,IAAI;CAGnB"}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AWS Secrets Manager secret provider.
|
|
3
|
-
*
|
|
4
|
-
* Resolves secrets from a single consolidated AWS Secrets Manager secret
|
|
5
|
-
* whose value is a JSON KV map. The secret is identified by `secretId`
|
|
6
|
-
* (e.g. "orgId/environment") and individual secret names are keys in that map.
|
|
7
|
-
*
|
|
8
|
-
* Usage in DSL:
|
|
9
|
-
* secret("aws:MY_SECRET")
|
|
10
|
-
*/
|
|
11
|
-
import { SecretResolutionError } from "../types.js";
|
|
12
|
-
export class AwsSecretsManagerProvider {
|
|
13
|
-
name = "aws";
|
|
14
|
-
client;
|
|
15
|
-
secretId;
|
|
16
|
-
defaultVersionStage;
|
|
17
|
-
// Cache the entire KV map with TTL
|
|
18
|
-
cache = null;
|
|
19
|
-
cacheTtlMs = 5 * 60 * 1000; // 5 minutes
|
|
20
|
-
constructor(options) {
|
|
21
|
-
this.client = options.client;
|
|
22
|
-
this.secretId = options.secretId;
|
|
23
|
-
this.defaultVersionStage = options.defaultVersionStage ?? "AWSCURRENT";
|
|
24
|
-
}
|
|
25
|
-
async resolve(ref, options) {
|
|
26
|
-
const kvMap = await this.fetchKvMap(options?.version ?? this.defaultVersionStage);
|
|
27
|
-
const value = kvMap[ref];
|
|
28
|
-
if (value === undefined) {
|
|
29
|
-
throw new SecretResolutionError(`Secret key "${ref}" not found in consolidated secret "${this.secretId}"`, { ref });
|
|
30
|
-
}
|
|
31
|
-
return value;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Fetch and cache the entire KV map from the consolidated SM secret.
|
|
35
|
-
*/
|
|
36
|
-
async fetchKvMap(versionStage) {
|
|
37
|
-
if (this.cache && this.cache.expires > Date.now()) {
|
|
38
|
-
return this.cache.value;
|
|
39
|
-
}
|
|
40
|
-
try {
|
|
41
|
-
const response = await this.client.getSecretValue({
|
|
42
|
-
SecretId: this.secretId,
|
|
43
|
-
VersionStage: versionStage,
|
|
44
|
-
});
|
|
45
|
-
if (!response.SecretString) {
|
|
46
|
-
throw new SecretResolutionError(`Secret "${this.secretId}" does not contain a string value (binary secrets are not supported)`, { ref: this.secretId });
|
|
47
|
-
}
|
|
48
|
-
let parsed;
|
|
49
|
-
try {
|
|
50
|
-
parsed = JSON.parse(response.SecretString);
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
throw new SecretResolutionError(`Secret "${this.secretId}" is not valid JSON`, { ref: this.secretId });
|
|
54
|
-
}
|
|
55
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
56
|
-
throw new SecretResolutionError(`Secret "${this.secretId}" is not a JSON object`, { ref: this.secretId });
|
|
57
|
-
}
|
|
58
|
-
const kvMap = parsed;
|
|
59
|
-
this.cache = {
|
|
60
|
-
value: kvMap,
|
|
61
|
-
expires: Date.now() + this.cacheTtlMs,
|
|
62
|
-
};
|
|
63
|
-
return kvMap;
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
if (error instanceof SecretResolutionError) {
|
|
67
|
-
throw error;
|
|
68
|
-
}
|
|
69
|
-
const awsError = error;
|
|
70
|
-
let message = `Failed to retrieve secret "${this.secretId}"`;
|
|
71
|
-
if (awsError.name === "ResourceNotFoundException") {
|
|
72
|
-
message = `Secret "${this.secretId}" not found in AWS Secrets Manager`;
|
|
73
|
-
}
|
|
74
|
-
else if (awsError.name === "AccessDeniedException") {
|
|
75
|
-
message = `Access denied to secret "${this.secretId}". Check IAM permissions.`;
|
|
76
|
-
}
|
|
77
|
-
else if (awsError.message) {
|
|
78
|
-
message = `${message}: ${awsError.message}`;
|
|
79
|
-
}
|
|
80
|
-
throw new SecretResolutionError(message, {
|
|
81
|
-
ref: this.secretId,
|
|
82
|
-
cause: error,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
async validate() {
|
|
87
|
-
// Validation happens on first secret access
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Clear the cache. Useful for testing or forced refresh.
|
|
91
|
-
*/
|
|
92
|
-
clearCache() {
|
|
93
|
-
this.cache = null;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
//# sourceMappingURL=aws.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"aws.js","sourceRoot":"","sources":["../../../src/secrets/providers/aws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAiCpD,MAAM,OAAO,yBAAyB;IAC3B,IAAI,GAAG,KAAK,CAAC;IACL,MAAM,CAA0B;IAChC,QAAQ,CAAS;IACjB,mBAAmB,CAAS;IAE7C,mCAAmC;IAC3B,KAAK,GACX,IAAI,CAAC;IACU,UAAU,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,YAAY;IAEzD,YAAY,OAAyC;QACnD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,IAAI,YAAY,CAAC;IACzE,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW,EAAE,OAA8B;QACvD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CACjC,OAAO,EAAE,OAAO,IAAI,IAAI,CAAC,mBAAmB,CAC7C,CAAC;QAEF,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,IAAI,qBAAqB,CAC7B,eAAe,GAAG,uCAAuC,IAAI,CAAC,QAAQ,GAAG,EACzE,EAAE,GAAG,EAAE,CACR,CAAC;QACJ,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,UAAU,CACtB,YAAoB;QAEpB,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAClD,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QAC1B,CAAC;QAED,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC;gBAChD,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,YAAY,EAAE,YAAY;aAC3B,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,CAAC;gBAC3B,MAAM,IAAI,qBAAqB,CAC7B,WAAW,IAAI,CAAC,QAAQ,sEAAsE,EAC9F,EAAE,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,CACvB,CAAC;YACJ,CAAC;YAED,IAAI,MAAe,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;YAC7C,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,IAAI,qBAAqB,CAC7B,WAAW,IAAI,CAAC,QAAQ,qBAAqB,EAC7C,EAAE,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,CACvB,CAAC;YACJ,CAAC;YAED,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3E,MAAM,IAAI,qBAAqB,CAC7B,WAAW,IAAI,CAAC,QAAQ,wBAAwB,EAChD,EAAE,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,CACvB,CAAC;YACJ,CAAC;YAED,MAAM,KAAK,GAAG,MAAgC,CAAC;YAE/C,IAAI,CAAC,KAAK,GAAG;gBACX,KAAK,EAAE,KAAK;gBACZ,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU;aACtC,CAAC;YAEF,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,qBAAqB,EAAE,CAAC;gBAC3C,MAAM,KAAK,CAAC;YACd,CAAC;YAED,MAAM,QAAQ,GAAG,KAA4C,CAAC;YAC9D,IAAI,OAAO,GAAG,8BAA8B,IAAI,CAAC,QAAQ,GAAG,CAAC;YAE7D,IAAI,QAAQ,CAAC,IAAI,KAAK,2BAA2B,EAAE,CAAC;gBAClD,OAAO,GAAG,WAAW,IAAI,CAAC,QAAQ,oCAAoC,CAAC;YACzE,CAAC;iBAAM,IAAI,QAAQ,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;gBACrD,OAAO,GAAG,4BAA4B,IAAI,CAAC,QAAQ,2BAA2B,CAAC;YACjF,CAAC;iBAAM,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;gBAC5B,OAAO,GAAG,GAAG,OAAO,KAAK,QAAQ,CAAC,OAAO,EAAE,CAAC;YAC9C,CAAC;YAED,MAAM,IAAI,qBAAqB,CAAC,OAAO,EAAE;gBACvC,GAAG,EAAE,IAAI,CAAC,QAAQ;gBAClB,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,4CAA4C;IAC9C,CAAC;IAED;;OAEG;IACH,UAAU;QACR,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;CACF"}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HashiCorp Vault secret provider.
|
|
3
|
-
*
|
|
4
|
-
* Reads secrets from HashiCorp Vault KV secrets engine (v1 and v2).
|
|
5
|
-
* Supports field extraction from JSON secrets.
|
|
6
|
-
*
|
|
7
|
-
* Usage in DSL:
|
|
8
|
-
* secret("vault:secret/data/myapp/config")
|
|
9
|
-
* secret("vault:secret/data/myapp/config", { field: "api_key" })
|
|
10
|
-
* secret("vault:secret/data/myapp/config", { version: "2" })
|
|
11
|
-
*/
|
|
12
|
-
import type { SecretProvider, SecretResolveOptions } from "../types.js";
|
|
13
|
-
/**
|
|
14
|
-
* HTTP client interface for Vault API calls.
|
|
15
|
-
* This allows dependency injection without requiring a specific HTTP library.
|
|
16
|
-
*/
|
|
17
|
-
export interface VaultHttpClient {
|
|
18
|
-
get(url: string, options: {
|
|
19
|
-
headers: Record<string, string>;
|
|
20
|
-
}): Promise<{
|
|
21
|
-
status: number;
|
|
22
|
-
data: unknown;
|
|
23
|
-
}>;
|
|
24
|
-
}
|
|
25
|
-
export interface VaultProviderOptions {
|
|
26
|
-
/**
|
|
27
|
-
* Vault server address (e.g., "https://vault.example.com:8200").
|
|
28
|
-
*/
|
|
29
|
-
address: string;
|
|
30
|
-
/**
|
|
31
|
-
* Authentication token.
|
|
32
|
-
*/
|
|
33
|
-
token: string;
|
|
34
|
-
/**
|
|
35
|
-
* HTTP client for making requests to Vault.
|
|
36
|
-
*/
|
|
37
|
-
httpClient: VaultHttpClient;
|
|
38
|
-
/**
|
|
39
|
-
* Optional namespace for Vault Enterprise.
|
|
40
|
-
*/
|
|
41
|
-
namespace?: string;
|
|
42
|
-
/**
|
|
43
|
-
* KV secrets engine version (1 or 2).
|
|
44
|
-
* Defaults to 2.
|
|
45
|
-
*/
|
|
46
|
-
kvVersion?: 1 | 2;
|
|
47
|
-
/**
|
|
48
|
-
* Optional prefix for secret paths.
|
|
49
|
-
*/
|
|
50
|
-
prefix?: string;
|
|
51
|
-
}
|
|
52
|
-
export declare class VaultProvider implements SecretProvider {
|
|
53
|
-
readonly name = "vault";
|
|
54
|
-
private readonly address;
|
|
55
|
-
private readonly token;
|
|
56
|
-
private readonly httpClient;
|
|
57
|
-
private readonly namespace?;
|
|
58
|
-
private readonly kvVersion;
|
|
59
|
-
private readonly prefix;
|
|
60
|
-
private cache;
|
|
61
|
-
private readonly cacheTtlMs;
|
|
62
|
-
constructor(options: VaultProviderOptions);
|
|
63
|
-
resolve(ref: string, options?: SecretResolveOptions): Promise<string>;
|
|
64
|
-
/**
|
|
65
|
-
* Extract a field from the secret data.
|
|
66
|
-
* If no field is specified, returns the entire data as JSON string.
|
|
67
|
-
*/
|
|
68
|
-
private extractField;
|
|
69
|
-
validate(): Promise<void>;
|
|
70
|
-
/**
|
|
71
|
-
* Clear the cache. Useful for testing or forced refresh.
|
|
72
|
-
*/
|
|
73
|
-
clearCache(): void;
|
|
74
|
-
}
|
|
75
|
-
//# sourceMappingURL=vault.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["../../../src/secrets/providers/vault.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGxE;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,CACD,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAC3C,OAAO,CAAC;QACT,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,OAAO,CAAC;KACf,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,EAAE,eAAe,CAAC;IAE5B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAElB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,aAAc,YAAW,cAAc;IAClD,QAAQ,CAAC,IAAI,WAAW;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkB;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAGhC,OAAO,CAAC,KAAK,CAGT;IACJ,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiB;gBAEhC,OAAO,EAAE,oBAAoB;IASnC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC;IAoG3E;;;OAGG;IACH,OAAO,CAAC,YAAY;IAuBd,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAoC/B;;OAEG;IACH,UAAU,IAAI,IAAI;CAGnB"}
|