@griffin-app/griffin-executor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -0
- package/dist/adapters/axios.d.ts +5 -0
- package/dist/adapters/axios.d.ts.map +1 -0
- package/dist/adapters/axios.js +36 -0
- package/dist/adapters/axios.js.map +1 -0
- package/dist/adapters/index.d.ts +3 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/stub.d.ts +22 -0
- package/dist/adapters/stub.d.ts.map +1 -0
- package/dist/adapters/stub.js +36 -0
- package/dist/adapters/stub.js.map +1 -0
- package/dist/events/adapters/in-memory.d.ts +52 -0
- package/dist/events/adapters/in-memory.d.ts.map +1 -0
- package/dist/events/adapters/in-memory.js +70 -0
- package/dist/events/adapters/in-memory.js.map +1 -0
- package/dist/events/adapters/in-memory.test.d.ts +2 -0
- package/dist/events/adapters/in-memory.test.d.ts.map +1 -0
- package/dist/events/adapters/in-memory.test.js +109 -0
- package/dist/events/adapters/in-memory.test.js.map +1 -0
- package/dist/events/adapters/index.d.ts +9 -0
- package/dist/events/adapters/index.d.ts.map +1 -0
- package/dist/events/adapters/index.js +9 -0
- package/dist/events/adapters/index.js.map +1 -0
- package/dist/events/adapters/kinesis.d.ts +91 -0
- package/dist/events/adapters/kinesis.d.ts.map +1 -0
- package/dist/events/adapters/kinesis.js +136 -0
- package/dist/events/adapters/kinesis.js.map +1 -0
- package/dist/events/adapters/kinesis.test.d.ts +2 -0
- package/dist/events/adapters/kinesis.test.d.ts.map +1 -0
- package/dist/events/adapters/kinesis.test.js +249 -0
- package/dist/events/adapters/kinesis.test.js.map +1 -0
- package/dist/events/emitter.d.ts +68 -0
- package/dist/events/emitter.d.ts.map +1 -0
- package/dist/events/emitter.js +83 -0
- package/dist/events/emitter.js.map +1 -0
- package/dist/events/emitter.test.d.ts +2 -0
- package/dist/events/emitter.test.d.ts.map +1 -0
- package/dist/events/emitter.test.js +262 -0
- package/dist/events/emitter.test.js.map +1 -0
- package/dist/events/index.d.ts +4 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +4 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/types.d.ts +112 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/events/types.js +9 -0
- package/dist/events/types.js.map +1 -0
- package/dist/executor.d.ts +4 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +799 -0
- package/dist/executor.js.map +1 -0
- package/dist/executor.test.d.ts +2 -0
- package/dist/executor.test.d.ts.map +1 -0
- package/dist/executor.test.js +1584 -0
- package/dist/executor.test.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/secrets/factory.d.ts +121 -0
- package/dist/secrets/factory.d.ts.map +1 -0
- package/dist/secrets/factory.js +137 -0
- package/dist/secrets/factory.js.map +1 -0
- package/dist/secrets/index.d.ts +14 -0
- package/dist/secrets/index.d.ts.map +1 -0
- package/dist/secrets/index.js +18 -0
- package/dist/secrets/index.js.map +1 -0
- package/dist/secrets/providers/aws.d.ts +63 -0
- package/dist/secrets/providers/aws.d.ts.map +1 -0
- package/dist/secrets/providers/aws.js +110 -0
- package/dist/secrets/providers/aws.js.map +1 -0
- package/dist/secrets/providers/env.d.ts +36 -0
- package/dist/secrets/providers/env.d.ts.map +1 -0
- package/dist/secrets/providers/env.js +37 -0
- package/dist/secrets/providers/env.js.map +1 -0
- package/dist/secrets/providers/index.d.ts +7 -0
- package/dist/secrets/providers/index.d.ts.map +1 -0
- package/dist/secrets/providers/index.js +7 -0
- package/dist/secrets/providers/index.js.map +1 -0
- package/dist/secrets/providers/vault.d.ts +75 -0
- package/dist/secrets/providers/vault.d.ts.map +1 -0
- package/dist/secrets/providers/vault.js +143 -0
- package/dist/secrets/providers/vault.js.map +1 -0
- package/dist/secrets/registry.d.ts +39 -0
- package/dist/secrets/registry.d.ts.map +1 -0
- package/dist/secrets/registry.js +134 -0
- package/dist/secrets/registry.js.map +1 -0
- package/dist/secrets/resolver.d.ts +45 -0
- package/dist/secrets/resolver.d.ts.map +1 -0
- package/dist/secrets/resolver.js +188 -0
- package/dist/secrets/resolver.js.map +1 -0
- package/dist/secrets/secrets.test.d.ts +2 -0
- package/dist/secrets/secrets.test.d.ts.map +1 -0
- package/dist/secrets/secrets.test.js +317 -0
- package/dist/secrets/secrets.test.js.map +1 -0
- package/dist/secrets/types.d.ts +70 -0
- package/dist/secrets/types.d.ts.map +1 -0
- package/dist/secrets/types.js +42 -0
- package/dist/secrets/types.js.map +1 -0
- package/dist/shared.d.ts +8 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +30 -0
- package/dist/shared.js.map +1 -0
- package/dist/test-monitor-types.d.ts +43 -0
- package/dist/test-monitor-types.d.ts.map +1 -0
- package/dist/test-monitor-types.js +2 -0
- package/dist/test-monitor-types.js.map +1 -0
- package/dist/test-plan-types.d.ts +43 -0
- package/dist/test-plan-types.d.ts.map +1 -0
- package/dist/test-plan-types.js +2 -0
- package/dist/test-plan-types.js.map +1 -0
- package/dist/types.d.ts +93 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/dates.d.ts +11 -0
- package/dist/utils/dates.d.ts.map +1 -0
- package/dist/utils/dates.js +13 -0
- package/dist/utils/dates.js.map +1 -0
- package/package.json +39 -0
- package/src/adapters/axios.ts +39 -0
- package/src/adapters/index.ts +2 -0
- package/src/adapters/stub.ts +47 -0
- package/src/events/adapters/README.md +144 -0
- package/src/events/adapters/in-memory.test.ts +146 -0
- package/src/events/adapters/in-memory.ts +93 -0
- package/src/events/adapters/index.ts +9 -0
- package/src/events/adapters/kinesis.test.ts +323 -0
- package/src/events/adapters/kinesis.ts +211 -0
- package/src/events/emitter.test.ts +327 -0
- package/src/events/emitter.ts +133 -0
- package/src/events/index.ts +3 -0
- package/src/events/types.ts +136 -0
- package/src/executor.test.ts +1732 -0
- package/src/executor.ts +1075 -0
- package/src/index.ts +81 -0
- package/src/secrets/factory.ts +248 -0
- package/src/secrets/index.ts +48 -0
- package/src/secrets/providers/aws.ts +178 -0
- package/src/secrets/providers/env.ts +66 -0
- package/src/secrets/providers/index.ts +15 -0
- package/src/secrets/providers/vault.ts +257 -0
- package/src/secrets/resolver.ts +269 -0
- package/src/secrets/secrets.test.ts +402 -0
- package/src/secrets/types.ts +106 -0
- package/src/shared.ts +46 -0
- package/src/test-monitor-types.ts +49 -0
- package/src/types.ts +114 -0
- package/src/utils/dates.ts +13 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +14 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export { executeMonitorV1 } from "./executor.js";
|
|
2
|
+
export type {
|
|
3
|
+
ExecutionOptions,
|
|
4
|
+
ExecutionResult,
|
|
5
|
+
NodeResult,
|
|
6
|
+
HttpClientAdapter,
|
|
7
|
+
HttpRequest,
|
|
8
|
+
HttpResponse,
|
|
9
|
+
RunStatusUpdate,
|
|
10
|
+
StatusCallbacks,
|
|
11
|
+
} from "./types.js";
|
|
12
|
+
export type {
|
|
13
|
+
TestMonitor,
|
|
14
|
+
HttpRequest as HttpRequestNode,
|
|
15
|
+
WaitNode,
|
|
16
|
+
AssertionNode,
|
|
17
|
+
Edge,
|
|
18
|
+
} from "./test-monitor-types.js";
|
|
19
|
+
export {
|
|
20
|
+
AxiosAdapter,
|
|
21
|
+
StubAdapter,
|
|
22
|
+
type StubResponse,
|
|
23
|
+
} from "./adapters/index.js";
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
LocalEventEmitter,
|
|
27
|
+
DurableEventEmitter,
|
|
28
|
+
type ExecutionEventEmitter,
|
|
29
|
+
type DurableEventBusAdapter,
|
|
30
|
+
} from "./events/emitter.js";
|
|
31
|
+
export type {
|
|
32
|
+
ExecutionEvent,
|
|
33
|
+
BaseEvent,
|
|
34
|
+
MonitorStartEvent,
|
|
35
|
+
MonitorEndEvent,
|
|
36
|
+
NodeStartEvent,
|
|
37
|
+
NodeEndEvent,
|
|
38
|
+
HttpRequestEvent,
|
|
39
|
+
HttpResponseEvent,
|
|
40
|
+
HttpRetryEvent,
|
|
41
|
+
AssertionResultEvent,
|
|
42
|
+
WaitStartEvent,
|
|
43
|
+
NodeStreamEvent,
|
|
44
|
+
ErrorEvent,
|
|
45
|
+
} from "./events/types.js";
|
|
46
|
+
export {
|
|
47
|
+
// Event bus adapters
|
|
48
|
+
KinesisAdapter,
|
|
49
|
+
type KinesisAdapterOptions,
|
|
50
|
+
InMemoryAdapter,
|
|
51
|
+
} from "./events/adapters/index.js";
|
|
52
|
+
|
|
53
|
+
// Export secrets system
|
|
54
|
+
export {
|
|
55
|
+
// Core types and utilities
|
|
56
|
+
type SecretProvider,
|
|
57
|
+
type SecretRef,
|
|
58
|
+
type SecretRefData,
|
|
59
|
+
type SecretResolveOptions,
|
|
60
|
+
SecretResolutionError,
|
|
61
|
+
isSecretRef,
|
|
62
|
+
// Resolution utilities
|
|
63
|
+
resolveSecretsInMonitor,
|
|
64
|
+
collectSecretsFromMonitor,
|
|
65
|
+
planHasSecrets,
|
|
66
|
+
// Providers
|
|
67
|
+
EnvSecretProvider,
|
|
68
|
+
type EnvSecretProviderOptions,
|
|
69
|
+
AwsSecretsManagerProvider,
|
|
70
|
+
type AwsSecretsManagerProviderOptions,
|
|
71
|
+
type AwsSecretsManagerClient,
|
|
72
|
+
VaultProvider,
|
|
73
|
+
type VaultProviderOptions,
|
|
74
|
+
type VaultHttpClient,
|
|
75
|
+
// Factory functions
|
|
76
|
+
createSecretProvider,
|
|
77
|
+
type SecretProviderConfig,
|
|
78
|
+
type EnvProviderConfig,
|
|
79
|
+
type AwsProviderConfig,
|
|
80
|
+
type VaultProviderConfig,
|
|
81
|
+
} from "./secrets/index.js";
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory for creating SecretProvider instances from configuration.
|
|
3
|
+
*
|
|
4
|
+
* This module provides pure factory functions that construct providers
|
|
5
|
+
* from config objects. Callers (hub, CLI, agents) are responsible for
|
|
6
|
+
* sourcing the configuration from their environment.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { SecretProvider } from "./types.js";
|
|
10
|
+
import { EnvSecretProvider } from "./providers/env.js";
|
|
11
|
+
import { AwsSecretsManagerProvider, type AwsSecretsManagerClient } from "./providers/aws.js";
|
|
12
|
+
import { VaultProvider, type VaultHttpClient } from "./providers/vault.js";
|
|
13
|
+
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
|
|
14
|
+
import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Configuration for environment variable provider.
|
|
19
|
+
*/
|
|
20
|
+
export interface EnvProviderConfig {
|
|
21
|
+
provider: "env";
|
|
22
|
+
/**
|
|
23
|
+
* Optional prefix to prepend to secret refs.
|
|
24
|
+
* For example, prefix="APP_" means secret("env:API_KEY") looks for "APP_API_KEY".
|
|
25
|
+
*/
|
|
26
|
+
prefix?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Custom environment object to read from.
|
|
29
|
+
* If not provided, caller should pass process.env.
|
|
30
|
+
*/
|
|
31
|
+
env?: Record<string, string | undefined>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Configuration for AWS Secrets Manager provider.
|
|
36
|
+
*/
|
|
37
|
+
export interface AwsProviderConfig {
|
|
38
|
+
provider: "aws";
|
|
39
|
+
/**
|
|
40
|
+
* AWS region for Secrets Manager.
|
|
41
|
+
*/
|
|
42
|
+
region: string;
|
|
43
|
+
/**
|
|
44
|
+
* Optional prefix for secret names.
|
|
45
|
+
* For example, prefix="myapp/" means secret("aws:api-key") looks for "myapp/api-key".
|
|
46
|
+
*/
|
|
47
|
+
prefix?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Optional IAM role to assume.
|
|
50
|
+
*/
|
|
51
|
+
roleArn?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Optional external ID for role assumption.
|
|
54
|
+
*/
|
|
55
|
+
externalId?: string;
|
|
56
|
+
/**
|
|
57
|
+
* AWS credentials. If not provided, uses ambient credentials (IAM role, environment, etc).
|
|
58
|
+
*/
|
|
59
|
+
credentials?: {
|
|
60
|
+
accessKeyId: string;
|
|
61
|
+
secretAccessKey: string;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Configuration for HashiCorp Vault provider.
|
|
67
|
+
*/
|
|
68
|
+
export interface VaultProviderConfig {
|
|
69
|
+
provider: "vault";
|
|
70
|
+
/**
|
|
71
|
+
* Vault server address (e.g., "https://vault.example.com").
|
|
72
|
+
*/
|
|
73
|
+
address: string;
|
|
74
|
+
/**
|
|
75
|
+
* Vault authentication token.
|
|
76
|
+
*/
|
|
77
|
+
token: string;
|
|
78
|
+
/**
|
|
79
|
+
* Optional Vault namespace.
|
|
80
|
+
*/
|
|
81
|
+
namespace?: string;
|
|
82
|
+
/**
|
|
83
|
+
* KV secrets engine version (1 or 2). Defaults to 2.
|
|
84
|
+
*/
|
|
85
|
+
kvVersion?: 1 | 2;
|
|
86
|
+
/**
|
|
87
|
+
* Optional prefix for secret paths.
|
|
88
|
+
*/
|
|
89
|
+
prefix?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Union type for all provider configurations.
|
|
94
|
+
*/
|
|
95
|
+
export type SecretProviderConfig =
|
|
96
|
+
| EnvProviderConfig
|
|
97
|
+
| AwsProviderConfig
|
|
98
|
+
| VaultProviderConfig;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Create a SecretProvider from configuration.
|
|
102
|
+
*
|
|
103
|
+
* This is a pure factory function - it does not access environment variables
|
|
104
|
+
* or external state. All configuration must be provided by the caller.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* // Environment provider
|
|
108
|
+
* const envProvider = await createSecretProvider({
|
|
109
|
+
* provider: "env",
|
|
110
|
+
* prefix: "APP_",
|
|
111
|
+
* env: process.env,
|
|
112
|
+
* });
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* // AWS provider with credentials
|
|
116
|
+
* const awsProvider = await createSecretProvider({
|
|
117
|
+
* provider: "aws",
|
|
118
|
+
* region: "us-east-1",
|
|
119
|
+
* prefix: "myapp/",
|
|
120
|
+
* credentials: {
|
|
121
|
+
* accessKeyId: "...",
|
|
122
|
+
* secretAccessKey: "...",
|
|
123
|
+
* },
|
|
124
|
+
* });
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* // Vault provider
|
|
128
|
+
* const vaultProvider = await createSecretProvider({
|
|
129
|
+
* provider: "vault",
|
|
130
|
+
* address: "https://vault.example.com",
|
|
131
|
+
* token: "...",
|
|
132
|
+
* kvVersion: 2,
|
|
133
|
+
* });
|
|
134
|
+
*/
|
|
135
|
+
export async function createSecretProvider(
|
|
136
|
+
config: SecretProviderConfig,
|
|
137
|
+
): Promise<SecretProvider> {
|
|
138
|
+
switch (config.provider) {
|
|
139
|
+
case "env":
|
|
140
|
+
return new EnvSecretProvider({
|
|
141
|
+
prefix: config.prefix,
|
|
142
|
+
env: config.env,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
case "aws":
|
|
146
|
+
return createAwsProvider(config);
|
|
147
|
+
|
|
148
|
+
case "vault":
|
|
149
|
+
return createVaultProvider(config);
|
|
150
|
+
|
|
151
|
+
default:
|
|
152
|
+
const exhaustive: never = config;
|
|
153
|
+
throw new Error(`Unknown provider type: ${(exhaustive as any).provider}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Create AWS Secrets Manager provider with SDK client.
|
|
159
|
+
*/
|
|
160
|
+
async function createAwsProvider(
|
|
161
|
+
config: AwsProviderConfig,
|
|
162
|
+
): Promise<AwsSecretsManagerProvider> {
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
const clientConfig: {
|
|
166
|
+
region: string;
|
|
167
|
+
credentials?: {
|
|
168
|
+
accessKeyId: string;
|
|
169
|
+
secretAccessKey: string;
|
|
170
|
+
sessionToken?: string;
|
|
171
|
+
};
|
|
172
|
+
} = { region: config.region };
|
|
173
|
+
|
|
174
|
+
// Handle role assumption if roleArn is provided
|
|
175
|
+
if (config.roleArn) {
|
|
176
|
+
const stsClient = new STSClient({ region: config.region });
|
|
177
|
+
const assumeRoleResponse = await stsClient.send(
|
|
178
|
+
new AssumeRoleCommand({
|
|
179
|
+
RoleArn: config.roleArn,
|
|
180
|
+
RoleSessionName: "griffin-executor",
|
|
181
|
+
ExternalId: config.externalId,
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
if (assumeRoleResponse.Credentials) {
|
|
186
|
+
clientConfig.credentials = {
|
|
187
|
+
accessKeyId: assumeRoleResponse.Credentials.AccessKeyId ?? "",
|
|
188
|
+
secretAccessKey: assumeRoleResponse.Credentials.SecretAccessKey ?? "",
|
|
189
|
+
sessionToken: assumeRoleResponse.Credentials.SessionToken,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
} else if (config.credentials) {
|
|
193
|
+
clientConfig.credentials = config.credentials;
|
|
194
|
+
}
|
|
195
|
+
// Otherwise uses ambient AWS credentials (IAM role, environment variables, etc)
|
|
196
|
+
|
|
197
|
+
const smClient = new SecretsManagerClient(clientConfig);
|
|
198
|
+
|
|
199
|
+
// Wrap SDK client with the interface expected by AwsSecretsManagerProvider
|
|
200
|
+
const client: AwsSecretsManagerClient = {
|
|
201
|
+
async getSecretValue(params: { SecretId: string; VersionStage?: string }) {
|
|
202
|
+
const command = new GetSecretValueCommand({
|
|
203
|
+
SecretId: params.SecretId,
|
|
204
|
+
VersionStage: params.VersionStage,
|
|
205
|
+
});
|
|
206
|
+
const response = await smClient.send(command);
|
|
207
|
+
return {
|
|
208
|
+
SecretString: response.SecretString,
|
|
209
|
+
SecretBinary: response.SecretBinary,
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
return new AwsSecretsManagerProvider({
|
|
215
|
+
client,
|
|
216
|
+
prefix: config.prefix,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Create Vault provider with HTTP client.
|
|
222
|
+
*/
|
|
223
|
+
function createVaultProvider(config: VaultProviderConfig): VaultProvider {
|
|
224
|
+
// Create HTTP client for Vault API calls
|
|
225
|
+
const httpClient: VaultHttpClient = {
|
|
226
|
+
async get(url: string, options: { headers: Record<string, string> }) {
|
|
227
|
+
// Import axios dynamically to avoid bundling it when not needed
|
|
228
|
+
const axios = await import("axios");
|
|
229
|
+
const response = await axios.default.get(url, {
|
|
230
|
+
headers: options.headers,
|
|
231
|
+
validateStatus: () => true, // Don't throw on non-2xx
|
|
232
|
+
});
|
|
233
|
+
return {
|
|
234
|
+
status: response.status,
|
|
235
|
+
data: response.data,
|
|
236
|
+
};
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
return new VaultProvider({
|
|
241
|
+
address: config.address,
|
|
242
|
+
token: config.token,
|
|
243
|
+
httpClient,
|
|
244
|
+
namespace: config.namespace,
|
|
245
|
+
kvVersion: config.kvVersion ?? 2,
|
|
246
|
+
prefix: config.prefix,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret management for griffin monitor executor.
|
|
3
|
+
*
|
|
4
|
+
* This module provides:
|
|
5
|
+
* - SecretProvider interface for implementing custom providers
|
|
6
|
+
* - Secret resolution utilities for test monitors
|
|
7
|
+
* - Built-in providers: env, aws, vault
|
|
8
|
+
* - Factory functions for creating providers from configuration
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Core types
|
|
12
|
+
export {
|
|
13
|
+
type SecretProvider,
|
|
14
|
+
type SecretRef,
|
|
15
|
+
type SecretRefData,
|
|
16
|
+
type SecretResolveOptions,
|
|
17
|
+
SecretResolutionError,
|
|
18
|
+
isSecretRef,
|
|
19
|
+
isStringLiteral,
|
|
20
|
+
} from "./types.js";
|
|
21
|
+
|
|
22
|
+
// Resolution utilities
|
|
23
|
+
export {
|
|
24
|
+
resolveSecretsInMonitor,
|
|
25
|
+
collectSecretsFromMonitor,
|
|
26
|
+
planHasSecrets,
|
|
27
|
+
} from "./resolver.js";
|
|
28
|
+
|
|
29
|
+
// Providers
|
|
30
|
+
export {
|
|
31
|
+
EnvSecretProvider,
|
|
32
|
+
type EnvSecretProviderOptions,
|
|
33
|
+
AwsSecretsManagerProvider,
|
|
34
|
+
type AwsSecretsManagerProviderOptions,
|
|
35
|
+
type AwsSecretsManagerClient,
|
|
36
|
+
VaultProvider,
|
|
37
|
+
type VaultProviderOptions,
|
|
38
|
+
type VaultHttpClient,
|
|
39
|
+
} from "./providers/index.js";
|
|
40
|
+
|
|
41
|
+
// Factory functions
|
|
42
|
+
export {
|
|
43
|
+
createSecretProvider,
|
|
44
|
+
type SecretProviderConfig,
|
|
45
|
+
type EnvProviderConfig,
|
|
46
|
+
type AwsProviderConfig,
|
|
47
|
+
type VaultProviderConfig,
|
|
48
|
+
} from "./factory.js";
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS Secrets Manager secret provider.
|
|
3
|
+
*
|
|
4
|
+
* Reads secrets from AWS Secrets Manager. Supports JSON secrets
|
|
5
|
+
* with field extraction and version staging.
|
|
6
|
+
*
|
|
7
|
+
* Usage in DSL:
|
|
8
|
+
* secret("aws:my-secret")
|
|
9
|
+
* secret("aws:prod/api-keys", { field: "stripe" })
|
|
10
|
+
* secret("aws:my-secret", { version: "AWSPREVIOUS" })
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { SecretProvider, SecretResolveOptions } from "../types.js";
|
|
14
|
+
import { SecretResolutionError } from "../types.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Interface for AWS Secrets Manager client.
|
|
18
|
+
* This allows dependency injection of the actual AWS SDK client.
|
|
19
|
+
*/
|
|
20
|
+
export interface AwsSecretsManagerClient {
|
|
21
|
+
getSecretValue(params: { SecretId: string; VersionStage?: string }): Promise<{
|
|
22
|
+
SecretString?: string;
|
|
23
|
+
SecretBinary?: Uint8Array;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AwsSecretsManagerProviderOptions {
|
|
28
|
+
/**
|
|
29
|
+
* AWS Secrets Manager client instance.
|
|
30
|
+
* Should be pre-configured with region and credentials.
|
|
31
|
+
*/
|
|
32
|
+
client: AwsSecretsManagerClient;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Optional prefix for secret names.
|
|
36
|
+
* For example, if prefix is "myapp/", then secret("aws:api-key")
|
|
37
|
+
* will look for "myapp/api-key" in Secrets Manager.
|
|
38
|
+
*/
|
|
39
|
+
prefix?: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Default version stage to use if not specified.
|
|
43
|
+
* Defaults to "AWSCURRENT".
|
|
44
|
+
*/
|
|
45
|
+
defaultVersionStage?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class AwsSecretsManagerProvider implements SecretProvider {
|
|
49
|
+
readonly name = "aws";
|
|
50
|
+
private readonly client: AwsSecretsManagerClient;
|
|
51
|
+
private readonly prefix: string;
|
|
52
|
+
private readonly defaultVersionStage: string;
|
|
53
|
+
|
|
54
|
+
// Simple in-memory cache with TTL
|
|
55
|
+
private cache = new Map<string, { value: string; expires: number }>();
|
|
56
|
+
private readonly cacheTtlMs = 5 * 60 * 1000; // 5 minutes
|
|
57
|
+
|
|
58
|
+
constructor(options: AwsSecretsManagerProviderOptions) {
|
|
59
|
+
this.client = options.client;
|
|
60
|
+
this.prefix = options.prefix ?? "";
|
|
61
|
+
this.defaultVersionStage = options.defaultVersionStage ?? "AWSCURRENT";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async resolve(ref: string, options?: SecretResolveOptions): Promise<string> {
|
|
65
|
+
const secretId = this.prefix + ref;
|
|
66
|
+
const versionStage = options?.version ?? this.defaultVersionStage;
|
|
67
|
+
const cacheKey = `${secretId}:${versionStage}`;
|
|
68
|
+
|
|
69
|
+
// Check cache
|
|
70
|
+
const cached = this.cache.get(cacheKey);
|
|
71
|
+
if (cached && cached.expires > Date.now()) {
|
|
72
|
+
return this.extractField(cached.value, options?.field, ref);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const response = await this.client.getSecretValue({
|
|
77
|
+
SecretId: secretId,
|
|
78
|
+
VersionStage: versionStage,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!response.SecretString) {
|
|
82
|
+
throw new SecretResolutionError(
|
|
83
|
+
`Secret "${secretId}" does not contain a string value (binary secrets are not supported)`,
|
|
84
|
+
{ ref },
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Cache the raw value
|
|
89
|
+
this.cache.set(cacheKey, {
|
|
90
|
+
value: response.SecretString,
|
|
91
|
+
expires: Date.now() + this.cacheTtlMs,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return this.extractField(response.SecretString, options?.field, ref);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error instanceof SecretResolutionError) {
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Handle common AWS errors
|
|
101
|
+
const awsError = error as { name?: string; message?: string };
|
|
102
|
+
let message = `Failed to retrieve secret "${secretId}"`;
|
|
103
|
+
|
|
104
|
+
if (awsError.name === "ResourceNotFoundException") {
|
|
105
|
+
message = `Secret "${secretId}" not found in AWS Secrets Manager`;
|
|
106
|
+
} else if (awsError.name === "AccessDeniedException") {
|
|
107
|
+
message = `Access denied to secret "${secretId}". Check IAM permissions.`;
|
|
108
|
+
} else if (awsError.message) {
|
|
109
|
+
message = `${message}: ${awsError.message}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
throw new SecretResolutionError(message, {
|
|
113
|
+
ref,
|
|
114
|
+
cause: error,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Extract a field from a JSON secret string.
|
|
121
|
+
*/
|
|
122
|
+
private extractField(
|
|
123
|
+
secretValue: string,
|
|
124
|
+
field: string | undefined,
|
|
125
|
+
ref: string,
|
|
126
|
+
): string {
|
|
127
|
+
if (!field) {
|
|
128
|
+
return secretValue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(secretValue);
|
|
133
|
+
|
|
134
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
135
|
+
throw new SecretResolutionError(
|
|
136
|
+
`Secret "${ref}" is not a JSON object, cannot extract field "${field}"`,
|
|
137
|
+
{ ref },
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const value = parsed[field];
|
|
142
|
+
|
|
143
|
+
if (value === undefined) {
|
|
144
|
+
throw new SecretResolutionError(
|
|
145
|
+
`Field "${field}" not found in secret "${ref}"`,
|
|
146
|
+
{ ref },
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Convert to string if not already
|
|
151
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (error instanceof SecretResolutionError) {
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
throw new SecretResolutionError(
|
|
158
|
+
`Failed to parse secret "${ref}" as JSON for field extraction: ${
|
|
159
|
+
error instanceof Error ? error.message : String(error)
|
|
160
|
+
}`,
|
|
161
|
+
{ ref, cause: error },
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async validate(): Promise<void> {
|
|
167
|
+
// Try a simple operation to verify credentials
|
|
168
|
+
// This is a no-op if the client is properly configured
|
|
169
|
+
// The actual validation happens on first secret access
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Clear the cache. Useful for testing or forced refresh.
|
|
174
|
+
*/
|
|
175
|
+
clearCache(): void {
|
|
176
|
+
this.cache.clear();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment variable secret provider.
|
|
3
|
+
*
|
|
4
|
+
* Reads secrets from process.env. Useful for local development
|
|
5
|
+
* and simple deployments where secrets are injected as environment variables.
|
|
6
|
+
*
|
|
7
|
+
* Usage in DSL:
|
|
8
|
+
* secret("env:MY_API_KEY")
|
|
9
|
+
* secret("env:DATABASE_URL")
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { SecretProvider, SecretResolveOptions } from "../types.js";
|
|
13
|
+
import { SecretResolutionError } from "../types.js";
|
|
14
|
+
|
|
15
|
+
export interface EnvSecretProviderOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Custom environment object to read from.
|
|
18
|
+
* Defaults to process.env.
|
|
19
|
+
*/
|
|
20
|
+
env?: Record<string, string | undefined>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Prefix to strip from secret refs.
|
|
24
|
+
* For example, if prefix is "APP_", then secret("env:API_KEY")
|
|
25
|
+
* will look for "APP_API_KEY" in the environment.
|
|
26
|
+
*/
|
|
27
|
+
prefix?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class EnvSecretProvider implements SecretProvider {
|
|
31
|
+
readonly name = "env";
|
|
32
|
+
private readonly env: Record<string, string | undefined>;
|
|
33
|
+
private readonly prefix: string;
|
|
34
|
+
|
|
35
|
+
constructor(options: EnvSecretProviderOptions = {}) {
|
|
36
|
+
this.env = options.env ?? process.env;
|
|
37
|
+
this.prefix = options.prefix ?? "";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async resolve(ref: string, _options?: SecretResolveOptions): Promise<string> {
|
|
41
|
+
const envKey = this.prefix + ref;
|
|
42
|
+
const value = this.env[envKey];
|
|
43
|
+
|
|
44
|
+
if (value === undefined) {
|
|
45
|
+
throw new SecretResolutionError(
|
|
46
|
+
`Environment variable "${envKey}" is not set`,
|
|
47
|
+
{ ref },
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async resolveMany(
|
|
55
|
+
refs: Array<{ ref: string; options?: SecretResolveOptions }>,
|
|
56
|
+
): Promise<Map<string, string>> {
|
|
57
|
+
const results = new Map<string, string>();
|
|
58
|
+
|
|
59
|
+
for (const { ref } of refs) {
|
|
60
|
+
const value = await this.resolve(ref);
|
|
61
|
+
results.set(ref, value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret provider implementations.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { EnvSecretProvider, type EnvSecretProviderOptions } from "./env.js";
|
|
6
|
+
export {
|
|
7
|
+
AwsSecretsManagerProvider,
|
|
8
|
+
type AwsSecretsManagerProviderOptions,
|
|
9
|
+
type AwsSecretsManagerClient,
|
|
10
|
+
} from "./aws.js";
|
|
11
|
+
export {
|
|
12
|
+
VaultProvider,
|
|
13
|
+
type VaultProviderOptions,
|
|
14
|
+
type VaultHttpClient,
|
|
15
|
+
} from "./vault.js";
|