@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
|
@@ -1,143 +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 { SecretResolutionError } from "../types.js";
|
|
13
|
-
export class VaultProvider {
|
|
14
|
-
name = "vault";
|
|
15
|
-
address;
|
|
16
|
-
token;
|
|
17
|
-
httpClient;
|
|
18
|
-
namespace;
|
|
19
|
-
kvVersion;
|
|
20
|
-
prefix;
|
|
21
|
-
// Simple in-memory cache with TTL
|
|
22
|
-
cache = new Map();
|
|
23
|
-
cacheTtlMs = 5 * 60 * 1000; // 5 minutes
|
|
24
|
-
constructor(options) {
|
|
25
|
-
this.address = options.address.replace(/\/$/, ""); // Remove trailing slash
|
|
26
|
-
this.token = options.token;
|
|
27
|
-
this.httpClient = options.httpClient;
|
|
28
|
-
this.namespace = options.namespace;
|
|
29
|
-
this.kvVersion = options.kvVersion ?? 2;
|
|
30
|
-
this.prefix = options.prefix ?? "";
|
|
31
|
-
}
|
|
32
|
-
async resolve(ref, options) {
|
|
33
|
-
const secretPath = this.prefix + ref;
|
|
34
|
-
const version = options?.version;
|
|
35
|
-
const cacheKey = `${secretPath}:${version ?? "latest"}`;
|
|
36
|
-
// Check cache
|
|
37
|
-
const cached = this.cache.get(cacheKey);
|
|
38
|
-
if (cached && cached.expires > Date.now()) {
|
|
39
|
-
return this.extractField(cached.value, options?.field, ref);
|
|
40
|
-
}
|
|
41
|
-
try {
|
|
42
|
-
// Build request URL
|
|
43
|
-
let url = `${this.address}/v1/${secretPath}`;
|
|
44
|
-
if (this.kvVersion === 2 && version) {
|
|
45
|
-
url += `?version=${version}`;
|
|
46
|
-
}
|
|
47
|
-
// Build headers
|
|
48
|
-
const headers = {
|
|
49
|
-
"X-Vault-Token": this.token,
|
|
50
|
-
};
|
|
51
|
-
if (this.namespace) {
|
|
52
|
-
headers["X-Vault-Namespace"] = this.namespace;
|
|
53
|
-
}
|
|
54
|
-
const response = await this.httpClient.get(url, { headers });
|
|
55
|
-
if (response.status === 404) {
|
|
56
|
-
throw new SecretResolutionError(`Secret "${secretPath}" not found in Vault`, { ref });
|
|
57
|
-
}
|
|
58
|
-
if (response.status === 403) {
|
|
59
|
-
throw new SecretResolutionError(`Access denied to secret "${secretPath}". Check Vault policies.`, { ref });
|
|
60
|
-
}
|
|
61
|
-
if (response.status !== 200) {
|
|
62
|
-
throw new SecretResolutionError(`Vault returned status ${response.status} for secret "${secretPath}"`, { ref });
|
|
63
|
-
}
|
|
64
|
-
// Parse response based on KV version
|
|
65
|
-
const data = response.data;
|
|
66
|
-
let secretData;
|
|
67
|
-
if (this.kvVersion === 2) {
|
|
68
|
-
// KV v2 wraps data in an extra "data" object
|
|
69
|
-
const kvData = data?.data;
|
|
70
|
-
if (!kvData?.data) {
|
|
71
|
-
throw new SecretResolutionError(`Invalid KV v2 response structure for secret "${secretPath}"`, { ref });
|
|
72
|
-
}
|
|
73
|
-
secretData = kvData.data;
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
// KV v1 has data directly
|
|
77
|
-
if (!data?.data || typeof data.data !== "object") {
|
|
78
|
-
throw new SecretResolutionError(`Invalid KV v1 response structure for secret "${secretPath}"`, { ref });
|
|
79
|
-
}
|
|
80
|
-
secretData = data.data;
|
|
81
|
-
}
|
|
82
|
-
// Cache the secret data
|
|
83
|
-
this.cache.set(cacheKey, {
|
|
84
|
-
value: secretData,
|
|
85
|
-
expires: Date.now() + this.cacheTtlMs,
|
|
86
|
-
});
|
|
87
|
-
return this.extractField(secretData, options?.field, ref);
|
|
88
|
-
}
|
|
89
|
-
catch (error) {
|
|
90
|
-
if (error instanceof SecretResolutionError) {
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
throw new SecretResolutionError(`Failed to retrieve secret "${secretPath}" from Vault: ${error instanceof Error ? error.message : String(error)}`, { ref, cause: error });
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Extract a field from the secret data.
|
|
98
|
-
* If no field is specified, returns the entire data as JSON string.
|
|
99
|
-
*/
|
|
100
|
-
extractField(secretData, field, ref) {
|
|
101
|
-
if (!field) {
|
|
102
|
-
// Return entire secret as JSON if no field specified
|
|
103
|
-
return JSON.stringify(secretData);
|
|
104
|
-
}
|
|
105
|
-
const value = secretData[field];
|
|
106
|
-
if (value === undefined) {
|
|
107
|
-
throw new SecretResolutionError(`Field "${field}" not found in secret "${ref}"`, { ref });
|
|
108
|
-
}
|
|
109
|
-
// Convert to string if not already
|
|
110
|
-
return typeof value === "string" ? value : JSON.stringify(value);
|
|
111
|
-
}
|
|
112
|
-
async validate() {
|
|
113
|
-
// Verify we can authenticate with Vault
|
|
114
|
-
try {
|
|
115
|
-
const headers = {
|
|
116
|
-
"X-Vault-Token": this.token,
|
|
117
|
-
};
|
|
118
|
-
if (this.namespace) {
|
|
119
|
-
headers["X-Vault-Namespace"] = this.namespace;
|
|
120
|
-
}
|
|
121
|
-
const response = await this.httpClient.get(`${this.address}/v1/auth/token/lookup-self`, { headers });
|
|
122
|
-
if (response.status === 403) {
|
|
123
|
-
throw new Error("Invalid or expired Vault token");
|
|
124
|
-
}
|
|
125
|
-
if (response.status !== 200) {
|
|
126
|
-
throw new Error(`Vault authentication check failed with status ${response.status}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
catch (error) {
|
|
130
|
-
if (error instanceof Error && error.message.includes("Vault")) {
|
|
131
|
-
throw error;
|
|
132
|
-
}
|
|
133
|
-
throw new Error(`Failed to connect to Vault at ${this.address}: ${error instanceof Error ? error.message : String(error)}`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Clear the cache. Useful for testing or forced refresh.
|
|
138
|
-
*/
|
|
139
|
-
clearCache() {
|
|
140
|
-
this.cache.clear();
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
//# sourceMappingURL=vault.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"vault.js","sourceRoot":"","sources":["../../../src/secrets/providers/vault.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Secret provider registry for managing the configured secret provider.
|
|
3
|
-
*/
|
|
4
|
-
import type { SecretProvider, SecretRefData } from "./types.js";
|
|
5
|
-
/**
|
|
6
|
-
* Registry for the single configured secret provider.
|
|
7
|
-
*/
|
|
8
|
-
export declare class SecretProviderRegistry {
|
|
9
|
-
private provider;
|
|
10
|
-
/**
|
|
11
|
-
* Set the secret provider.
|
|
12
|
-
* @param provider - The provider to use for resolution
|
|
13
|
-
*/
|
|
14
|
-
setProvider(provider: SecretProvider): void;
|
|
15
|
-
/**
|
|
16
|
-
* Resolve a secret reference using the configured provider.
|
|
17
|
-
* @param secretRef - The secret reference data
|
|
18
|
-
* @returns The resolved secret value
|
|
19
|
-
* @throws SecretResolutionError if resolution fails
|
|
20
|
-
*/
|
|
21
|
-
resolve(secretRef: SecretRefData): Promise<string>;
|
|
22
|
-
/**
|
|
23
|
-
* Resolve multiple secrets using the configured provider.
|
|
24
|
-
* @param refs - Array of secret reference data
|
|
25
|
-
* @returns Map of key to resolved value
|
|
26
|
-
* @throws SecretResolutionError if any resolution fails (fail-fast)
|
|
27
|
-
*/
|
|
28
|
-
resolveMany(refs: SecretRefData[]): Promise<Map<string, string>>;
|
|
29
|
-
/**
|
|
30
|
-
* Validate the configured provider.
|
|
31
|
-
* @throws Error if provider validation fails
|
|
32
|
-
*/
|
|
33
|
-
validateAll(): Promise<void>;
|
|
34
|
-
/**
|
|
35
|
-
* Create a unique key for a secret reference (for caching/deduplication).
|
|
36
|
-
*/
|
|
37
|
-
makeKey(secretRef: SecretRefData): string;
|
|
38
|
-
}
|
|
39
|
-
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/secrets/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EAEd,MAAM,YAAY,CAAC;AAGpB;;GAEG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAA+B;IAE/C;;;OAGG;IACH,WAAW,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI;IAI3C;;;;;OAKG;IACG,OAAO,CAAC,SAAS,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IA4BxD;;;;;OAKG;IACG,WAAW,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IA0EtE;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAclC;;OAEG;IACH,OAAO,CAAC,SAAS,EAAE,aAAa,GAAG,MAAM;CAM1C"}
|
package/dist/secrets/registry.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Secret provider registry for managing the configured secret provider.
|
|
3
|
-
*/
|
|
4
|
-
import { SecretResolutionError } from "./types.js";
|
|
5
|
-
/**
|
|
6
|
-
* Registry for the single configured secret provider.
|
|
7
|
-
*/
|
|
8
|
-
export class SecretProviderRegistry {
|
|
9
|
-
provider = null;
|
|
10
|
-
/**
|
|
11
|
-
* Set the secret provider.
|
|
12
|
-
* @param provider - The provider to use for resolution
|
|
13
|
-
*/
|
|
14
|
-
setProvider(provider) {
|
|
15
|
-
this.provider = provider;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Resolve a secret reference using the configured provider.
|
|
19
|
-
* @param secretRef - The secret reference data
|
|
20
|
-
* @returns The resolved secret value
|
|
21
|
-
* @throws SecretResolutionError if resolution fails
|
|
22
|
-
*/
|
|
23
|
-
async resolve(secretRef) {
|
|
24
|
-
if (!this.provider) {
|
|
25
|
-
throw new SecretResolutionError("No secret provider configured", {
|
|
26
|
-
ref: secretRef.ref,
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
try {
|
|
30
|
-
return await this.provider.resolve(secretRef.ref, {
|
|
31
|
-
version: secretRef.version,
|
|
32
|
-
field: secretRef.field,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
if (error instanceof SecretResolutionError) {
|
|
37
|
-
throw error;
|
|
38
|
-
}
|
|
39
|
-
throw new SecretResolutionError(`Failed to resolve secret "${secretRef.ref}": ${error instanceof Error ? error.message : String(error)}`, {
|
|
40
|
-
ref: secretRef.ref,
|
|
41
|
-
cause: error,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Resolve multiple secrets using the configured provider.
|
|
47
|
-
* @param refs - Array of secret reference data
|
|
48
|
-
* @returns Map of key to resolved value
|
|
49
|
-
* @throws SecretResolutionError if any resolution fails (fail-fast)
|
|
50
|
-
*/
|
|
51
|
-
async resolveMany(refs) {
|
|
52
|
-
if (refs.length === 0) {
|
|
53
|
-
return new Map();
|
|
54
|
-
}
|
|
55
|
-
if (!this.provider) {
|
|
56
|
-
throw new SecretResolutionError("No secret provider configured", {
|
|
57
|
-
ref: refs[0].ref,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
const results = new Map();
|
|
61
|
-
if (this.provider.resolveMany) {
|
|
62
|
-
const batchRefs = refs.map((r) => ({
|
|
63
|
-
ref: r.ref,
|
|
64
|
-
options: {
|
|
65
|
-
version: r.version,
|
|
66
|
-
field: r.field,
|
|
67
|
-
},
|
|
68
|
-
}));
|
|
69
|
-
try {
|
|
70
|
-
const batchResults = await this.provider.resolveMany(batchRefs);
|
|
71
|
-
for (const secretRef of refs) {
|
|
72
|
-
const value = batchResults.get(secretRef.ref);
|
|
73
|
-
if (value === undefined) {
|
|
74
|
-
throw new SecretResolutionError(`Secret "${secretRef.ref}" not found in batch results`, { ref: secretRef.ref });
|
|
75
|
-
}
|
|
76
|
-
results.set(this.makeKey(secretRef), value);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
if (error instanceof SecretResolutionError) {
|
|
81
|
-
throw error;
|
|
82
|
-
}
|
|
83
|
-
throw new SecretResolutionError(`Batch resolution failed: ${error instanceof Error ? error.message : String(error)}`, {
|
|
84
|
-
ref: refs[0].ref,
|
|
85
|
-
cause: error,
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
for (const secretRef of refs) {
|
|
91
|
-
try {
|
|
92
|
-
const value = await this.provider.resolve(secretRef.ref, {
|
|
93
|
-
version: secretRef.version,
|
|
94
|
-
field: secretRef.field,
|
|
95
|
-
});
|
|
96
|
-
results.set(this.makeKey(secretRef), value);
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
if (error instanceof SecretResolutionError) {
|
|
100
|
-
throw error;
|
|
101
|
-
}
|
|
102
|
-
throw new SecretResolutionError(`Failed to resolve secret "${secretRef.ref}": ${error instanceof Error ? error.message : String(error)}`, { ref: secretRef.ref, cause: error });
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return results;
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Validate the configured provider.
|
|
110
|
-
* @throws Error if provider validation fails
|
|
111
|
-
*/
|
|
112
|
-
async validateAll() {
|
|
113
|
-
if (this.provider?.validate) {
|
|
114
|
-
try {
|
|
115
|
-
await this.provider.validate();
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
throw new Error(`Provider validation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Create a unique key for a secret reference (for caching/deduplication).
|
|
124
|
-
*/
|
|
125
|
-
makeKey(secretRef) {
|
|
126
|
-
const parts = [secretRef.ref];
|
|
127
|
-
if (secretRef.version)
|
|
128
|
-
parts.push(`v:${secretRef.version}`);
|
|
129
|
-
if (secretRef.field)
|
|
130
|
-
parts.push(`f:${secretRef.field}`);
|
|
131
|
-
return parts.join(":");
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
//# sourceMappingURL=registry.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/secrets/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEnD;;GAEG;AACH,MAAM,OAAO,sBAAsB;IACzB,QAAQ,GAA0B,IAAI,CAAC;IAE/C;;;OAGG;IACH,WAAW,CAAC,QAAwB;QAClC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CAAC,SAAwB;QACpC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,IAAI,qBAAqB,CAAC,+BAA+B,EAAE;gBAC/D,GAAG,EAAE,SAAS,CAAC,GAAG;aACnB,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE;gBAChD,OAAO,EAAE,SAAS,CAAC,OAAO;gBAC1B,KAAK,EAAE,SAAS,CAAC,KAAK;aACvB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,qBAAqB,EAAE,CAAC;gBAC3C,MAAM,KAAK,CAAC;YACd,CAAC;YACD,MAAM,IAAI,qBAAqB,CAC7B,6BAA6B,SAAS,CAAC,GAAG,MACxC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,EACF;gBACE,GAAG,EAAE,SAAS,CAAC,GAAG;gBAClB,KAAK,EAAE,KAAK;aACb,CACF,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CAAC,IAAqB;QACrC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,IAAI,GAAG,EAAE,CAAC;QACnB,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,IAAI,qBAAqB,CAAC,+BAA+B,EAAE;gBAC/D,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG;aACjB,CAAC,CAAC;QACL,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;QAE1C,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACjC,GAAG,EAAE,CAAC,CAAC,GAAG;gBACV,OAAO,EAAE;oBACP,OAAO,EAAE,CAAC,CAAC,OAAO;oBAClB,KAAK,EAAE,CAAC,CAAC,KAAK;iBACS;aAC1B,CAAC,CAAC,CAAC;YAEJ,IAAI,CAAC;gBACH,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;gBAEhE,KAAK,MAAM,SAAS,IAAI,IAAI,EAAE,CAAC;oBAC7B,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;oBAC9C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;wBACxB,MAAM,IAAI,qBAAqB,CAC7B,WAAW,SAAS,CAAC,GAAG,8BAA8B,EACtD,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,CACvB,CAAC;oBACJ,CAAC;oBACD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,KAAK,YAAY,qBAAqB,EAAE,CAAC;oBAC3C,MAAM,KAAK,CAAC;gBACd,CAAC;gBACD,MAAM,IAAI,qBAAqB,CAC7B,4BACE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,EACF;oBACE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG;oBAChB,KAAK,EAAE,KAAK;iBACb,CACF,CAAC;YACJ,CAAC;QACH,CAAC;aAAM,CAAC;YACN,KAAK,MAAM,SAAS,IAAI,IAAI,EAAE,CAAC;gBAC7B,IAAI,CAAC;oBACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE;wBACvD,OAAO,EAAE,SAAS,CAAC,OAAO;wBAC1B,KAAK,EAAE,SAAS,CAAC,KAAK;qBACvB,CAAC,CAAC;oBACH,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,CAAC;gBAC9C,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,IAAI,KAAK,YAAY,qBAAqB,EAAE,CAAC;wBAC3C,MAAM,KAAK,CAAC;oBACd,CAAC;oBACD,MAAM,IAAI,qBAAqB,CAC7B,6BAA6B,SAAS,CAAC,GAAG,MACxC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,EACF,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CACrC,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,WAAW;QACf,IAAI,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACjC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CACb,+BACE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,CACH,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACH,OAAO,CAAC,SAAwB;QAC9B,MAAM,KAAK,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,SAAS,CAAC,OAAO;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC;QAC5D,IAAI,SAAS,CAAC,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;QACxD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;CACF"}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
2
|
-
export type ResponseFormat = "JSON" | "XML" | "TEXT";
|
|
3
|
-
export interface HttpRequest {
|
|
4
|
-
id: string;
|
|
5
|
-
type: "HTTP_REQUEST";
|
|
6
|
-
method: HttpMethod;
|
|
7
|
-
path: string;
|
|
8
|
-
response_format: ResponseFormat;
|
|
9
|
-
headers?: Record<string, string>;
|
|
10
|
-
body?: any;
|
|
11
|
-
}
|
|
12
|
-
export interface WaitNode {
|
|
13
|
-
id: string;
|
|
14
|
-
type: "WAIT";
|
|
15
|
-
duration_ms: number;
|
|
16
|
-
}
|
|
17
|
-
export interface AssertionNode {
|
|
18
|
-
id: string;
|
|
19
|
-
type: "ASSERTION";
|
|
20
|
-
assertions: Assertion[];
|
|
21
|
-
}
|
|
22
|
-
export interface Assertion {
|
|
23
|
-
type: string;
|
|
24
|
-
expected?: any;
|
|
25
|
-
actual?: any;
|
|
26
|
-
message?: string;
|
|
27
|
-
}
|
|
28
|
-
export interface Edge {
|
|
29
|
-
from: string;
|
|
30
|
-
to: string;
|
|
31
|
-
}
|
|
32
|
-
export interface Frequency {
|
|
33
|
-
every: number;
|
|
34
|
-
unit: "minute" | "hour" | "day";
|
|
35
|
-
}
|
|
36
|
-
export interface TestPlan {
|
|
37
|
-
name: string;
|
|
38
|
-
endpoint_host: string;
|
|
39
|
-
frequency?: Frequency;
|
|
40
|
-
nodes: (HttpRequest | WaitNode | AssertionNode)[];
|
|
41
|
-
edges: Edge[];
|
|
42
|
-
}
|
|
43
|
-
//# sourceMappingURL=test-plan-types.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test-plan-types.d.ts","sourceRoot":"","sources":["../src/test-plan-types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;AACrE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,cAAc,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;CACjC;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,KAAK,EAAE,CAAC,WAAW,GAAG,QAAQ,GAAG,aAAa,CAAC,EAAE,CAAC;IAClD,KAAK,EAAE,IAAI,EAAE,CAAC;CACf"}
|
package/dist/test-plan-types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test-plan-types.js","sourceRoot":"","sources":["../src/test-plan-types.ts"],"names":[],"mappings":""}
|
|
@@ -1,257 +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
|
-
|
|
13
|
-
import type { SecretProvider, SecretResolveOptions } from "../types.js";
|
|
14
|
-
import { SecretResolutionError } from "../types.js";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* HTTP client interface for Vault API calls.
|
|
18
|
-
* This allows dependency injection without requiring a specific HTTP library.
|
|
19
|
-
*/
|
|
20
|
-
export interface VaultHttpClient {
|
|
21
|
-
get(
|
|
22
|
-
url: string,
|
|
23
|
-
options: { headers: Record<string, string> },
|
|
24
|
-
): Promise<{
|
|
25
|
-
status: number;
|
|
26
|
-
data: unknown;
|
|
27
|
-
}>;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface VaultProviderOptions {
|
|
31
|
-
/**
|
|
32
|
-
* Vault server address (e.g., "https://vault.example.com:8200").
|
|
33
|
-
*/
|
|
34
|
-
address: string;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Authentication token.
|
|
38
|
-
*/
|
|
39
|
-
token: string;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* HTTP client for making requests to Vault.
|
|
43
|
-
*/
|
|
44
|
-
httpClient: VaultHttpClient;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Optional namespace for Vault Enterprise.
|
|
48
|
-
*/
|
|
49
|
-
namespace?: string;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* KV secrets engine version (1 or 2).
|
|
53
|
-
* Defaults to 2.
|
|
54
|
-
*/
|
|
55
|
-
kvVersion?: 1 | 2;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Optional prefix for secret paths.
|
|
59
|
-
*/
|
|
60
|
-
prefix?: string;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export class VaultProvider implements SecretProvider {
|
|
64
|
-
readonly name = "vault";
|
|
65
|
-
private readonly address: string;
|
|
66
|
-
private readonly token: string;
|
|
67
|
-
private readonly httpClient: VaultHttpClient;
|
|
68
|
-
private readonly namespace?: string;
|
|
69
|
-
private readonly kvVersion: 1 | 2;
|
|
70
|
-
private readonly prefix: string;
|
|
71
|
-
|
|
72
|
-
// Simple in-memory cache with TTL
|
|
73
|
-
private cache = new Map<
|
|
74
|
-
string,
|
|
75
|
-
{ value: Record<string, unknown>; expires: number }
|
|
76
|
-
>();
|
|
77
|
-
private readonly cacheTtlMs = 5 * 60 * 1000; // 5 minutes
|
|
78
|
-
|
|
79
|
-
constructor(options: VaultProviderOptions) {
|
|
80
|
-
this.address = options.address.replace(/\/$/, ""); // Remove trailing slash
|
|
81
|
-
this.token = options.token;
|
|
82
|
-
this.httpClient = options.httpClient;
|
|
83
|
-
this.namespace = options.namespace;
|
|
84
|
-
this.kvVersion = options.kvVersion ?? 2;
|
|
85
|
-
this.prefix = options.prefix ?? "";
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async resolve(ref: string, options?: SecretResolveOptions): Promise<string> {
|
|
89
|
-
const secretPath = this.prefix + ref;
|
|
90
|
-
const version = options?.version;
|
|
91
|
-
const cacheKey = `${secretPath}:${version ?? "latest"}`;
|
|
92
|
-
|
|
93
|
-
// Check cache
|
|
94
|
-
const cached = this.cache.get(cacheKey);
|
|
95
|
-
if (cached && cached.expires > Date.now()) {
|
|
96
|
-
return this.extractField(cached.value, options?.field, ref);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
// Build request URL
|
|
101
|
-
let url = `${this.address}/v1/${secretPath}`;
|
|
102
|
-
if (this.kvVersion === 2 && version) {
|
|
103
|
-
url += `?version=${version}`;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Build headers
|
|
107
|
-
const headers: Record<string, string> = {
|
|
108
|
-
"X-Vault-Token": this.token,
|
|
109
|
-
};
|
|
110
|
-
if (this.namespace) {
|
|
111
|
-
headers["X-Vault-Namespace"] = this.namespace;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const response = await this.httpClient.get(url, { headers });
|
|
115
|
-
|
|
116
|
-
if (response.status === 404) {
|
|
117
|
-
throw new SecretResolutionError(
|
|
118
|
-
`Secret "${secretPath}" not found in Vault`,
|
|
119
|
-
{ ref },
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (response.status === 403) {
|
|
124
|
-
throw new SecretResolutionError(
|
|
125
|
-
`Access denied to secret "${secretPath}". Check Vault policies.`,
|
|
126
|
-
{ ref },
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (response.status !== 200) {
|
|
131
|
-
throw new SecretResolutionError(
|
|
132
|
-
`Vault returned status ${response.status} for secret "${secretPath}"`,
|
|
133
|
-
{ ref },
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Parse response based on KV version
|
|
138
|
-
const data = response.data as {
|
|
139
|
-
data?: Record<string, unknown> | { data?: Record<string, unknown> };
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
let secretData: Record<string, unknown>;
|
|
143
|
-
|
|
144
|
-
if (this.kvVersion === 2) {
|
|
145
|
-
// KV v2 wraps data in an extra "data" object
|
|
146
|
-
const kvData = data?.data as
|
|
147
|
-
| { data?: Record<string, unknown> }
|
|
148
|
-
| undefined;
|
|
149
|
-
if (!kvData?.data) {
|
|
150
|
-
throw new SecretResolutionError(
|
|
151
|
-
`Invalid KV v2 response structure for secret "${secretPath}"`,
|
|
152
|
-
{ ref },
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
secretData = kvData.data;
|
|
156
|
-
} else {
|
|
157
|
-
// KV v1 has data directly
|
|
158
|
-
if (!data?.data || typeof data.data !== "object") {
|
|
159
|
-
throw new SecretResolutionError(
|
|
160
|
-
`Invalid KV v1 response structure for secret "${secretPath}"`,
|
|
161
|
-
{ ref },
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
secretData = data.data as Record<string, unknown>;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Cache the secret data
|
|
168
|
-
this.cache.set(cacheKey, {
|
|
169
|
-
value: secretData,
|
|
170
|
-
expires: Date.now() + this.cacheTtlMs,
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
return this.extractField(secretData, options?.field, ref);
|
|
174
|
-
} catch (error) {
|
|
175
|
-
if (error instanceof SecretResolutionError) {
|
|
176
|
-
throw error;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
throw new SecretResolutionError(
|
|
180
|
-
`Failed to retrieve secret "${secretPath}" from Vault: ${
|
|
181
|
-
error instanceof Error ? error.message : String(error)
|
|
182
|
-
}`,
|
|
183
|
-
{ ref, cause: error },
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Extract a field from the secret data.
|
|
190
|
-
* If no field is specified, returns the entire data as JSON string.
|
|
191
|
-
*/
|
|
192
|
-
private extractField(
|
|
193
|
-
secretData: Record<string, unknown>,
|
|
194
|
-
field: string | undefined,
|
|
195
|
-
ref: string,
|
|
196
|
-
): string {
|
|
197
|
-
if (!field) {
|
|
198
|
-
// Return entire secret as JSON if no field specified
|
|
199
|
-
return JSON.stringify(secretData);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const value = secretData[field];
|
|
203
|
-
|
|
204
|
-
if (value === undefined) {
|
|
205
|
-
throw new SecretResolutionError(
|
|
206
|
-
`Field "${field}" not found in secret "${ref}"`,
|
|
207
|
-
{ ref },
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Convert to string if not already
|
|
212
|
-
return typeof value === "string" ? value : JSON.stringify(value);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async validate(): Promise<void> {
|
|
216
|
-
// Verify we can authenticate with Vault
|
|
217
|
-
try {
|
|
218
|
-
const headers: Record<string, string> = {
|
|
219
|
-
"X-Vault-Token": this.token,
|
|
220
|
-
};
|
|
221
|
-
if (this.namespace) {
|
|
222
|
-
headers["X-Vault-Namespace"] = this.namespace;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const response = await this.httpClient.get(
|
|
226
|
-
`${this.address}/v1/auth/token/lookup-self`,
|
|
227
|
-
{ headers },
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
if (response.status === 403) {
|
|
231
|
-
throw new Error("Invalid or expired Vault token");
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (response.status !== 200) {
|
|
235
|
-
throw new Error(
|
|
236
|
-
`Vault authentication check failed with status ${response.status}`,
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
} catch (error) {
|
|
240
|
-
if (error instanceof Error && error.message.includes("Vault")) {
|
|
241
|
-
throw error;
|
|
242
|
-
}
|
|
243
|
-
throw new Error(
|
|
244
|
-
`Failed to connect to Vault at ${this.address}: ${
|
|
245
|
-
error instanceof Error ? error.message : String(error)
|
|
246
|
-
}`,
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Clear the cache. Useful for testing or forced refresh.
|
|
253
|
-
*/
|
|
254
|
-
clearCache(): void {
|
|
255
|
-
this.cache.clear();
|
|
256
|
-
}
|
|
257
|
-
}
|