@voxgig/sdkgen 4.5.2 → 4.6.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/action/check.js +15 -0
- package/dist/action/check.js.map +1 -1
- package/dist/cmp/AgentGuide.js +1 -1
- package/dist/cmp/AgentGuide.js.map +1 -1
- package/dist/cmp/AgentGuideContent.d.ts +1 -1
- package/dist/cmp/AgentGuideContent.js +8 -2
- package/dist/cmp/AgentGuideContent.js.map +1 -1
- package/dist/cmp/Feature.js +17 -0
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/cmp/FeatureDocs.d.ts +1 -1
- package/dist/cmp/FeatureDocs.js +6 -1
- package/dist/cmp/FeatureDocs.js.map +1 -1
- package/dist/cmp/ReadmeExplanation.js +3 -1
- package/dist/cmp/ReadmeExplanation.js.map +1 -1
- package/dist/cmp/ReadmeFeatures.js +1 -1
- package/dist/cmp/ReadmeFeatures.js.map +1 -1
- package/dist/cmp/ReadmeRefFeatures.js +1 -1
- package/dist/cmp/ReadmeRefFeatures.js.map +1 -1
- package/dist/cmp/ReadmeTop.js +1 -1
- package/dist/helpers/applicability.d.ts +6 -0
- package/dist/helpers/applicability.js +103 -0
- package/dist/helpers/applicability.js.map +1 -0
- package/dist/helpers/collectDeps.js +4 -2
- package/dist/helpers/collectDeps.js.map +1 -1
- package/dist/helpers/serverVars.d.ts +2 -1
- package/dist/helpers/serverVars.js +17 -0
- package/dist/helpers/serverVars.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -2
- package/dist/sdkgen.js +9 -2
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.js +7 -1
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +39 -0
- package/package.json +1 -1
- package/project/.sdk/model/feature/feature-index.aon +1 -0
- package/project/.sdk/model/feature/secrets.aon +118 -0
- package/project/.sdk/model/target/ts.aon +8 -1
- package/project/.sdk/src/cmp/c/Config_c.ts +5 -1
- package/project/.sdk/src/cmp/cpp/Config_cpp.ts +5 -1
- package/project/.sdk/src/cmp/csharp/Config_csharp.ts +5 -1
- package/project/.sdk/src/cmp/dart/Config_dart.ts +5 -1
- package/project/.sdk/src/cmp/elixir/Config_elixir.ts +5 -1
- package/project/.sdk/src/cmp/elixir/Main_elixir.ts +5 -1
- package/project/.sdk/src/cmp/go/Config_go.ts +5 -1
- package/project/.sdk/src/cmp/go/Main_go.ts +6 -2
- package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +16 -3
- package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +16 -3
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +28 -4
- package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -10
- package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +5 -5
- package/project/.sdk/src/cmp/java/Config_java.ts +22 -1
- package/project/.sdk/src/cmp/java/Main_java.ts +5 -1
- package/project/.sdk/src/cmp/java/ReadmeQuick_java.ts +17 -3
- package/project/.sdk/src/cmp/java/ReadmeTopQuick_java.ts +17 -3
- package/project/.sdk/src/cmp/java/TestDirect_java.ts +23 -4
- package/project/.sdk/src/cmp/java/TestEntity_java.ts +29 -4
- package/project/.sdk/src/cmp/js/Config_js.ts +5 -1
- package/project/.sdk/src/cmp/js/Main_js.ts +5 -1
- package/project/.sdk/src/cmp/js/Package_js.ts +4 -1
- package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +5 -1
- package/project/.sdk/src/cmp/lean/Config_lean.ts +5 -1
- package/project/.sdk/src/cmp/lua/Config_lua.ts +5 -1
- package/project/.sdk/src/cmp/lua/Main_lua.ts +5 -1
- package/project/.sdk/src/cmp/lua/Package_lua.ts +4 -1
- package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +5 -1
- package/project/.sdk/src/cmp/perl/Main_perl.ts +5 -1
- package/project/.sdk/src/cmp/php/Config_php.ts +5 -1
- package/project/.sdk/src/cmp/php/Main_php.ts +5 -1
- package/project/.sdk/src/cmp/py/Config_py.ts +5 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +5 -1
- package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +16 -3
- package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +16 -3
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +24 -5
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +23 -3
- package/project/.sdk/src/cmp/rb/Config_rb.ts +5 -1
- package/project/.sdk/src/cmp/rb/Main_rb.ts +5 -1
- package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -1
- package/project/.sdk/src/cmp/rust/Main_rust.ts +5 -1
- package/project/.sdk/src/cmp/scala/Config_scala.ts +5 -1
- package/project/.sdk/src/cmp/scala/Main_scala.ts +5 -1
- package/project/.sdk/src/cmp/swift/Config_swift.ts +5 -1
- package/project/.sdk/src/cmp/ts/Config_ts.ts +5 -1
- package/project/.sdk/src/cmp/ts/Main_ts.ts +57 -1
- package/project/.sdk/src/cmp/ts/Package_ts.ts +4 -1
- package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +15 -3
- package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +18 -5
- package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +19 -5
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +67 -7
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +27 -4
- package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +9 -2
- package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +9 -2
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +7 -0
- package/project/.sdk/src/cmp/zig/Config_zig.ts +5 -1
- package/project/.sdk/tm/c/utility/make_options.c +22 -0
- package/project/.sdk/tm/clojure/src/sdk/core.clj +18 -1
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +23 -0
- package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +15 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +23 -0
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +22 -0
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -0
- package/project/.sdk/tm/go/core/context.go +1 -1
- package/project/.sdk/tm/go/feature/cost_feature.go +1 -1
- package/project/.sdk/tm/go/feature/paging_feature.go +5 -5
- package/project/.sdk/tm/go/feature/test_feature.go +12 -5
- package/project/.sdk/tm/go/test/custom_utility_test.go +15 -0
- package/project/.sdk/tm/go/test/feature_corpus_test.go +7 -0
- package/project/.sdk/tm/go/test/runner_test.go +46 -0
- package/project/.sdk/tm/go/test/struct_runner_test.go +7 -7
- package/project/.sdk/tm/go/test/struct_utility_test.go +63 -27
- package/project/.sdk/tm/go/utility/fetcher.go +2 -2
- package/project/.sdk/tm/go/utility/make_options.go +24 -5
- package/project/.sdk/tm/go/utility/make_point.go +1 -1
- package/project/.sdk/tm/go/utility/make_spec.go +17 -1
- package/project/.sdk/tm/go/utility/prepare_auth.go +1 -1
- package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +370 -307
- package/project/.sdk/tm/go/utility/transform_request.go +29 -1
- package/project/.sdk/tm/go/utility/transform_response.go +20 -1
- package/project/.sdk/tm/java/test/CustomUtilityTest.java +10 -0
- package/project/.sdk/tm/java/test/FeatureCorpusTest.java +7 -0
- package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +6 -0
- package/project/.sdk/tm/java/test/RunnerSupport.java +43 -0
- package/project/.sdk/tm/java/utility/MakeOptions.java +84 -1
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +24 -0
- package/project/.sdk/tm/js/src/utility/StructUtility.js +2076 -1837
- package/project/.sdk/tm/js/test/feature/Corpus.test.js +9 -0
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +21 -0
- package/project/.sdk/tm/lean/src/SdkUtility.lean +12 -1
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +11 -0
- package/project/.sdk/tm/perl/utility/make_options.pm +18 -0
- package/project/.sdk/tm/php/test/FeatureCorpusTest.php +10 -1
- package/project/.sdk/tm/php/utility/MakeOptions.php +24 -0
- package/project/.sdk/tm/py/pkg/utility/make_options.py +20 -0
- package/project/.sdk/tm/py/test/runner.py +32 -0
- package/project/.sdk/tm/py/test/test_feature_corpus.py +10 -1
- package/project/.sdk/tm/rb/test/feature_corpus_test.rb +9 -1
- package/project/.sdk/tm/rb/utility/make_options.rb +17 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +25 -0
- package/project/.sdk/tm/scala/utility/Make.scala +17 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -0
- package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +419 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +1221 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +381 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +151 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +42 -0
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +18 -0
- package/project/.sdk/tm/ts/src/utility/StructUtility.ts +1321 -710
- package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +9 -0
- package/project/.sdk/tm/ts/test/feature/secrets/Secrets.test.ts +764 -0
- package/project/.sdk/tm/ts/test/omni.test.ts +84 -0
- package/project/.sdk/tm/ts/test/omni.ts +86 -0
- package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +6 -2
- package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +2 -2
- package/project/.sdk/tm/ts/test/utility.ts +122 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +692 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +238 -0
- package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +188 -0
- package/project/.sdk/tm/ts/test/vendor/omni/index.ts +38 -0
- package/project/.sdk/tm/zig/core/utility.zig +23 -0
- package/project/sdkgen-package.json +2 -1
- package/src/action/check.ts +18 -0
- package/src/cmp/AgentGuide.ts +1 -1
- package/src/cmp/AgentGuideContent.ts +9 -2
- package/src/cmp/Feature.ts +19 -0
- package/src/cmp/FeatureDocs.ts +7 -1
- package/src/cmp/ReadmeExplanation.ts +4 -1
- package/src/cmp/ReadmeFeatures.ts +1 -1
- package/src/cmp/ReadmeRefFeatures.ts +1 -1
- package/src/cmp/ReadmeTop.ts +1 -1
- package/src/helpers/applicability.ts +123 -0
- package/src/helpers/collectDeps.ts +5 -1
- package/src/helpers/serverVars.ts +19 -0
- package/src/sdkgen.ts +9 -1
- package/src/utility.ts +10 -1
- package/project/.sdk/tm/ts/test/runner.ts +0 -425
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
// VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/Sekreto.ts)
|
|
2
|
+
// Source: https://github.com/voxgig/sekreto @ a8c293be1b6c33d65223b2b2275797c241b1a1f1
|
|
3
|
+
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
|
+
// sekreto: one interface for secrets, wherever they live.
|
|
5
|
+
//
|
|
6
|
+
// A Sekreto is an ordered chain of providers. `get` asks each in turn and
|
|
7
|
+
// returns the first hit, so an app can be configured from environment
|
|
8
|
+
// variables in development and a vault in production without changing a
|
|
9
|
+
// line of its own code.
|
|
10
|
+
//
|
|
11
|
+
// This file is CANONICAL. Every other port is a translation of it, and
|
|
12
|
+
// spec/sekreto.json is the behavioural contract they all run.
|
|
13
|
+
|
|
14
|
+
import { Provider, ProviderSpec, makeprovider } from './Providers'
|
|
15
|
+
|
|
16
|
+
/** A secret name: dot-separated lowercase segments, e.g. `api.token`. */
|
|
17
|
+
export type Name = string
|
|
18
|
+
|
|
19
|
+
export type SekretoOptions = {
|
|
20
|
+
/** The provider chain, in resolution order. */
|
|
21
|
+
providers?: (Provider | ProviderSpec)[]
|
|
22
|
+
/** Cache resolved values (default: true). */
|
|
23
|
+
cache?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Anything sekreto refuses to do: a bad name, a missing secret, a
|
|
27
|
+
* provider that could not be reached. */
|
|
28
|
+
export class SekretoError extends Error {
|
|
29
|
+
constructor(message: string) {
|
|
30
|
+
super(message)
|
|
31
|
+
this.name = 'SekretoError'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const NAMEPART = /^[a-z0-9_]+$/
|
|
36
|
+
|
|
37
|
+
/** Is this a well-formed secret name? */
|
|
38
|
+
export function validname(name: any): boolean {
|
|
39
|
+
if ('string' !== typeof name || 0 === name.length) {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const parts = name.split('.')
|
|
44
|
+
|
|
45
|
+
for (const part of parts) {
|
|
46
|
+
if (!NAMEPART.test(part)) {
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return true
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function checkname(name: any): string {
|
|
55
|
+
if (!validname(name)) {
|
|
56
|
+
throw new SekretoError('sekreto: invalid name: ' + String(null == name ? '' : name))
|
|
57
|
+
}
|
|
58
|
+
return name
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The environment-variable key for a name: `api.token` -> `API_TOKEN`. */
|
|
62
|
+
export function envkey(name: Name, prefix?: string): string {
|
|
63
|
+
checkname(name)
|
|
64
|
+
return (prefix || '') + name.split('.').join('_').toUpperCase()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Where a name lives in a KV vault: `api.token` -> `api` / `token`.
|
|
68
|
+
*
|
|
69
|
+
* A single-segment name has no path of its own, so it becomes a secret of
|
|
70
|
+
* that name with the conventional field `value`. */
|
|
71
|
+
export function vaultref(name: Name): { path: string; field: string } {
|
|
72
|
+
checkname(name)
|
|
73
|
+
|
|
74
|
+
const parts = name.split('.')
|
|
75
|
+
|
|
76
|
+
if (1 === parts.length) {
|
|
77
|
+
return { path: parts[0], field: 'value' }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { path: parts.slice(0, -1).join('/'), field: parts[parts.length - 1] }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A name flattened to one segment: `api.token` -> `api_token` (GCP
|
|
84
|
+
* Secret Manager, `_`) or `api-token` (Azure Key Vault, `-`).
|
|
85
|
+
*
|
|
86
|
+
* Those stores have no path hierarchy and reject dots in ids, so the
|
|
87
|
+
* dots become the store's conventional separator. With `-` as the
|
|
88
|
+
* separator, underscores flatten too: Azure Key Vault's alphabet is
|
|
89
|
+
* letters, digits and hyphens only, and a valid sekreto name like
|
|
90
|
+
* `with_underscore` must still be representable there. (The resulting
|
|
91
|
+
* `.`/`_` collision mirrors the documented envkey behaviour, where
|
|
92
|
+
* both already map to `_`.) */
|
|
93
|
+
export function flatname(name: Name, sep: string): string {
|
|
94
|
+
checkname(name)
|
|
95
|
+
const flat = name.split('.').join(sep)
|
|
96
|
+
return '-' === sep ? flat.split('_').join('-') : flat
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The AWS SSM Parameter Store name for a name: dots become the path
|
|
100
|
+
* hierarchy, rooted at `/` (or at a prefix): `db.pass.main` ->
|
|
101
|
+
* `/db/pass/main`, or `/app/db/pass/main` under prefix `/app`. */
|
|
102
|
+
export function awsparam(name: Name, prefix?: string): string {
|
|
103
|
+
checkname(name)
|
|
104
|
+
|
|
105
|
+
let base = prefix || ''
|
|
106
|
+
if ('' !== base && !base.startsWith('/')) {
|
|
107
|
+
base = '/' + base
|
|
108
|
+
}
|
|
109
|
+
base = base.replace(/\/$/, '')
|
|
110
|
+
|
|
111
|
+
return base + '/' + name.split('.').join('/')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Parse `.env` text into a map of raw keys to values.
|
|
115
|
+
*
|
|
116
|
+
* Deliberately small: `KEY=value`, optional `export`, `#` comments on their
|
|
117
|
+
* own line, and single- or double-quoted values (double quotes also
|
|
118
|
+
* unescape `\n`, `\r`, `\t` and `\\`). A line with no `=` is skipped. */
|
|
119
|
+
export function parsedotenv(text: string): Record<string, string> {
|
|
120
|
+
const out: Record<string, string> = {}
|
|
121
|
+
|
|
122
|
+
if ('string' !== typeof text) {
|
|
123
|
+
return out
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const rawline of text.split('\n')) {
|
|
127
|
+
const line = rawline.replace(/\r$/, '').trim()
|
|
128
|
+
|
|
129
|
+
if (0 === line.length || line.startsWith('#')) {
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const body = line.startsWith('export ') ? line.slice(7).trim() : line
|
|
134
|
+
|
|
135
|
+
const eq = body.indexOf('=')
|
|
136
|
+
if (0 >= eq) {
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const key = body.slice(0, eq).trim()
|
|
141
|
+
let value = body.slice(eq + 1).trim()
|
|
142
|
+
|
|
143
|
+
if (2 <= value.length && value.startsWith('"') && value.endsWith('"')) {
|
|
144
|
+
value = unescape(value.slice(1, -1))
|
|
145
|
+
} else if (2 <= value.length && value.startsWith("'") && value.endsWith("'")) {
|
|
146
|
+
value = value.slice(1, -1)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
out[key] = value
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return out
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function unescape(text: string): string {
|
|
156
|
+
let out = ''
|
|
157
|
+
|
|
158
|
+
for (let index = 0; index < text.length; index++) {
|
|
159
|
+
if ('\\' === text[index] && index + 1 < text.length) {
|
|
160
|
+
const next = text[index + 1]
|
|
161
|
+
index++
|
|
162
|
+
if ('n' === next) {
|
|
163
|
+
out += '\n'
|
|
164
|
+
} else if ('r' === next) {
|
|
165
|
+
out += '\r'
|
|
166
|
+
} else if ('t' === next) {
|
|
167
|
+
out += '\t'
|
|
168
|
+
} else if ('\\' === next) {
|
|
169
|
+
out += '\\'
|
|
170
|
+
} else if ('"' === next) {
|
|
171
|
+
out += '"'
|
|
172
|
+
} else {
|
|
173
|
+
out += '\\' + next
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
out += text[index]
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return out
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Replace known secret values in text with `[redacted]`.
|
|
184
|
+
*
|
|
185
|
+
* Only values of four characters or more are replaced: shorter ones are
|
|
186
|
+
* too likely to appear in ordinary text, and redacting them would make
|
|
187
|
+
* logs unreadable without making them safer. */
|
|
188
|
+
export function redact(text: string, values: string[]): string {
|
|
189
|
+
let out = 'string' === typeof text ? text : ''
|
|
190
|
+
|
|
191
|
+
for (const value of values || []) {
|
|
192
|
+
if ('string' !== typeof value || 4 > value.length) {
|
|
193
|
+
continue
|
|
194
|
+
}
|
|
195
|
+
out = out.split(value).join('[redacted]')
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return out
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** One provider in the chain, under the store name it answers to. */
|
|
202
|
+
type Entry = { store: string; provider: Provider }
|
|
203
|
+
|
|
204
|
+
/** One resolved value. Kept as a list rather than a map so that the store
|
|
205
|
+
* a value came from stays attached, and so redaction order is stable. */
|
|
206
|
+
type Cached = { store: string; name: Name; value: string }
|
|
207
|
+
|
|
208
|
+
/** The store name a provider answers to when its spec does not say.
|
|
209
|
+
*
|
|
210
|
+
* `describe()` opens with the provider's kind - `vault:...`, `dotenv:...`,
|
|
211
|
+
* plain `env` - so the kind is the natural default, and a custom provider
|
|
212
|
+
* gets a sensible name without having to implement anything extra. */
|
|
213
|
+
function storename(provider: Provider, spec?: ProviderSpec): string {
|
|
214
|
+
if (spec && spec.name) {
|
|
215
|
+
return spec.name
|
|
216
|
+
}
|
|
217
|
+
return provider.describe().split(':')[0]
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** The secrets facade: a chain of providers plus a cache.
|
|
221
|
+
*
|
|
222
|
+
* Two ways to read. `get` is transparent - it walks the chain and takes
|
|
223
|
+
* the first hit, and the caller never learns which store answered. `getfrom`
|
|
224
|
+
* is directed - it names the store, and only that store is asked. Use the
|
|
225
|
+
* first for ordinary configuration, the second when *which* store holds a
|
|
226
|
+
* secret is part of what you mean. */
|
|
227
|
+
export class Sekreto {
|
|
228
|
+
private entries: Entry[]
|
|
229
|
+
private docache: boolean
|
|
230
|
+
private cache: Cached[]
|
|
231
|
+
// Every value ever resolved, for redact(). Kept independently of the
|
|
232
|
+
// read cache so that redaction still works when cache is off - otherwise
|
|
233
|
+
// `cache: false` would silently disable redact() and leak secrets to logs.
|
|
234
|
+
private seen: string[]
|
|
235
|
+
|
|
236
|
+
constructor(options?: SekretoOptions) {
|
|
237
|
+
const opts = options || {}
|
|
238
|
+
|
|
239
|
+
this.entries = (opts.providers || []).map((entry) => {
|
|
240
|
+
if ('function' === typeof (entry as Provider).lookup) {
|
|
241
|
+
const provider = entry as Provider
|
|
242
|
+
return { store: storename(provider), provider }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const spec = entry as ProviderSpec
|
|
246
|
+
const provider = makeprovider(spec)
|
|
247
|
+
return { store: storename(provider, spec), provider }
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
this.docache = false === opts.cache ? false : true
|
|
251
|
+
this.cache = []
|
|
252
|
+
this.seen = []
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** The secret, or a SekretoError if no provider has it. */
|
|
256
|
+
async get(name: Name): Promise<string> {
|
|
257
|
+
const found = await this.try(name)
|
|
258
|
+
|
|
259
|
+
if (undefined === found) {
|
|
260
|
+
throw new SekretoError('sekreto: unknown secret: ' + name)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return found
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** The secret, or undefined if no provider has it. */
|
|
267
|
+
async try(name: Name): Promise<string | undefined> {
|
|
268
|
+
return this.resolve('', name, this.entries)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** The secret from one named store, or a SekretoError if that store does
|
|
272
|
+
* not have it. */
|
|
273
|
+
async getfrom(store: string, name: Name): Promise<string> {
|
|
274
|
+
const found = await this.tryfrom(store, name)
|
|
275
|
+
|
|
276
|
+
if (undefined === found) {
|
|
277
|
+
throw new SekretoError('sekreto: unknown secret: ' + store + ':' + name)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return found
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** The secret from one named store, or undefined if that store does not
|
|
284
|
+
* have it.
|
|
285
|
+
*
|
|
286
|
+
* Naming a store that is not in the chain is an error, not a miss: `try`
|
|
287
|
+
* already means "this store may not have it", so it cannot also mean
|
|
288
|
+
* "this store may not exist" without hiding a typo. */
|
|
289
|
+
async tryfrom(store: string, name: Name): Promise<string | undefined> {
|
|
290
|
+
const matching = this.entries.filter((entry) => entry.store === store)
|
|
291
|
+
|
|
292
|
+
if (0 === matching.length) {
|
|
293
|
+
throw new SekretoError('sekreto: unknown store: ' + store)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return this.resolve(store, name, matching)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private async resolve(store: string, name: Name, entries: Entry[]): Promise<string | undefined> {
|
|
300
|
+
checkname(name)
|
|
301
|
+
|
|
302
|
+
if (this.docache) {
|
|
303
|
+
const hit = this.cache.find((entry) => entry.store === store && entry.name === name)
|
|
304
|
+
if (undefined !== hit) {
|
|
305
|
+
return hit.value
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
for (const entry of entries) {
|
|
310
|
+
const found = await entry.provider.lookup(name)
|
|
311
|
+
|
|
312
|
+
if (undefined !== found && null !== found) {
|
|
313
|
+
if (this.docache) {
|
|
314
|
+
this.cache.push({ store, name, value: found })
|
|
315
|
+
}
|
|
316
|
+
this.seen.push(found)
|
|
317
|
+
return found
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return undefined
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Does any provider have this secret? */
|
|
325
|
+
async has(name: Name): Promise<boolean> {
|
|
326
|
+
return undefined !== (await this.try(name))
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** Does this named store have this secret? */
|
|
330
|
+
async hasin(store: string, name: Name): Promise<boolean> {
|
|
331
|
+
return undefined !== (await this.tryfrom(store, name))
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Every named secret at once. Missing ones are an error. */
|
|
335
|
+
async all(names: Name[]): Promise<Record<string, string>> {
|
|
336
|
+
const out: Record<string, string> = {}
|
|
337
|
+
|
|
338
|
+
for (const name of names) {
|
|
339
|
+
out[name] = await this.get(name)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return out
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** A description of each provider, in resolution order. */
|
|
346
|
+
sources(): string[] {
|
|
347
|
+
return this.entries.map((entry) => entry.provider.describe())
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** The name of each store that can be named by `getfrom`, in resolution
|
|
351
|
+
* order and without repeats. */
|
|
352
|
+
stores(): string[] {
|
|
353
|
+
const out: string[] = []
|
|
354
|
+
|
|
355
|
+
for (const entry of this.entries) {
|
|
356
|
+
if (!out.includes(entry.store)) {
|
|
357
|
+
out.push(entry.store)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return out
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** Replace every value this Sekreto has resolved with `[redacted]`.
|
|
365
|
+
*
|
|
366
|
+
* Works whether or not caching is enabled: the redaction list is kept
|
|
367
|
+
* independently of the read cache. */
|
|
368
|
+
redact(text: string): string {
|
|
369
|
+
return redact(text, this.seen)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Drop cached values, so the next `get` asks the providers again. */
|
|
373
|
+
refresh(): void {
|
|
374
|
+
this.cache = []
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** Make a Sekreto from options. */
|
|
379
|
+
export function sekreto(options?: SekretoOptions): Sekreto {
|
|
380
|
+
return new Sekreto(options)
|
|
381
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/Sigv4.ts)
|
|
2
|
+
// Source: https://github.com/voxgig/sekreto @ a8c293be1b6c33d65223b2b2275797c241b1a1f1
|
|
3
|
+
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
|
+
// AWS Signature Version 4, hand-rolled.
|
|
5
|
+
//
|
|
6
|
+
// The AWS providers need exactly one thing from the AWS SDK - request
|
|
7
|
+
// signing - and taking the SDK for it would break the no-dependency rule
|
|
8
|
+
// that keeps ten ports honest. SigV4 is a stable, published algorithm
|
|
9
|
+
// built from HMAC-SHA256, which every port's standard library (or, for
|
|
10
|
+
// Rust, a small in-tree implementation) already has.
|
|
11
|
+
//
|
|
12
|
+
// `sigv4` is pure: the caller passes the timestamp, so the same input
|
|
13
|
+
// yields the same signature everywhere. That is what lets the shared spec
|
|
14
|
+
// carry known-answer cases that all ten ports must reproduce bit-for-bit,
|
|
15
|
+
// and lets the integration mock recompute the signature server-side.
|
|
16
|
+
|
|
17
|
+
import { createHash, createHmac } from 'node:crypto'
|
|
18
|
+
|
|
19
|
+
export type Sigv4Input = {
|
|
20
|
+
method: string
|
|
21
|
+
/** Full request URL; the host, path and query are signed. */
|
|
22
|
+
url: string
|
|
23
|
+
/** Extra headers to sign, e.g. content-type and x-amz-target. */
|
|
24
|
+
headers?: Record<string, string>
|
|
25
|
+
body?: string
|
|
26
|
+
service: string
|
|
27
|
+
region: string
|
|
28
|
+
keyid: string
|
|
29
|
+
secret: string
|
|
30
|
+
/** STS session token; signed as x-amz-security-token when present. */
|
|
31
|
+
session?: string
|
|
32
|
+
/** The signing moment, `YYYYMMDDTHHMMSSZ`. Passed in, never sampled,
|
|
33
|
+
* so the function stays pure. */
|
|
34
|
+
datetime: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The headers to attach to the request: authorization, x-amz-date, and
|
|
38
|
+
* x-amz-security-token when a session token was given. */
|
|
39
|
+
export type Sigv4Output = Record<string, string>
|
|
40
|
+
|
|
41
|
+
function sha256hex(text: string): string {
|
|
42
|
+
return createHash('sha256').update(text, 'utf8').digest('hex')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hmac(key: Buffer | string, text: string): Buffer {
|
|
46
|
+
return createHmac('sha256', key).update(text, 'utf8').digest()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** RFC 3986 escaping, which is stricter than encodeURIComponent: AWS
|
|
50
|
+
* wants `!`, `'`, `(`, `)` and `*` escaped too. */
|
|
51
|
+
function uriescape(text: string): string {
|
|
52
|
+
return encodeURIComponent(text).replace(
|
|
53
|
+
/[!'()*]/g,
|
|
54
|
+
(ch) => '%' + ch.charCodeAt(0).toString(16).toUpperCase(),
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The canonical query string: each pair RFC 3986-escaped, sorted by
|
|
59
|
+
* escaped key then escaped value. */
|
|
60
|
+
function canonicalquery(query: string): string {
|
|
61
|
+
if ('' === query) {
|
|
62
|
+
return ''
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const pairs = query.split('&').map((pair) => {
|
|
66
|
+
const eq = pair.indexOf('=')
|
|
67
|
+
const key = -1 === eq ? pair : pair.slice(0, eq)
|
|
68
|
+
const value = -1 === eq ? '' : pair.slice(eq + 1)
|
|
69
|
+
return [uriescape(decodeURIComponent(key)), uriescape(decodeURIComponent(value))]
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
pairs.sort((left, right) => {
|
|
73
|
+
if (left[0] !== right[0]) {
|
|
74
|
+
return left[0] < right[0] ? -1 : 1
|
|
75
|
+
}
|
|
76
|
+
return left[1] < right[1] ? -1 : left[1] > right[1] ? 1 : 0
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
return pairs.map((pair) => pair[0] + '=' + pair[1]).join('&')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Sign one request. Returns the headers to attach. */
|
|
83
|
+
export function sigv4(input: Sigv4Input): Sigv4Output {
|
|
84
|
+
const url = new URL(input.url)
|
|
85
|
+
|
|
86
|
+
const date = input.datetime.slice(0, 8)
|
|
87
|
+
const body = input.body || ''
|
|
88
|
+
|
|
89
|
+
// Every header that will be signed: the caller's extras, plus host and
|
|
90
|
+
// x-amz-date (and the session token when present), lower-cased and
|
|
91
|
+
// trimmed the way the canonical form requires.
|
|
92
|
+
// Canonical header values are trimmed AND internally collapsed -
|
|
93
|
+
// AWS folds sequential whitespace to one space before signing, so a
|
|
94
|
+
// header like "a b" must sign as "a b" or the service refuses it.
|
|
95
|
+
const headers: Record<string, string> = {}
|
|
96
|
+
for (const [key, value] of Object.entries(input.headers || {})) {
|
|
97
|
+
headers[key.toLowerCase()] = String(value).trim().replace(/\s+/g, ' ')
|
|
98
|
+
}
|
|
99
|
+
headers['host'] = url.host
|
|
100
|
+
headers['x-amz-date'] = input.datetime
|
|
101
|
+
if (input.session) {
|
|
102
|
+
headers['x-amz-security-token'] = input.session
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const names = Object.keys(headers).sort()
|
|
106
|
+
const canonicalheaders = names.map((name) => name + ':' + headers[name] + '\n').join('')
|
|
107
|
+
const signedheaders = names.join(';')
|
|
108
|
+
|
|
109
|
+
const canonicalrequest = [
|
|
110
|
+
input.method.toUpperCase(),
|
|
111
|
+
url.pathname || '/',
|
|
112
|
+
canonicalquery(url.search.replace(/^\?/, '')),
|
|
113
|
+
canonicalheaders,
|
|
114
|
+
signedheaders,
|
|
115
|
+
sha256hex(body),
|
|
116
|
+
].join('\n')
|
|
117
|
+
|
|
118
|
+
const scope = date + '/' + input.region + '/' + input.service + '/aws4_request'
|
|
119
|
+
|
|
120
|
+
const stringtosign = [
|
|
121
|
+
'AWS4-HMAC-SHA256',
|
|
122
|
+
input.datetime,
|
|
123
|
+
scope,
|
|
124
|
+
sha256hex(canonicalrequest),
|
|
125
|
+
].join('\n')
|
|
126
|
+
|
|
127
|
+
const kdate = hmac('AWS4' + input.secret, date)
|
|
128
|
+
const kregion = hmac(kdate, input.region)
|
|
129
|
+
const kservice = hmac(kregion, input.service)
|
|
130
|
+
const ksigning = hmac(kservice, 'aws4_request')
|
|
131
|
+
const signature = createHmac('sha256', ksigning).update(stringtosign, 'utf8').digest('hex')
|
|
132
|
+
|
|
133
|
+
const out: Sigv4Output = {
|
|
134
|
+
authorization:
|
|
135
|
+
'AWS4-HMAC-SHA256 Credential=' +
|
|
136
|
+
input.keyid +
|
|
137
|
+
'/' +
|
|
138
|
+
scope +
|
|
139
|
+
', SignedHeaders=' +
|
|
140
|
+
signedheaders +
|
|
141
|
+
', Signature=' +
|
|
142
|
+
signature,
|
|
143
|
+
'x-amz-date': input.datetime,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (input.session) {
|
|
147
|
+
out['x-amz-security-token'] = input.session
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return out
|
|
151
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/index.ts)
|
|
2
|
+
// Source: https://github.com/voxgig/sekreto @ a8c293be1b6c33d65223b2b2275797c241b1a1f1
|
|
3
|
+
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
|
+
// @voxgig/sekreto - one interface for secrets, wherever they live.
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
Sekreto,
|
|
8
|
+
SekretoError,
|
|
9
|
+
awsparam,
|
|
10
|
+
envkey,
|
|
11
|
+
flatname,
|
|
12
|
+
parsedotenv,
|
|
13
|
+
redact,
|
|
14
|
+
sekreto,
|
|
15
|
+
validname,
|
|
16
|
+
vaultref,
|
|
17
|
+
} from './Sekreto'
|
|
18
|
+
|
|
19
|
+
export type { Name, SekretoOptions } from './Sekreto'
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
awsparamsprovider,
|
|
23
|
+
awssecretsprovider,
|
|
24
|
+
azuresecretsprovider,
|
|
25
|
+
boruprovider,
|
|
26
|
+
checkaddr,
|
|
27
|
+
dopplerprovider,
|
|
28
|
+
dotenvprovider,
|
|
29
|
+
envprovider,
|
|
30
|
+
fileprovider,
|
|
31
|
+
gcpsecretsprovider,
|
|
32
|
+
hashicorpprovider,
|
|
33
|
+
infisicalprovider,
|
|
34
|
+
makeprovider,
|
|
35
|
+
memoryprovider,
|
|
36
|
+
onepasswordprovider,
|
|
37
|
+
} from './Providers'
|
|
38
|
+
|
|
39
|
+
export type { Provider, ProviderSpec } from './Providers'
|
|
40
|
+
|
|
41
|
+
export { sigv4 } from './Sigv4'
|
|
42
|
+
export type { Sigv4Input, Sigv4Output } from './Sigv4'
|
|
@@ -14,6 +14,19 @@ function makeOptions(ctx: Context) {
|
|
|
14
14
|
|
|
15
15
|
let opts = { ...(options || {}) }
|
|
16
16
|
|
|
17
|
+
// EXPLICIT AUTH SUPPRESSION, captured BEFORE merge and validate.
|
|
18
|
+
//
|
|
19
|
+
// `auth: null` is the documented way to disable auth outright, and
|
|
20
|
+
// prepareAuth honours it. But struct 0.3.2's getprop treats a stored
|
|
21
|
+
// null as "no value", so validate fills in the optspec's `auth`
|
|
22
|
+
// default and the suppression silently became "use default auth" —
|
|
23
|
+
// transmitting a credential the caller explicitly asked not to send.
|
|
24
|
+
// (0.0.10 rejected `auth: null` outright, so nothing depended on the
|
|
25
|
+
// old behaviour, and nothing catches the new one.)
|
|
26
|
+
//
|
|
27
|
+
// Suppliedness cannot be recovered after validate, hence here.
|
|
28
|
+
const authSuppressed = null === (options || {}).auth
|
|
29
|
+
|
|
17
30
|
// Feature add-order. `options.feature` may be given as an ordered ARRAY of
|
|
18
31
|
// { name, active, ...opts } entries (the array position IS the order in
|
|
19
32
|
// which features are added), or as a { name: {opts} } map. Normalize an
|
|
@@ -111,6 +124,11 @@ function makeOptions(ctx: Context) {
|
|
|
111
124
|
|
|
112
125
|
opts = validate(opts, optspec)
|
|
113
126
|
|
|
127
|
+
// Restore the suppression the optspec default would otherwise erase.
|
|
128
|
+
if (authSuppressed) {
|
|
129
|
+
opts.auth = null
|
|
130
|
+
}
|
|
131
|
+
|
|
114
132
|
// Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
|
|
115
133
|
// Every placeholder must resolve to a non-empty value: from
|
|
116
134
|
// options.server (user), else the Config default. A placeholder that
|