@voxgig/sdkgen 4.6.0 → 4.7.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/bin/voxgig-sdkgen +1 -1
- package/dist/cmp/Feature.js +5 -0
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/cmp/TestControl.d.ts +4 -0
- package/dist/cmp/TestControl.js +51 -0
- package/dist/cmp/TestControl.js.map +1 -0
- package/dist/helpers/featureSource.d.ts +3 -1
- package/dist/helpers/featureSource.js +105 -0
- package/dist/helpers/featureSource.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -2
- package/dist/sdkgen.js +9 -4
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +2 -1
- package/dist/utility.js +52 -0
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +58 -0
- package/package.json +1 -1
- package/project/.sdk/model/feature/secrets.aon +82 -0
- package/project/.sdk/src/cmp/cpp/Main_cpp.ts +2 -1
- package/project/.sdk/src/cmp/cpp/Test_cpp.ts +5 -1
- package/project/.sdk/src/cmp/csharp/Main_csharp.ts +2 -1
- package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +30 -6
- package/project/.sdk/src/cmp/csharp/TestEntity_csharp.ts +29 -5
- package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -1
- package/project/.sdk/src/cmp/dart/Main_dart.ts +2 -1
- package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +23 -6
- package/project/.sdk/src/cmp/dart/TestEntity_dart.ts +28 -7
- package/project/.sdk/src/cmp/dart/Test_dart.ts +5 -1
- package/project/.sdk/src/cmp/go/Main_go.ts +2 -1
- package/project/.sdk/src/cmp/go/Test_go.ts +5 -1
- package/project/.sdk/src/cmp/java/Main_java.ts +2 -1
- package/project/.sdk/src/cmp/java/Test_java.ts +5 -1
- package/project/.sdk/src/cmp/js/Main_js.ts +2 -1
- package/project/.sdk/src/cmp/js/TestDirect_js.ts +30 -5
- package/project/.sdk/src/cmp/js/TestEntity_js.ts +35 -5
- package/project/.sdk/src/cmp/js/Test_js.ts +5 -1
- package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +7 -1
- package/project/.sdk/src/cmp/js/fragment/Entity.test.fragment.js +7 -1
- package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +2 -1
- package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +5 -1
- package/project/.sdk/src/cmp/lua/Main_lua.ts +2 -1
- package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +28 -4
- package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +24 -4
- package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -1
- package/project/.sdk/src/cmp/ocaml/Gitignore_ocaml.ts +1 -0
- package/project/.sdk/src/cmp/perl/Main_perl.ts +2 -1
- package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +25 -4
- package/project/.sdk/src/cmp/perl/TestEntity_perl.ts +24 -4
- package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -1
- package/project/.sdk/src/cmp/perl/utility_perl.ts +10 -0
- package/project/.sdk/src/cmp/php/Main_php.ts +2 -1
- package/project/.sdk/src/cmp/php/TestDirect_php.ts +24 -5
- package/project/.sdk/src/cmp/php/TestEntity_php.ts +28 -4
- package/project/.sdk/src/cmp/php/Test_php.ts +5 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +2 -1
- package/project/.sdk/src/cmp/py/Test_py.ts +5 -1
- package/project/.sdk/src/cmp/rb/Main_rb.ts +2 -1
- package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +24 -5
- package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +24 -4
- package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -1
- package/project/.sdk/src/cmp/rb/utility_rb.ts +3 -1
- package/project/.sdk/src/cmp/rust/Main_rust.ts +2 -1
- package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +31 -4
- package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +37 -4
- package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -1
- package/project/.sdk/src/cmp/scala/Main_scala.ts +2 -1
- package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -1
- package/project/.sdk/src/cmp/ts/Config_ts.ts +50 -8
- package/project/.sdk/src/cmp/ts/Main_ts.ts +10 -2
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +10 -3
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +11 -3
- package/project/.sdk/src/cmp/ts/Test_ts.ts +5 -1
- package/project/.sdk/tm/csharp/test/Runner.cs +45 -0
- package/project/.sdk/tm/dart/test/utility.dart +39 -0
- package/project/.sdk/tm/js/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/js/test/utility.js +91 -1
- package/project/.sdk/tm/lua/test/runner.lua +40 -0
- package/project/.sdk/tm/ocaml/test/harness.ml +14 -4
- package/project/.sdk/tm/perl/t/runner.pm +30 -0
- package/project/.sdk/tm/php/test/Runner.php +36 -0
- package/project/.sdk/tm/rb/test/runner.rb +25 -0
- package/project/.sdk/tm/rust/tests/common/mod.rs +32 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +1 -1
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +8 -2
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +29 -5
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +30 -21
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/Registry.ts +90 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/addr.ts +48 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/aws.ts +219 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/azuresecrets.ts +144 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/boru.ts +113 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/doppler.ts +77 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/dotenv.ts +51 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/env.ts +31 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/file.ts +54 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/gcpsecrets.ts +118 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/hashicorp.ts +159 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/http.ts +66 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/infisical.ts +107 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/memory.ts +33 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/onepassword.ts +120 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/support.ts +176 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +15 -13
- package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +25 -19
- package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +1 -1
- package/project/.sdk/tm/ts/test/vendor/omni/index.ts +1 -1
- package/project/.sdk/tm/zig/core/utility.zig +1 -1
- package/project/sdkgen-package.json +1 -1
- package/src/cmp/Feature.ts +5 -0
- package/src/cmp/TestControl.ts +64 -0
- package/src/helpers/featureSource.ts +130 -0
- package/src/sdkgen.ts +7 -0
- package/src/utility.ts +61 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +0 -1221
|
@@ -93,6 +93,38 @@ pub fn is_control_skipped(kind: &str, name: &str, mode: &str) -> (bool, String)
|
|
|
93
93
|
(false, String::new())
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
/// Extra SDK options every LIVE client is constructed with, read from
|
|
97
|
+
/// sdk-test-control.json `test.client.options`.
|
|
98
|
+
///
|
|
99
|
+
/// The generated live client knows two things: the base URL (from the spec)
|
|
100
|
+
/// and the credential (from the environment). Everything else about how a
|
|
101
|
+
/// particular API wants to be talked to - which features to switch on, and
|
|
102
|
+
/// with what settings - is a property of THAT API, known to the project and
|
|
103
|
+
/// to nothing in the toolchain.
|
|
104
|
+
///
|
|
105
|
+
/// Merged UNDER the generated fields, so the suite's own base/apikey/server
|
|
106
|
+
/// values win: this ADDS to the live client, it does not redirect it.
|
|
107
|
+
///
|
|
108
|
+
/// Reserved fields are stripped HERE rather than at each merge site: the
|
|
109
|
+
/// generated map only names a field when the model calls for one, so a
|
|
110
|
+
/// "base" in this block would face no competing value and would silently
|
|
111
|
+
/// redirect the whole suite - credential included - to another host.
|
|
112
|
+
pub const LIVE_RESERVED: [&str; 6] =
|
|
113
|
+
["base", "prefix", "suffix", "server", "apikey", "secret"];
|
|
114
|
+
|
|
115
|
+
pub fn live_client_options() -> Value {
|
|
116
|
+
let ctrl = load_test_control();
|
|
117
|
+
let out = Value::empty_map();
|
|
118
|
+
if let Value::Map(m) = get_spec(&ctrl, &["test", "client", "options"]) {
|
|
119
|
+
for (k, v) in m.borrow().iter() {
|
|
120
|
+
if !LIVE_RESERVED.contains(&k.as_str()) {
|
|
121
|
+
setp(&out, k, v.clone());
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
out
|
|
126
|
+
}
|
|
127
|
+
|
|
96
128
|
/// Configured per-test live delay in ms; default 500.
|
|
97
129
|
pub fn live_delay_ms() -> i64 {
|
|
98
130
|
let ctrl = load_test_control();
|
|
@@ -78,7 +78,7 @@ pub fn make_options_util(ctx: &Rc<Context>) -> Value {
|
|
|
78
78
|
("base", Value::str("http://localhost:8000")),
|
|
79
79
|
("prefix", Value::str("")),
|
|
80
80
|
("suffix", Value::str("")),
|
|
81
|
-
("auth", jo(vec![("prefix", Value::str(""))])),
|
|
81
|
+
("auth", jo(vec![("prefix", Value::str("")), ("basic", Value::Bool(false))])),
|
|
82
82
|
("headers", jo(vec![("`$CHILD`", Value::str("`$STRING`"))])),
|
|
83
83
|
(
|
|
84
84
|
"allow",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/Sekreto.ts)
|
|
2
|
-
// Source: https://github.com/voxgig/sekreto @
|
|
2
|
+
// Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
|
|
3
3
|
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
4
|
// sekreto: one interface for secrets, wherever they live.
|
|
5
5
|
//
|
|
@@ -11,7 +11,13 @@
|
|
|
11
11
|
// This file is CANONICAL. Every other port is a translation of it, and
|
|
12
12
|
// spec/sekreto.json is the behavioural contract they all run.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// The REGISTRY, never './Providers'. Importing the barrel here is what
|
|
15
|
+
// made every provider kind reachable from the core: the chain needs
|
|
16
|
+
// `makeprovider`, and the barrel's edge dragged AWS request signing and
|
|
17
|
+
// seven HTTP clients in behind it. The registry knows only the kinds
|
|
18
|
+
// something actually imported. See docs/design/plugin-providers.md.
|
|
19
|
+
import { Provider, ProviderSpec } from './provider/support'
|
|
20
|
+
import { makeprovider } from './provider/Registry'
|
|
15
21
|
|
|
16
22
|
/** A secret name: dot-separated lowercase segments, e.g. `api.token`. */
|
|
17
23
|
export type Name = string
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/Sigv4.ts)
|
|
2
|
-
// Source: https://github.com/voxgig/sekreto @
|
|
2
|
+
// Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
|
|
3
3
|
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
4
|
// AWS Signature Version 4, hand-rolled.
|
|
5
5
|
//
|
|
@@ -14,7 +14,28 @@
|
|
|
14
14
|
// carry known-answer cases that all ten ports must reproduce bit-for-bit,
|
|
15
15
|
// and lets the integration mock recompute the signature server-side.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// node:crypto is loaded ON FIRST USE, not at import time.
|
|
18
|
+
//
|
|
19
|
+
// Providers.ts imports sigv4 for the AWS providers, and Sekreto.ts imports
|
|
20
|
+
// Providers — so a top-level `node:crypto` here put it in the module graph
|
|
21
|
+
// of every sekreto consumer, including one that never signs anything.
|
|
22
|
+
// Deferring keeps this module pure until an AWS provider actually runs.
|
|
23
|
+
//
|
|
24
|
+
// require(), not `await import()`: these helpers are synchronous and are
|
|
25
|
+
// called from synchronous signing code. The package is CommonJS.
|
|
26
|
+
function crypto(): typeof import('node:crypto') {
|
|
27
|
+
try {
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
|
+
return require('node:crypto')
|
|
30
|
+
} catch (err: any) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
'sekreto: AWS request signing needs node:crypto, which this runtime ' +
|
|
33
|
+
'does not provide: ' +
|
|
34
|
+
err.message,
|
|
35
|
+
{ cause: err },
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
18
39
|
|
|
19
40
|
export type Sigv4Input = {
|
|
20
41
|
method: string
|
|
@@ -39,11 +60,11 @@ export type Sigv4Input = {
|
|
|
39
60
|
export type Sigv4Output = Record<string, string>
|
|
40
61
|
|
|
41
62
|
function sha256hex(text: string): string {
|
|
42
|
-
return createHash('sha256').update(text, 'utf8').digest('hex')
|
|
63
|
+
return crypto().createHash('sha256').update(text, 'utf8').digest('hex')
|
|
43
64
|
}
|
|
44
65
|
|
|
45
66
|
function hmac(key: Buffer | string, text: string): Buffer {
|
|
46
|
-
return createHmac('sha256', key).update(text, 'utf8').digest()
|
|
67
|
+
return crypto().createHmac('sha256', key).update(text, 'utf8').digest()
|
|
47
68
|
}
|
|
48
69
|
|
|
49
70
|
/** RFC 3986 escaping, which is stricter than encodeURIComponent: AWS
|
|
@@ -128,7 +149,10 @@ export function sigv4(input: Sigv4Input): Sigv4Output {
|
|
|
128
149
|
const kregion = hmac(kdate, input.region)
|
|
129
150
|
const kservice = hmac(kregion, input.service)
|
|
130
151
|
const ksigning = hmac(kservice, 'aws4_request')
|
|
131
|
-
const signature =
|
|
152
|
+
const signature = crypto()
|
|
153
|
+
.createHmac('sha256', ksigning)
|
|
154
|
+
.update(stringtosign, 'utf8')
|
|
155
|
+
.digest('hex')
|
|
132
156
|
|
|
133
157
|
const out: Sigv4Output = {
|
|
134
158
|
authorization:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/index.ts)
|
|
2
|
-
// Source: https://github.com/voxgig/sekreto @
|
|
2
|
+
// Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
|
|
3
3
|
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
4
|
// @voxgig/sekreto - one interface for secrets, wherever they live.
|
|
5
5
|
|
|
@@ -18,25 +18,34 @@ export {
|
|
|
18
18
|
|
|
19
19
|
export type { Name, SekretoOptions } from './Sekreto'
|
|
20
20
|
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
21
|
+
// THE CORE SURFACE. Deliberately does NOT re-export the eleven provider
|
|
22
|
+
// kinds that need something of their runtime: pulling one through this
|
|
23
|
+
// file would make all of them reachable and put AWS request signing in
|
|
24
|
+
// every build, which is the thing the split removes.
|
|
25
|
+
//
|
|
26
|
+
// `env` and `memory` are here because they import nothing at all, and a
|
|
27
|
+
// chain with nowhere to read from is not usable or testable.
|
|
28
|
+
//
|
|
29
|
+
// Everything else registers itself when its module is imported:
|
|
30
|
+
//
|
|
31
|
+
// import '@voxgig/sekreto/provider/dotenv'
|
|
32
|
+
//
|
|
33
|
+
// or, for the old all-in behaviour, `from '@voxgig/sekreto/Providers'`.
|
|
34
|
+
// See docs/design/plugin-providers.md.
|
|
35
|
+
export { envprovider } from './provider/env'
|
|
36
|
+
export { memoryprovider } from './provider/memory'
|
|
37
|
+
|
|
38
|
+
// A pure validator, no platform dependency - kept on the core surface
|
|
39
|
+
// because callers validate an address before configuring a provider.
|
|
40
|
+
export { checkaddr } from './provider/addr'
|
|
41
|
+
|
|
42
|
+
export { makeprovider, register, registered, kinds } from './provider/Registry'
|
|
43
|
+
export type { ProviderDefinition } from './provider/Registry'
|
|
38
44
|
|
|
39
|
-
export type { Provider, ProviderSpec } from './
|
|
45
|
+
export type { Provider, ProviderSpec } from './provider/support'
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
// `sigv4` is NOT on the core surface: it is the node:crypto edge, and
|
|
48
|
+
// only the two aws providers use it. Import it from the module that
|
|
49
|
+
// needs it - `@voxgig/sekreto/provider/aws` - or from the full-set
|
|
50
|
+
// barrel. Re-exporting it here would put request signing in every
|
|
51
|
+
// build again, which is the thing the split removes.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/provider/Registry.ts)
|
|
2
|
+
// Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
|
|
3
|
+
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
|
+
/* Copyright (c) 2025 Voxgig Ltd, MIT License */
|
|
5
|
+
|
|
6
|
+
// THE PROVIDER REGISTRY — the seam that replaced the `makeprovider`
|
|
7
|
+
// switch, and the reason an SDK no longer carries thirteen providers to
|
|
8
|
+
// use two.
|
|
9
|
+
//
|
|
10
|
+
// The switch named every kind in one function, so every kind was
|
|
11
|
+
// statically reachable from `Sekreto` and no bundler could drop one. Its
|
|
12
|
+
// replacement is a map that a provider ADDS ITSELF to. A kind that is
|
|
13
|
+
// never imported is never registered, is never reachable, and is not in
|
|
14
|
+
// the build — which is the whole point.
|
|
15
|
+
//
|
|
16
|
+
// The vocabulary is voxgig/plugin's (docs/design/plugin-providers.md): a
|
|
17
|
+
// provider kind is a plugin `Definition`, a configured provider is an
|
|
18
|
+
// instance addressed by name+tag. What sekreto does NOT route through
|
|
19
|
+
// plugin is the lookup itself: `Provider.lookup` is async, and plugin's
|
|
20
|
+
// dispatch is synchronous by design — twenty ports, many with no
|
|
21
|
+
// async/await. So plugin owns the catalog, the instances and their
|
|
22
|
+
// order; sekreto walks them in each language's own idiom.
|
|
23
|
+
//
|
|
24
|
+
// `define` is the definition's factory: spec in, Provider out. It is
|
|
25
|
+
// separate from the plugin `Definition.define` callback on purpose —
|
|
26
|
+
// this one runs at construction and returns a value, while plugin's runs
|
|
27
|
+
// at load and binds. They are bridged in Catalog.ts.
|
|
28
|
+
|
|
29
|
+
import { Provider, ProviderSpec, SekretoError } from './support'
|
|
30
|
+
|
|
31
|
+
export type ProviderDefinition = {
|
|
32
|
+
/** The `kind` a ProviderSpec names. */
|
|
33
|
+
name: string
|
|
34
|
+
/** What this provider needs of its runtime, so a host can refuse or
|
|
35
|
+
* report before a lookup fails: 'fs', 'fetch', 'crypto', 'process'. */
|
|
36
|
+
needs?: string[]
|
|
37
|
+
/** Build the provider from its declarative spec. */
|
|
38
|
+
define: (spec: ProviderSpec) => Provider
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const registry: { [kind: string]: ProviderDefinition } = {}
|
|
42
|
+
|
|
43
|
+
/** Register a provider kind. Called by each provider module at import,
|
|
44
|
+
* so importing the module IS installing it. Re-registering the same name
|
|
45
|
+
* replaces it, which is how a host substitutes an implementation. */
|
|
46
|
+
export function register(def: ProviderDefinition): void {
|
|
47
|
+
if (null == def || 'string' !== typeof def.name || '' === def.name) {
|
|
48
|
+
throw new SekretoError('sekreto: invalid provider definition')
|
|
49
|
+
}
|
|
50
|
+
registry[def.name] = def
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function registered(kind: string): ProviderDefinition | undefined {
|
|
54
|
+
return registry[kind]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function kinds(): string[] {
|
|
58
|
+
return Object.keys(registry).sort()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Build a provider from its spec.
|
|
62
|
+
*
|
|
63
|
+
* An UNREGISTERED kind is not the same failure as an unknown one, and the
|
|
64
|
+
* message says which: a kind sekreto has never heard of is a typo, while
|
|
65
|
+
* a known kind that was not imported is the leanness mechanism working as
|
|
66
|
+
* designed and telling you to import it. Collapsing the two was the first
|
|
67
|
+
* thing that made the split confusing to use. */
|
|
68
|
+
export function makeprovider(spec: ProviderSpec): Provider {
|
|
69
|
+
const kind = null == spec ? undefined : (spec as any).kind
|
|
70
|
+
const def = registry[kind as string]
|
|
71
|
+
|
|
72
|
+
if (null == def) {
|
|
73
|
+
throw new SekretoError(
|
|
74
|
+
'sekreto: unknown provider kind: ' + String(kind) +
|
|
75
|
+
' (registered: ' + (kinds().join(', ') || 'none') + ')' +
|
|
76
|
+
(KNOWN.indexOf(String(kind)) < 0 ? '' :
|
|
77
|
+
" - that kind exists but its module was not imported; import" +
|
|
78
|
+
" '@voxgig/sekreto/provider/" + String(kind) + "' to register it"))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return def.define(spec)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Every kind this library ships, registered or not. Used only to tell a
|
|
85
|
+
* typo from a module that was not imported. */
|
|
86
|
+
const KNOWN = [
|
|
87
|
+
'env', 'memory', 'dotenv', 'file', 'hashicorp', 'boru',
|
|
88
|
+
'awssecrets', 'awsparams', 'gcpsecrets', 'azuresecrets',
|
|
89
|
+
'onepassword', 'doppler', 'infisical',
|
|
90
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/provider/addr.ts)
|
|
2
|
+
// Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
|
|
3
|
+
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
|
+
/* Copyright (c) 2025 Voxgig Ltd, MIT License */
|
|
5
|
+
|
|
6
|
+
import { SekretoError } from './support'
|
|
7
|
+
|
|
8
|
+
export function checkaddr(addr: string): void {
|
|
9
|
+
if (addr.startsWith('https://')) {
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!addr.startsWith('http://')) {
|
|
14
|
+
throw new SekretoError('sekreto: not an http(s) address: ' + addr)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Parsed, not split on ':'. Splitting the authority on the first colon
|
|
18
|
+
// yields '[' for a bracketed IPv6 address, so `http://[::1]:8200` could
|
|
19
|
+
// never match — which made the '::1' and '[::1]' entries below
|
|
20
|
+
// unreachable, and refused a legitimate local vault.
|
|
21
|
+
//
|
|
22
|
+
// The ALLOWLIST is unchanged. URL only fixes what the host IS: it strips
|
|
23
|
+
// userinfo (so `http://127.0.0.1@evil.com/` reads as evil.com, as
|
|
24
|
+
// before), lowercases, and normalises numeric forms — `0177.0.0.1` is
|
|
25
|
+
// 127.0.0.1 and is now recognised as the loopback it actually is.
|
|
26
|
+
// IPv4-mapped IPv6 (`[::ffff:127.0.0.1]`) is deliberately still refused:
|
|
27
|
+
// widening the set is a separate decision from parsing it correctly.
|
|
28
|
+
let host: string
|
|
29
|
+
try {
|
|
30
|
+
host = new URL(addr).hostname
|
|
31
|
+
} catch (err: any) {
|
|
32
|
+
throw new SekretoError('sekreto: not a valid http(s) address: ' + addr)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if ('localhost' === host || '127.0.0.1' === host || '::1' === host || '[::1]' === host) {
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
throw new SekretoError(
|
|
40
|
+
'sekreto: refusing to send a token in plaintext to ' + addr + ' (use https)',
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** How long any single vault round-trip may take before it is treated as
|
|
45
|
+
* unreachable. Ports carry the same bound. */
|
|
46
|
+
|
|
47
|
+
/** One JSON round-trip. Network failure is always an error - an
|
|
48
|
+
* unreachable store is a store that could not answer. */
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/provider/aws.ts)
|
|
2
|
+
// Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
|
|
3
|
+
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
|
+
/* Copyright (c) 2025 Voxgig Ltd, MIT License */
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
ProviderSpec, Provider, SekretoError, awsparam, flatname, vaultref,
|
|
8
|
+
} from './support'
|
|
9
|
+
import { checkaddr } from './addr'
|
|
10
|
+
import { sigv4 } from '../Sigv4'
|
|
11
|
+
import { fetchjson } from './http'
|
|
12
|
+
|
|
13
|
+
function awsnow(): string {
|
|
14
|
+
return new Date()
|
|
15
|
+
.toISOString()
|
|
16
|
+
.replace(/[-:]/g, '')
|
|
17
|
+
.replace(/\.\d+Z$/, 'Z')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type Awsopts = {
|
|
21
|
+
region?: string
|
|
22
|
+
keyid?: string
|
|
23
|
+
secret?: string
|
|
24
|
+
session?: string
|
|
25
|
+
addr?: string
|
|
26
|
+
prefix?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Region and credentials, from config first and the standard AWS_*
|
|
30
|
+
* environment variables second - those are AWS's own convention, and a
|
|
31
|
+
* pod or CI job that has them set should just work. Missing either is
|
|
32
|
+
* an error: an AWS store with no credentials could not answer. */
|
|
33
|
+
function awsauth(opts: Awsopts): {
|
|
34
|
+
region: string
|
|
35
|
+
keyid: string
|
|
36
|
+
secret: string
|
|
37
|
+
session?: string
|
|
38
|
+
} {
|
|
39
|
+
const env = process.env
|
|
40
|
+
|
|
41
|
+
const region = opts.region || env.AWS_REGION || env.AWS_DEFAULT_REGION || ''
|
|
42
|
+
const keyid = opts.keyid || env.AWS_ACCESS_KEY_ID || ''
|
|
43
|
+
const secret = opts.secret || env.AWS_SECRET_ACCESS_KEY || ''
|
|
44
|
+
const session = opts.session || env.AWS_SESSION_TOKEN || undefined
|
|
45
|
+
|
|
46
|
+
if ('' === region) {
|
|
47
|
+
throw new SekretoError('sekreto: aws: no region (set region or AWS_REGION)')
|
|
48
|
+
}
|
|
49
|
+
if ('' === keyid || '' === secret) {
|
|
50
|
+
throw new SekretoError(
|
|
51
|
+
'sekreto: aws: no credentials (set keyid/secret or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY)',
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { region, keyid, secret, session }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** One signed call to an AWS JSON-1.1 API. */
|
|
59
|
+
async function awscall(
|
|
60
|
+
opts: Awsopts,
|
|
61
|
+
service: string,
|
|
62
|
+
target: string,
|
|
63
|
+
payload: any,
|
|
64
|
+
): Promise<{ status: number; body: any }> {
|
|
65
|
+
const auth = awsauth(opts)
|
|
66
|
+
// The China partition lives under its own suffix; every other
|
|
67
|
+
// commercial region is plain amazonaws.com.
|
|
68
|
+
const suffix = auth.region.startsWith('cn-') ? '.amazonaws.com.cn' : '.amazonaws.com'
|
|
69
|
+
const addr = opts.addr || 'https://' + service + '.' + auth.region + suffix
|
|
70
|
+
checkaddr(addr)
|
|
71
|
+
|
|
72
|
+
const url = addr.replace(/\/$/, '') + '/'
|
|
73
|
+
const body = JSON.stringify(payload)
|
|
74
|
+
const headers: Record<string, string> = {
|
|
75
|
+
'content-type': 'application/x-amz-json-1.1',
|
|
76
|
+
'x-amz-target': target,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const signed = sigv4({
|
|
80
|
+
method: 'POST',
|
|
81
|
+
url,
|
|
82
|
+
headers,
|
|
83
|
+
body,
|
|
84
|
+
service,
|
|
85
|
+
region: auth.region,
|
|
86
|
+
keyid: auth.keyid,
|
|
87
|
+
secret: auth.secret,
|
|
88
|
+
session: auth.session,
|
|
89
|
+
datetime: awsnow(),
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
return fetchjson('POST', url, { ...headers, ...signed }, body)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Does this AWS error body name one of the not-found types? Those are
|
|
96
|
+
* a miss; every other failure is a store that could not answer. */
|
|
97
|
+
function awsmiss(body: any, types: string[]): boolean {
|
|
98
|
+
const errtype = body && 'string' === typeof body.__type ? body.__type : ''
|
|
99
|
+
return types.some((name) => errtype.includes(name))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** AWS Secrets Manager.
|
|
103
|
+
*
|
|
104
|
+
* `api.token` reads the secret named `api` (the vaultref path, so
|
|
105
|
+
* `db.pass.main` reads `db/pass`) and takes the `token` field of its
|
|
106
|
+
* JSON SecretString - the AWS idiom of one JSON map per secret. A
|
|
107
|
+
* SecretString that is not JSON is the value itself, under the
|
|
108
|
+
* conventional field `value`. Requests are SigV4-signed in-tree; see
|
|
109
|
+
* Sigv4.ts. */
|
|
110
|
+
export function awssecretsprovider(options?: Awsopts): Provider {
|
|
111
|
+
const opts = options || {}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
lookup: async (name: string) => {
|
|
115
|
+
const ref = vaultref(name)
|
|
116
|
+
|
|
117
|
+
const res = await awscall(opts, 'secretsmanager', 'secretsmanager.GetSecretValue', {
|
|
118
|
+
SecretId: ref.path,
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
if (400 === res.status && awsmiss(res.body, ['ResourceNotFoundException'])) {
|
|
122
|
+
return undefined
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (200 !== res.status) {
|
|
126
|
+
throw new SekretoError('sekreto: aws secretsmanager error: ' + res.status)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const text = res.body && res.body.SecretString
|
|
130
|
+
|
|
131
|
+
if ('string' !== typeof text) {
|
|
132
|
+
// A binary secret has no fields to address; only the conventional
|
|
133
|
+
// `value` field can mean "the bytes themselves".
|
|
134
|
+
const bin = res.body && res.body.SecretBinary
|
|
135
|
+
if ('string' === typeof bin && 'value' === ref.field) {
|
|
136
|
+
return Buffer.from(bin, 'base64').toString('utf8')
|
|
137
|
+
}
|
|
138
|
+
return undefined
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let parsed: any
|
|
142
|
+
try {
|
|
143
|
+
parsed = JSON.parse(text)
|
|
144
|
+
} catch (err: any) {
|
|
145
|
+
parsed = undefined
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (parsed && 'object' === typeof parsed && !Array.isArray(parsed)) {
|
|
149
|
+
const value = parsed[ref.field]
|
|
150
|
+
return undefined === value || null === value ? undefined : String(value)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// A plain-string secret is the whole value; it has no named fields.
|
|
154
|
+
return 'value' === ref.field ? text : undefined
|
|
155
|
+
},
|
|
156
|
+
// Config only, never the environment: describe() feeds the spec's
|
|
157
|
+
// sources group, which must answer the same everywhere.
|
|
158
|
+
describe: () => 'awssecrets:' + (opts.region || ''),
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** AWS SSM Parameter Store.
|
|
163
|
+
*
|
|
164
|
+
* `db.pass.main` reads the parameter `/db/pass/main` (under an optional
|
|
165
|
+
* prefix path), decrypted. Parameter Store carries flat strings, so
|
|
166
|
+
* there is no field indirection. */
|
|
167
|
+
export function awsparamsprovider(options?: Awsopts): Provider {
|
|
168
|
+
const opts = options || {}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
lookup: async (name: string) => {
|
|
172
|
+
const res = await awscall(opts, 'ssm', 'AmazonSSM.GetParameter', {
|
|
173
|
+
Name: awsparam(name, opts.prefix),
|
|
174
|
+
WithDecryption: true,
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
if (400 === res.status && awsmiss(res.body, ['ParameterNotFound'])) {
|
|
178
|
+
return undefined
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (200 !== res.status) {
|
|
182
|
+
throw new SekretoError('sekreto: aws ssm error: ' + res.status)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const value = res.body && res.body.Parameter && res.body.Parameter.Value
|
|
186
|
+
return undefined === value || null === value ? undefined : String(value)
|
|
187
|
+
},
|
|
188
|
+
describe: () => 'awsparams:' + (opts.region || '') + (opts.prefix || ''),
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** GCP Secret Manager.
|
|
193
|
+
*
|
|
194
|
+
* `api.token` reads secret `api_token` (dots flattened to `_`; Secret
|
|
195
|
+
* Manager ids have no hierarchy and reject dots), latest version. The
|
|
196
|
+
* token comes from config, then `GOOGLE_OAUTH_ACCESS_TOKEN`, then the
|
|
197
|
+
* GCE/GKE metadata server - so on Google's own platform no credential
|
|
198
|
+
* configuration is needed at all.
|
|
199
|
+
*
|
|
200
|
+
* The metadata call itself is plain http to a link-local host by
|
|
201
|
+
* platform design; no credential rides on it, so `checkaddr` guards the
|
|
202
|
+
* Secret Manager address instead. */
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
// Registering at import is what makes this module's presence the only
|
|
206
|
+
// thing that decides whether the kind exists in a build.
|
|
207
|
+
import { register } from './Registry'
|
|
208
|
+
|
|
209
|
+
register({
|
|
210
|
+
name: 'awssecrets',
|
|
211
|
+
needs: ['fetch', 'crypto'],
|
|
212
|
+
define: (spec: ProviderSpec) => awssecretsprovider(spec),
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
register({
|
|
216
|
+
name: 'awsparams',
|
|
217
|
+
needs: ['fetch', 'crypto'],
|
|
218
|
+
define: (spec: ProviderSpec) => awsparamsprovider(spec),
|
|
219
|
+
})
|