@secrefs/node 0.1.0 → 0.2.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 +1 -1
- package/dist/index.cjs +331 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +178 -1
- package/dist/index.d.ts +178 -1
- package/dist/index.js +326 -10
- package/dist/index.js.map +1 -1
- package/dist/secrefs.cjs +361 -18
- package/dist/secrefs.cjs.map +1 -1
- package/package.json +5 -4
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
|
|
2
2
|
import vaultFactory from 'node-vault';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Why a fetch failed, and - more usefully - who has to do something
|
|
6
|
+
* about it.
|
|
7
|
+
*
|
|
8
|
+
* The distinction that matters is between a problem with *the reference*
|
|
9
|
+
* and a problem with *the environment*. They currently look identical to
|
|
10
|
+
* a caller, which produces the worst message SecRefs emits today: an
|
|
11
|
+
* expired `aws sso login` reported once per reference as
|
|
12
|
+
* `could not fetch secret "prod/db"`, blaming four healthy secrets for
|
|
13
|
+
* one dead credential.
|
|
14
|
+
*/
|
|
15
|
+
type SecretErrorKind =
|
|
16
|
+
/** Credentials are missing, expired, or unusable. Nothing about the
|
|
17
|
+
* reference is wrong; a human has to re-authenticate. Report once for
|
|
18
|
+
* the whole provider, never per reference. */
|
|
19
|
+
"auth"
|
|
20
|
+
/** Credentials worked and the backend says this path does not exist.
|
|
21
|
+
* Specific to the reference. */
|
|
22
|
+
| "not_found"
|
|
23
|
+
/** Credentials worked and the backend refused *this* path. Also
|
|
24
|
+
* specific to the reference - sending someone to re-login when the
|
|
25
|
+
* real problem is an IAM policy wastes their afternoon. */
|
|
26
|
+
| "denied"
|
|
27
|
+
/** Network, timeout, throttle, or 5xx. Nobody is at fault and the same
|
|
28
|
+
* call may well succeed a second later. The only kind for which
|
|
29
|
+
* serving a stale value is defensible. */
|
|
30
|
+
| "transient"
|
|
31
|
+
/** Unclassified. Treated as permanent, because guessing "transient"
|
|
32
|
+
* would mean retrying something that will never succeed. */
|
|
33
|
+
| "unknown";
|
|
34
|
+
|
|
4
35
|
/**
|
|
5
36
|
* The provider contract every SecRefs backend (AWS, Vault, local, or a
|
|
6
37
|
* custom one you bring yourself) implements. Providers never log, print,
|
|
@@ -41,6 +72,13 @@ interface ISecretProvider {
|
|
|
41
72
|
declare class SecretFetchError extends Error {
|
|
42
73
|
readonly provider: string;
|
|
43
74
|
readonly path: string;
|
|
75
|
+
readonly cause: unknown;
|
|
76
|
+
/** Whose problem this is - see {@link SecretErrorKind}. Classified from
|
|
77
|
+
* `cause` so every existing throw site is categorised without having to
|
|
78
|
+
* know about categories. */
|
|
79
|
+
readonly kind: SecretErrorKind;
|
|
80
|
+
/** The action that fixes it, when there is one (auth failures). */
|
|
81
|
+
readonly remedy?: string;
|
|
44
82
|
constructor(provider: string, path: string, cause: unknown);
|
|
45
83
|
}
|
|
46
84
|
declare abstract class BaseSecretProvider implements ISecretProvider {
|
|
@@ -78,10 +116,20 @@ interface ResolutionFailure {
|
|
|
78
116
|
/** The original `sec://` string. */
|
|
79
117
|
ref: string;
|
|
80
118
|
message: string;
|
|
119
|
+
/** Whose problem this is. `undefined` when the failure came from
|
|
120
|
+
* somewhere that doesn't classify (a malformed reference, say). */
|
|
121
|
+
kind?: SecretErrorKind;
|
|
122
|
+
/** Provider alias the reference named, for grouping auth failures. */
|
|
123
|
+
provider?: string;
|
|
124
|
+
/** What to run to fix it, for auth failures. */
|
|
125
|
+
remedy?: string;
|
|
81
126
|
}
|
|
82
127
|
declare class SecRefsResolutionError extends Error {
|
|
83
128
|
readonly errors: ResolutionFailure[];
|
|
84
129
|
constructor(errors: ResolutionFailure[]);
|
|
130
|
+
/** True when every failure was an environment/auth problem, so a caller
|
|
131
|
+
* can tell "your credentials lapsed" from "your references are wrong". */
|
|
132
|
+
get isAuthOnly(): boolean;
|
|
85
133
|
}
|
|
86
134
|
interface CheckResult {
|
|
87
135
|
key: string;
|
|
@@ -194,6 +242,32 @@ interface TtlCacheOptions {
|
|
|
194
242
|
/** Milliseconds an entry stays fresh. `0` (default) disables caching
|
|
195
243
|
* entirely - every `fetch` call goes to the source. */
|
|
196
244
|
ttlMs?: number;
|
|
245
|
+
/**
|
|
246
|
+
* Milliseconds a *previously successful* value may be served after a
|
|
247
|
+
* failed refresh. `0` (default) means a failure is a failure.
|
|
248
|
+
*
|
|
249
|
+
* This exists for one narrow case: use-time resolution couples every
|
|
250
|
+
* use to the vault being reachable right now, so a two-second network
|
|
251
|
+
* blip can fail a request that would otherwise have been fine. A short
|
|
252
|
+
* grace window rides that out.
|
|
253
|
+
*
|
|
254
|
+
* It is emphatically not a general fallback, and `isStaleServable`
|
|
255
|
+
* below is what keeps it honest. Serving a stale value over an expired
|
|
256
|
+
* credential hides a change the operator has to act on; serving one
|
|
257
|
+
* over a rotation means continuing to use a key that may have been
|
|
258
|
+
* rotated *because it leaked*. Keep the window short.
|
|
259
|
+
*/
|
|
260
|
+
staleGraceMs?: number;
|
|
261
|
+
/**
|
|
262
|
+
* Decides whether a given failure may be answered from the stale
|
|
263
|
+
* value. Defaults to "never". Providers pass a predicate that admits
|
|
264
|
+
* only transient faults - the cache itself stays free of any knowledge
|
|
265
|
+
* about provider error taxonomies.
|
|
266
|
+
*/
|
|
267
|
+
isStaleServable?: (err: unknown) => boolean;
|
|
268
|
+
/** Called when a stale value is served, so the layer above can warn.
|
|
269
|
+
* Never receives the value - only the key and its age. */
|
|
270
|
+
onStale?: (key: string, ageMs: number, err: unknown) => void;
|
|
197
271
|
/** Injected in tests so expiry doesn't require real waiting. */
|
|
198
272
|
now?: () => number;
|
|
199
273
|
}
|
|
@@ -206,6 +280,9 @@ declare class TtlCache<T> {
|
|
|
206
280
|
* stays correct even with caching fully disabled. */
|
|
207
281
|
private readonly inFlight;
|
|
208
282
|
private readonly ttlMs;
|
|
283
|
+
private readonly staleGraceMs;
|
|
284
|
+
private readonly isStaleServable;
|
|
285
|
+
private readonly onStale?;
|
|
209
286
|
private readonly now;
|
|
210
287
|
constructor(options?: TtlCacheOptions);
|
|
211
288
|
/**
|
|
@@ -224,6 +301,86 @@ declare class TtlCache<T> {
|
|
|
224
301
|
clear(): void;
|
|
225
302
|
}
|
|
226
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Project configuration: `secrefs.config.json`.
|
|
306
|
+
*
|
|
307
|
+
* The reference format has always supported arbitrary aliases -
|
|
308
|
+
* `ProviderRegistry` is a plain `Record<string, ISecretProvider>` - but
|
|
309
|
+
* only library callers could register them. The CLI was stuck with four
|
|
310
|
+
* hardcoded names, so `secrefs run` could reach exactly one AWS account
|
|
311
|
+
* and one Bitwarden vault. This closes that gap.
|
|
312
|
+
*
|
|
313
|
+
* **This file never holds a secret.** Every credential is referenced by
|
|
314
|
+
* the name of the environment variable that carries it, or by an AWS
|
|
315
|
+
* profile name. That is the whole design constraint: a config file that
|
|
316
|
+
* could hold a token would recreate the `.env` problem one level up, in
|
|
317
|
+
* the tool built to solve it. `secrefs.config.json` is meant to be
|
|
318
|
+
* committed, and nothing in this parser will read a literal credential
|
|
319
|
+
* even if someone puts one there.
|
|
320
|
+
*/
|
|
321
|
+
declare const CONFIG_FILENAME = "secrefs.config.json";
|
|
322
|
+
interface AwsAliasConfig {
|
|
323
|
+
type: "aws";
|
|
324
|
+
/** Named profile from ~/.aws/config. Uses the ambient chain if absent. */
|
|
325
|
+
profile?: string;
|
|
326
|
+
region?: string;
|
|
327
|
+
/** Milliseconds a fetched value may be reused. Default 0 (re-fetch). */
|
|
328
|
+
cacheTtlMs?: number;
|
|
329
|
+
/** Milliseconds a stale value may answer a *transient* failure. */
|
|
330
|
+
staleGraceMs?: number;
|
|
331
|
+
}
|
|
332
|
+
interface BitwardenAliasConfig {
|
|
333
|
+
type: "bitwarden";
|
|
334
|
+
/** Name of the env var holding the machine account token. Never the
|
|
335
|
+
* token. Defaults to BWS_ACCESS_TOKEN. */
|
|
336
|
+
tokenEnv?: string;
|
|
337
|
+
/** Name of the env var holding the organization id. */
|
|
338
|
+
organizationIdEnv?: string;
|
|
339
|
+
apiUrl?: string;
|
|
340
|
+
identityUrl?: string;
|
|
341
|
+
}
|
|
342
|
+
interface VaultAliasConfig {
|
|
343
|
+
type: "vault";
|
|
344
|
+
/** Name of the env var holding the Vault token. Defaults to VAULT_TOKEN. */
|
|
345
|
+
tokenEnv?: string;
|
|
346
|
+
addr?: string;
|
|
347
|
+
}
|
|
348
|
+
interface LocalAliasConfig {
|
|
349
|
+
type: "local";
|
|
350
|
+
/** Path to the gitignored JSON file, relative to the config file. */
|
|
351
|
+
file?: string;
|
|
352
|
+
}
|
|
353
|
+
type AliasConfig = AwsAliasConfig | BitwardenAliasConfig | VaultAliasConfig | LocalAliasConfig;
|
|
354
|
+
interface SecRefsConfig {
|
|
355
|
+
providers: Record<string, AliasConfig>;
|
|
356
|
+
}
|
|
357
|
+
declare class ConfigError extends Error {
|
|
358
|
+
constructor(message: string);
|
|
359
|
+
}
|
|
360
|
+
declare function parseConfig(raw: string, source?: string): SecRefsConfig;
|
|
361
|
+
/**
|
|
362
|
+
* Builds a provider registry from parsed config. Aliases entirely replace
|
|
363
|
+
* the built-in defaults rather than merging with them: a config that
|
|
364
|
+
* declares `aws-prod` and `aws-staging` almost certainly does *not* want
|
|
365
|
+
* a third, differently-configured `aws` quietly still working, because
|
|
366
|
+
* that is how a reference ends up resolving against the wrong account.
|
|
367
|
+
*/
|
|
368
|
+
declare function buildProviders(config: SecRefsConfig, options?: {
|
|
369
|
+
configDir?: string;
|
|
370
|
+
env?: NodeJS.ProcessEnv;
|
|
371
|
+
}): ProviderRegistry;
|
|
372
|
+
/**
|
|
373
|
+
* Loads `secrefs.config.json` from `dir`, or from the nearest ancestor
|
|
374
|
+
* that has one - so `secrefs run` works from a subdirectory of a repo the
|
|
375
|
+
* way git and every other project tool does. Returns undefined when no
|
|
376
|
+
* config exists anywhere up the tree, which is the common case and not an
|
|
377
|
+
* error: the built-in aliases are used instead.
|
|
378
|
+
*/
|
|
379
|
+
declare function loadConfigFrom(dir?: string): {
|
|
380
|
+
config: SecRefsConfig;
|
|
381
|
+
path: string;
|
|
382
|
+
} | undefined;
|
|
383
|
+
|
|
227
384
|
/**
|
|
228
385
|
* Thin HTTP client for a running control plane's credential-broker
|
|
229
386
|
* endpoint (docs/control-plane-design.md §7). This is the piece §10
|
|
@@ -304,6 +461,14 @@ declare class ControlPlaneClient {
|
|
|
304
461
|
|
|
305
462
|
interface AwsProviderOptions {
|
|
306
463
|
region?: string;
|
|
464
|
+
/**
|
|
465
|
+
* Named profile from the shared AWS config, for addressing more than
|
|
466
|
+
* one account. Note this does not make a profile self-sufficient: with
|
|
467
|
+
* SSO each profile needs its own live session, and they expire
|
|
468
|
+
* independently - which is why an auth failure names the alias that
|
|
469
|
+
* failed rather than just saying "AWS".
|
|
470
|
+
*/
|
|
471
|
+
profile?: string;
|
|
307
472
|
/** Inject a pre-configured client (primarily for testing) - also wins
|
|
308
473
|
* over `controlPlane` if both are set, since a test that supplies an
|
|
309
474
|
* explicit client wants full control regardless of the mode. */
|
|
@@ -326,6 +491,17 @@ interface AwsProviderOptions {
|
|
|
326
491
|
* ../ttlCache.ts.
|
|
327
492
|
*/
|
|
328
493
|
cacheTtlMs?: number;
|
|
494
|
+
/**
|
|
495
|
+
* Milliseconds a previously-fetched value may be served after a *failed*
|
|
496
|
+
* refresh. Defaults to 0 (off). Only ever applies to transient faults -
|
|
497
|
+
* network, timeout, throttle, 5xx. An expired credential or a denial is
|
|
498
|
+
* never answered from a stale value, because both mean something in the
|
|
499
|
+
* environment changed that a human has to see. See ../ttlCache.ts.
|
|
500
|
+
*/
|
|
501
|
+
staleGraceMs?: number;
|
|
502
|
+
/** Called when a stale value is served, so a CLI can warn. Receives the
|
|
503
|
+
* secret path and the age of the value - never the value. */
|
|
504
|
+
onStaleValue?: (path: string, ageMs: number, err: unknown) => void;
|
|
329
505
|
}
|
|
330
506
|
/**
|
|
331
507
|
* AWS Secrets Manager provider. Two credential-sourcing modes:
|
|
@@ -350,6 +526,7 @@ declare class AwsSecretsManagerProvider extends BaseSecretProvider {
|
|
|
350
526
|
readonly name = "aws";
|
|
351
527
|
private readonly explicitClient?;
|
|
352
528
|
private readonly region?;
|
|
529
|
+
private readonly profile?;
|
|
353
530
|
private readonly controlPlane?;
|
|
354
531
|
private readonly controlPlaneClient?;
|
|
355
532
|
private ambientClient;
|
|
@@ -578,4 +755,4 @@ declare class SecRefs {
|
|
|
578
755
|
/** Convenience singleton mirroring `secRefs.init()` / `secRefs.expandEnv()` / `secRefs.expandString()`. */
|
|
579
756
|
declare const secRefs: SecRefs;
|
|
580
757
|
|
|
581
|
-
export { type AwsProviderOptions, AwsSecretsManagerProvider, BaseSecretProvider, type BitwardenClientLike, BitwardenProvider, type BitwardenProviderOptions, type CheckResult, ControlPlaneClient, type ControlPlaneClientOptions, type ControlPlaneCredentialSource, ControlPlaneRequestError, type ExpandOptions, type ISecretProvider, LocalProvider, type LocalProviderOptions, type MintCredentialResponse, type MintedAwsCredentials, type MintedBitwardenCredentials, type ParsedSecretRef, type ProviderHealth, type ProviderRegistry, type ResolutionFailure, SecRefParseError, SecRefs, type SecRefsOptions, SecRefsResolutionError, SecretFetchError, type SecretFetchRequest, TtlCache, type TtlCacheOptions, VaultProvider, type VaultProviderOptions, checkReferences, createDefaultProviders, expandKeyValueMap, expandProcessEnv, extractField, isSecretRef, parseEnvFileText, parseSecretRef, recoverTruncatedSecRefs, secRefs, tryParseSecretRef };
|
|
758
|
+
export { type AliasConfig, type AwsProviderOptions, AwsSecretsManagerProvider, BaseSecretProvider, type BitwardenClientLike, BitwardenProvider, type BitwardenProviderOptions, CONFIG_FILENAME, type CheckResult, ConfigError, ControlPlaneClient, type ControlPlaneClientOptions, type ControlPlaneCredentialSource, ControlPlaneRequestError, type ExpandOptions, type ISecretProvider, LocalProvider, type LocalProviderOptions, type MintCredentialResponse, type MintedAwsCredentials, type MintedBitwardenCredentials, type ParsedSecretRef, type ProviderHealth, type ProviderRegistry, type ResolutionFailure, SecRefParseError, SecRefs, type SecRefsConfig, type SecRefsOptions, SecRefsResolutionError, SecretFetchError, type SecretFetchRequest, TtlCache, type TtlCacheOptions, VaultProvider, type VaultProviderOptions, buildProviders, checkReferences, createDefaultProviders, expandKeyValueMap, expandProcessEnv, extractField, isSecretRef, loadConfigFrom, parseConfig, parseEnvFileText, parseSecretRef, recoverTruncatedSecRefs, secRefs, tryParseSecretRef };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
|
|
2
2
|
import vaultFactory from 'node-vault';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Why a fetch failed, and - more usefully - who has to do something
|
|
6
|
+
* about it.
|
|
7
|
+
*
|
|
8
|
+
* The distinction that matters is between a problem with *the reference*
|
|
9
|
+
* and a problem with *the environment*. They currently look identical to
|
|
10
|
+
* a caller, which produces the worst message SecRefs emits today: an
|
|
11
|
+
* expired `aws sso login` reported once per reference as
|
|
12
|
+
* `could not fetch secret "prod/db"`, blaming four healthy secrets for
|
|
13
|
+
* one dead credential.
|
|
14
|
+
*/
|
|
15
|
+
type SecretErrorKind =
|
|
16
|
+
/** Credentials are missing, expired, or unusable. Nothing about the
|
|
17
|
+
* reference is wrong; a human has to re-authenticate. Report once for
|
|
18
|
+
* the whole provider, never per reference. */
|
|
19
|
+
"auth"
|
|
20
|
+
/** Credentials worked and the backend says this path does not exist.
|
|
21
|
+
* Specific to the reference. */
|
|
22
|
+
| "not_found"
|
|
23
|
+
/** Credentials worked and the backend refused *this* path. Also
|
|
24
|
+
* specific to the reference - sending someone to re-login when the
|
|
25
|
+
* real problem is an IAM policy wastes their afternoon. */
|
|
26
|
+
| "denied"
|
|
27
|
+
/** Network, timeout, throttle, or 5xx. Nobody is at fault and the same
|
|
28
|
+
* call may well succeed a second later. The only kind for which
|
|
29
|
+
* serving a stale value is defensible. */
|
|
30
|
+
| "transient"
|
|
31
|
+
/** Unclassified. Treated as permanent, because guessing "transient"
|
|
32
|
+
* would mean retrying something that will never succeed. */
|
|
33
|
+
| "unknown";
|
|
34
|
+
|
|
4
35
|
/**
|
|
5
36
|
* The provider contract every SecRefs backend (AWS, Vault, local, or a
|
|
6
37
|
* custom one you bring yourself) implements. Providers never log, print,
|
|
@@ -41,6 +72,13 @@ interface ISecretProvider {
|
|
|
41
72
|
declare class SecretFetchError extends Error {
|
|
42
73
|
readonly provider: string;
|
|
43
74
|
readonly path: string;
|
|
75
|
+
readonly cause: unknown;
|
|
76
|
+
/** Whose problem this is - see {@link SecretErrorKind}. Classified from
|
|
77
|
+
* `cause` so every existing throw site is categorised without having to
|
|
78
|
+
* know about categories. */
|
|
79
|
+
readonly kind: SecretErrorKind;
|
|
80
|
+
/** The action that fixes it, when there is one (auth failures). */
|
|
81
|
+
readonly remedy?: string;
|
|
44
82
|
constructor(provider: string, path: string, cause: unknown);
|
|
45
83
|
}
|
|
46
84
|
declare abstract class BaseSecretProvider implements ISecretProvider {
|
|
@@ -78,10 +116,20 @@ interface ResolutionFailure {
|
|
|
78
116
|
/** The original `sec://` string. */
|
|
79
117
|
ref: string;
|
|
80
118
|
message: string;
|
|
119
|
+
/** Whose problem this is. `undefined` when the failure came from
|
|
120
|
+
* somewhere that doesn't classify (a malformed reference, say). */
|
|
121
|
+
kind?: SecretErrorKind;
|
|
122
|
+
/** Provider alias the reference named, for grouping auth failures. */
|
|
123
|
+
provider?: string;
|
|
124
|
+
/** What to run to fix it, for auth failures. */
|
|
125
|
+
remedy?: string;
|
|
81
126
|
}
|
|
82
127
|
declare class SecRefsResolutionError extends Error {
|
|
83
128
|
readonly errors: ResolutionFailure[];
|
|
84
129
|
constructor(errors: ResolutionFailure[]);
|
|
130
|
+
/** True when every failure was an environment/auth problem, so a caller
|
|
131
|
+
* can tell "your credentials lapsed" from "your references are wrong". */
|
|
132
|
+
get isAuthOnly(): boolean;
|
|
85
133
|
}
|
|
86
134
|
interface CheckResult {
|
|
87
135
|
key: string;
|
|
@@ -194,6 +242,32 @@ interface TtlCacheOptions {
|
|
|
194
242
|
/** Milliseconds an entry stays fresh. `0` (default) disables caching
|
|
195
243
|
* entirely - every `fetch` call goes to the source. */
|
|
196
244
|
ttlMs?: number;
|
|
245
|
+
/**
|
|
246
|
+
* Milliseconds a *previously successful* value may be served after a
|
|
247
|
+
* failed refresh. `0` (default) means a failure is a failure.
|
|
248
|
+
*
|
|
249
|
+
* This exists for one narrow case: use-time resolution couples every
|
|
250
|
+
* use to the vault being reachable right now, so a two-second network
|
|
251
|
+
* blip can fail a request that would otherwise have been fine. A short
|
|
252
|
+
* grace window rides that out.
|
|
253
|
+
*
|
|
254
|
+
* It is emphatically not a general fallback, and `isStaleServable`
|
|
255
|
+
* below is what keeps it honest. Serving a stale value over an expired
|
|
256
|
+
* credential hides a change the operator has to act on; serving one
|
|
257
|
+
* over a rotation means continuing to use a key that may have been
|
|
258
|
+
* rotated *because it leaked*. Keep the window short.
|
|
259
|
+
*/
|
|
260
|
+
staleGraceMs?: number;
|
|
261
|
+
/**
|
|
262
|
+
* Decides whether a given failure may be answered from the stale
|
|
263
|
+
* value. Defaults to "never". Providers pass a predicate that admits
|
|
264
|
+
* only transient faults - the cache itself stays free of any knowledge
|
|
265
|
+
* about provider error taxonomies.
|
|
266
|
+
*/
|
|
267
|
+
isStaleServable?: (err: unknown) => boolean;
|
|
268
|
+
/** Called when a stale value is served, so the layer above can warn.
|
|
269
|
+
* Never receives the value - only the key and its age. */
|
|
270
|
+
onStale?: (key: string, ageMs: number, err: unknown) => void;
|
|
197
271
|
/** Injected in tests so expiry doesn't require real waiting. */
|
|
198
272
|
now?: () => number;
|
|
199
273
|
}
|
|
@@ -206,6 +280,9 @@ declare class TtlCache<T> {
|
|
|
206
280
|
* stays correct even with caching fully disabled. */
|
|
207
281
|
private readonly inFlight;
|
|
208
282
|
private readonly ttlMs;
|
|
283
|
+
private readonly staleGraceMs;
|
|
284
|
+
private readonly isStaleServable;
|
|
285
|
+
private readonly onStale?;
|
|
209
286
|
private readonly now;
|
|
210
287
|
constructor(options?: TtlCacheOptions);
|
|
211
288
|
/**
|
|
@@ -224,6 +301,86 @@ declare class TtlCache<T> {
|
|
|
224
301
|
clear(): void;
|
|
225
302
|
}
|
|
226
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Project configuration: `secrefs.config.json`.
|
|
306
|
+
*
|
|
307
|
+
* The reference format has always supported arbitrary aliases -
|
|
308
|
+
* `ProviderRegistry` is a plain `Record<string, ISecretProvider>` - but
|
|
309
|
+
* only library callers could register them. The CLI was stuck with four
|
|
310
|
+
* hardcoded names, so `secrefs run` could reach exactly one AWS account
|
|
311
|
+
* and one Bitwarden vault. This closes that gap.
|
|
312
|
+
*
|
|
313
|
+
* **This file never holds a secret.** Every credential is referenced by
|
|
314
|
+
* the name of the environment variable that carries it, or by an AWS
|
|
315
|
+
* profile name. That is the whole design constraint: a config file that
|
|
316
|
+
* could hold a token would recreate the `.env` problem one level up, in
|
|
317
|
+
* the tool built to solve it. `secrefs.config.json` is meant to be
|
|
318
|
+
* committed, and nothing in this parser will read a literal credential
|
|
319
|
+
* even if someone puts one there.
|
|
320
|
+
*/
|
|
321
|
+
declare const CONFIG_FILENAME = "secrefs.config.json";
|
|
322
|
+
interface AwsAliasConfig {
|
|
323
|
+
type: "aws";
|
|
324
|
+
/** Named profile from ~/.aws/config. Uses the ambient chain if absent. */
|
|
325
|
+
profile?: string;
|
|
326
|
+
region?: string;
|
|
327
|
+
/** Milliseconds a fetched value may be reused. Default 0 (re-fetch). */
|
|
328
|
+
cacheTtlMs?: number;
|
|
329
|
+
/** Milliseconds a stale value may answer a *transient* failure. */
|
|
330
|
+
staleGraceMs?: number;
|
|
331
|
+
}
|
|
332
|
+
interface BitwardenAliasConfig {
|
|
333
|
+
type: "bitwarden";
|
|
334
|
+
/** Name of the env var holding the machine account token. Never the
|
|
335
|
+
* token. Defaults to BWS_ACCESS_TOKEN. */
|
|
336
|
+
tokenEnv?: string;
|
|
337
|
+
/** Name of the env var holding the organization id. */
|
|
338
|
+
organizationIdEnv?: string;
|
|
339
|
+
apiUrl?: string;
|
|
340
|
+
identityUrl?: string;
|
|
341
|
+
}
|
|
342
|
+
interface VaultAliasConfig {
|
|
343
|
+
type: "vault";
|
|
344
|
+
/** Name of the env var holding the Vault token. Defaults to VAULT_TOKEN. */
|
|
345
|
+
tokenEnv?: string;
|
|
346
|
+
addr?: string;
|
|
347
|
+
}
|
|
348
|
+
interface LocalAliasConfig {
|
|
349
|
+
type: "local";
|
|
350
|
+
/** Path to the gitignored JSON file, relative to the config file. */
|
|
351
|
+
file?: string;
|
|
352
|
+
}
|
|
353
|
+
type AliasConfig = AwsAliasConfig | BitwardenAliasConfig | VaultAliasConfig | LocalAliasConfig;
|
|
354
|
+
interface SecRefsConfig {
|
|
355
|
+
providers: Record<string, AliasConfig>;
|
|
356
|
+
}
|
|
357
|
+
declare class ConfigError extends Error {
|
|
358
|
+
constructor(message: string);
|
|
359
|
+
}
|
|
360
|
+
declare function parseConfig(raw: string, source?: string): SecRefsConfig;
|
|
361
|
+
/**
|
|
362
|
+
* Builds a provider registry from parsed config. Aliases entirely replace
|
|
363
|
+
* the built-in defaults rather than merging with them: a config that
|
|
364
|
+
* declares `aws-prod` and `aws-staging` almost certainly does *not* want
|
|
365
|
+
* a third, differently-configured `aws` quietly still working, because
|
|
366
|
+
* that is how a reference ends up resolving against the wrong account.
|
|
367
|
+
*/
|
|
368
|
+
declare function buildProviders(config: SecRefsConfig, options?: {
|
|
369
|
+
configDir?: string;
|
|
370
|
+
env?: NodeJS.ProcessEnv;
|
|
371
|
+
}): ProviderRegistry;
|
|
372
|
+
/**
|
|
373
|
+
* Loads `secrefs.config.json` from `dir`, or from the nearest ancestor
|
|
374
|
+
* that has one - so `secrefs run` works from a subdirectory of a repo the
|
|
375
|
+
* way git and every other project tool does. Returns undefined when no
|
|
376
|
+
* config exists anywhere up the tree, which is the common case and not an
|
|
377
|
+
* error: the built-in aliases are used instead.
|
|
378
|
+
*/
|
|
379
|
+
declare function loadConfigFrom(dir?: string): {
|
|
380
|
+
config: SecRefsConfig;
|
|
381
|
+
path: string;
|
|
382
|
+
} | undefined;
|
|
383
|
+
|
|
227
384
|
/**
|
|
228
385
|
* Thin HTTP client for a running control plane's credential-broker
|
|
229
386
|
* endpoint (docs/control-plane-design.md §7). This is the piece §10
|
|
@@ -304,6 +461,14 @@ declare class ControlPlaneClient {
|
|
|
304
461
|
|
|
305
462
|
interface AwsProviderOptions {
|
|
306
463
|
region?: string;
|
|
464
|
+
/**
|
|
465
|
+
* Named profile from the shared AWS config, for addressing more than
|
|
466
|
+
* one account. Note this does not make a profile self-sufficient: with
|
|
467
|
+
* SSO each profile needs its own live session, and they expire
|
|
468
|
+
* independently - which is why an auth failure names the alias that
|
|
469
|
+
* failed rather than just saying "AWS".
|
|
470
|
+
*/
|
|
471
|
+
profile?: string;
|
|
307
472
|
/** Inject a pre-configured client (primarily for testing) - also wins
|
|
308
473
|
* over `controlPlane` if both are set, since a test that supplies an
|
|
309
474
|
* explicit client wants full control regardless of the mode. */
|
|
@@ -326,6 +491,17 @@ interface AwsProviderOptions {
|
|
|
326
491
|
* ../ttlCache.ts.
|
|
327
492
|
*/
|
|
328
493
|
cacheTtlMs?: number;
|
|
494
|
+
/**
|
|
495
|
+
* Milliseconds a previously-fetched value may be served after a *failed*
|
|
496
|
+
* refresh. Defaults to 0 (off). Only ever applies to transient faults -
|
|
497
|
+
* network, timeout, throttle, 5xx. An expired credential or a denial is
|
|
498
|
+
* never answered from a stale value, because both mean something in the
|
|
499
|
+
* environment changed that a human has to see. See ../ttlCache.ts.
|
|
500
|
+
*/
|
|
501
|
+
staleGraceMs?: number;
|
|
502
|
+
/** Called when a stale value is served, so a CLI can warn. Receives the
|
|
503
|
+
* secret path and the age of the value - never the value. */
|
|
504
|
+
onStaleValue?: (path: string, ageMs: number, err: unknown) => void;
|
|
329
505
|
}
|
|
330
506
|
/**
|
|
331
507
|
* AWS Secrets Manager provider. Two credential-sourcing modes:
|
|
@@ -350,6 +526,7 @@ declare class AwsSecretsManagerProvider extends BaseSecretProvider {
|
|
|
350
526
|
readonly name = "aws";
|
|
351
527
|
private readonly explicitClient?;
|
|
352
528
|
private readonly region?;
|
|
529
|
+
private readonly profile?;
|
|
353
530
|
private readonly controlPlane?;
|
|
354
531
|
private readonly controlPlaneClient?;
|
|
355
532
|
private ambientClient;
|
|
@@ -578,4 +755,4 @@ declare class SecRefs {
|
|
|
578
755
|
/** Convenience singleton mirroring `secRefs.init()` / `secRefs.expandEnv()` / `secRefs.expandString()`. */
|
|
579
756
|
declare const secRefs: SecRefs;
|
|
580
757
|
|
|
581
|
-
export { type AwsProviderOptions, AwsSecretsManagerProvider, BaseSecretProvider, type BitwardenClientLike, BitwardenProvider, type BitwardenProviderOptions, type CheckResult, ControlPlaneClient, type ControlPlaneClientOptions, type ControlPlaneCredentialSource, ControlPlaneRequestError, type ExpandOptions, type ISecretProvider, LocalProvider, type LocalProviderOptions, type MintCredentialResponse, type MintedAwsCredentials, type MintedBitwardenCredentials, type ParsedSecretRef, type ProviderHealth, type ProviderRegistry, type ResolutionFailure, SecRefParseError, SecRefs, type SecRefsOptions, SecRefsResolutionError, SecretFetchError, type SecretFetchRequest, TtlCache, type TtlCacheOptions, VaultProvider, type VaultProviderOptions, checkReferences, createDefaultProviders, expandKeyValueMap, expandProcessEnv, extractField, isSecretRef, parseEnvFileText, parseSecretRef, recoverTruncatedSecRefs, secRefs, tryParseSecretRef };
|
|
758
|
+
export { type AliasConfig, type AwsProviderOptions, AwsSecretsManagerProvider, BaseSecretProvider, type BitwardenClientLike, BitwardenProvider, type BitwardenProviderOptions, CONFIG_FILENAME, type CheckResult, ConfigError, ControlPlaneClient, type ControlPlaneClientOptions, type ControlPlaneCredentialSource, ControlPlaneRequestError, type ExpandOptions, type ISecretProvider, LocalProvider, type LocalProviderOptions, type MintCredentialResponse, type MintedAwsCredentials, type MintedBitwardenCredentials, type ParsedSecretRef, type ProviderHealth, type ProviderRegistry, type ResolutionFailure, SecRefParseError, SecRefs, type SecRefsConfig, type SecRefsOptions, SecRefsResolutionError, SecretFetchError, type SecretFetchRequest, TtlCache, type TtlCacheOptions, VaultProvider, type VaultProviderOptions, buildProviders, checkReferences, createDefaultProviders, expandKeyValueMap, expandProcessEnv, extractField, isSecretRef, loadConfigFrom, parseConfig, parseEnvFileText, parseSecretRef, recoverTruncatedSecRefs, secRefs, tryParseSecretRef };
|