@homeflare/alchemy 0.4.0 → 0.5.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 +57 -0
- package/dist/openbao/approle-login-form.d.ts +72 -0
- package/dist/openbao/approle-login-form.d.ts.map +1 -0
- package/dist/openbao/approle-login-result.d.ts +39 -0
- package/dist/openbao/approle-login-result.d.ts.map +1 -0
- package/dist/openbao/approle-login.d.ts +77 -0
- package/dist/openbao/approle-login.d.ts.map +1 -0
- package/dist/openbao/bao-http.d.ts +2 -0
- package/dist/openbao/bao-http.d.ts.map +1 -1
- package/dist/openbao/index.d.ts +7 -0
- package/dist/openbao/index.d.ts.map +1 -1
- package/dist/openbao/index.js +589 -305
- package/dist/openbao/index.js.map +16 -8
- package/dist/openbao/mount-path.d.ts +14 -0
- package/dist/openbao/mount-path.d.ts.map +1 -0
- package/dist/openbao/plugin-form.d.ts +60 -0
- package/dist/openbao/plugin-form.d.ts.map +1 -0
- package/dist/openbao/plugin-reconcile.d.ts +18 -0
- package/dist/openbao/plugin-reconcile.d.ts.map +1 -0
- package/dist/openbao/plugin-wire.d.ts +45 -0
- package/dist/openbao/plugin-wire.d.ts.map +1 -0
- package/dist/openbao/plugin.d.ts +31 -0
- package/dist/openbao/plugin.d.ts.map +1 -0
- package/dist/openbao/ssh-role-form.d.ts +6 -5
- package/dist/openbao/ssh-role-form.d.ts.map +1 -1
- package/dist/openbao/ssh-role.d.ts.map +1 -1
- package/dist/proxmox/index.d.ts +23 -20
- package/dist/proxmox/index.d.ts.map +1 -1
- package/dist/proxmox/index.js +19 -1
- package/dist/proxmox/index.js.map +1 -1
- package/package.json +1 -1
- package/src/openbao/approle-login-form.test.ts +64 -0
- package/src/openbao/approle-login-form.ts +153 -0
- package/src/openbao/approle-login-result.ts +96 -0
- package/src/openbao/approle-login.test.ts +200 -0
- package/src/openbao/approle-login.ts +156 -0
- package/src/openbao/auth-role-form.test.ts +42 -0
- package/src/openbao/bao-http.ts +2 -0
- package/src/openbao/index.ts +13 -0
- package/src/openbao/mount-path.test.ts +23 -0
- package/src/openbao/mount-path.ts +25 -0
- package/src/openbao/mount-wire.test.ts +13 -0
- package/src/openbao/plugin-form.test.ts +86 -0
- package/src/openbao/plugin-form.ts +164 -0
- package/src/openbao/plugin-reconcile.ts +92 -0
- package/src/openbao/plugin-wire.test.ts +146 -0
- package/src/openbao/plugin-wire.ts +65 -0
- package/src/openbao/plugin.ts +100 -0
- package/src/openbao/ssh-role-form.test.ts +105 -0
- package/src/openbao/ssh-role-form.ts +18 -16
- package/src/openbao/ssh-role.ts +3 -4
- package/src/proxmox/index.test.ts +53 -0
- package/src/proxmox/index.ts +26 -20
package/dist/openbao/index.js
CHANGED
|
@@ -1,8 +1,301 @@
|
|
|
1
|
+
// src/openbao/approle-login.ts
|
|
2
|
+
import * as Effect2 from "effect/Effect";
|
|
3
|
+
import * as FetchHttpClient2 from "effect/unstable/http/FetchHttpClient";
|
|
4
|
+
|
|
5
|
+
// src/openbao/bao-http.ts
|
|
6
|
+
import * as Context from "effect/Context";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Option from "effect/Option";
|
|
9
|
+
import * as Schedule from "effect/Schedule";
|
|
10
|
+
import * as Semaphore from "effect/Semaphore";
|
|
11
|
+
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
12
|
+
import * as Headers from "effect/unstable/http/Headers";
|
|
13
|
+
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
14
|
+
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
|
|
15
|
+
|
|
16
|
+
// src/openbao/bao-address.ts
|
|
17
|
+
var DEFAULT_ADDRESS = "https://127.0.0.1:8200";
|
|
18
|
+
var UNIX = "unix://";
|
|
19
|
+
var readBaoVariable = (env, name) => env[name] ?? env[`VAULT_${name.slice("BAO_".length)}`] ?? "";
|
|
20
|
+
var resolveAddress = (env) => {
|
|
21
|
+
const agent = readBaoVariable(env, "BAO_AGENT_ADDR");
|
|
22
|
+
const direct = readBaoVariable(env, "BAO_ADDR");
|
|
23
|
+
const raw = agent !== "" ? agent : direct !== "" ? direct : DEFAULT_ADDRESS;
|
|
24
|
+
const namespace = readBaoVariable(env, "BAO_NAMESPACE");
|
|
25
|
+
const socket = raw.startsWith(UNIX) ? raw.slice(UNIX.length) : undefined;
|
|
26
|
+
return {
|
|
27
|
+
base: socket === undefined ? raw.replace(/\/+$/, "") : "http://localhost",
|
|
28
|
+
namespace: namespace === "" ? undefined : namespace,
|
|
29
|
+
socket
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
var headersFor = (address, env) => {
|
|
33
|
+
const headers = { "X-Vault-Request": "true" };
|
|
34
|
+
if (address.namespace !== undefined)
|
|
35
|
+
headers["X-Vault-Namespace"] = address.namespace;
|
|
36
|
+
const token = readBaoVariable(env, "BAO_TOKEN");
|
|
37
|
+
if (token !== "")
|
|
38
|
+
headers["X-Vault-Token"] = token;
|
|
39
|
+
return headers;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// src/openbao/bao-status.ts
|
|
43
|
+
class BaoError extends Error {
|
|
44
|
+
status;
|
|
45
|
+
method;
|
|
46
|
+
path;
|
|
47
|
+
errors;
|
|
48
|
+
constructor(status, method, path, errors) {
|
|
49
|
+
const code = status === 0 ? "no response" : String(status);
|
|
50
|
+
super(`OpenBao ${method} /v1/${path} -> ${code}: ${errors.join("; ") || "(no errors given)"}`);
|
|
51
|
+
this.status = status;
|
|
52
|
+
this.method = method;
|
|
53
|
+
this.path = path;
|
|
54
|
+
this.errors = errors;
|
|
55
|
+
this.name = "BaoError";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
var objectOf = (text) => {
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(text);
|
|
61
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : undefined;
|
|
62
|
+
} catch {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var errorsOf = (text) => {
|
|
67
|
+
const raw = objectOf(text)?.["errors"];
|
|
68
|
+
return Array.isArray(raw) && raw.every((entry) => typeof entry === "string") ? raw : undefined;
|
|
69
|
+
};
|
|
70
|
+
var settle = (intent, method, path, status, text) => {
|
|
71
|
+
const fail = (errors) => ({
|
|
72
|
+
error: new BaoError(status, method, path, errors)
|
|
73
|
+
});
|
|
74
|
+
if (status < 200 || status >= 300) {
|
|
75
|
+
const errors = errorsOf(text);
|
|
76
|
+
if (errors === undefined) {
|
|
77
|
+
return fail([`not an OpenBao error body (${String(text.length)} bytes)`]);
|
|
78
|
+
}
|
|
79
|
+
if (status === 404 && intent !== "write")
|
|
80
|
+
return { body: undefined };
|
|
81
|
+
return fail(errors);
|
|
82
|
+
}
|
|
83
|
+
if (text.trim() === "") {
|
|
84
|
+
return intent === "read" ? fail(["a successful read with an empty body"]) : { body: undefined };
|
|
85
|
+
}
|
|
86
|
+
const body = objectOf(text);
|
|
87
|
+
return body === undefined ? fail(["a success body that is not a JSON object"]) : { body };
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/openbao/bao-http.ts
|
|
91
|
+
var BaoEnv = Context.Reference("homeflare/openbao/BaoEnv", {
|
|
92
|
+
defaultValue: () => process.env
|
|
93
|
+
});
|
|
94
|
+
var MAX_IN_FLIGHT = 8;
|
|
95
|
+
var BaoGate = Context.Reference("homeflare/openbao/BaoGate", {
|
|
96
|
+
defaultValue: () => Semaphore.makeUnsafe(MAX_IN_FLIGHT)
|
|
97
|
+
});
|
|
98
|
+
var TIMEOUT = "60 seconds";
|
|
99
|
+
var retryTransport = (effect) => Effect.retry(effect, {
|
|
100
|
+
schedule: Schedule.spaced("750 millis"),
|
|
101
|
+
times: 2,
|
|
102
|
+
while: (error) => error.status === 0
|
|
103
|
+
});
|
|
104
|
+
var redactingToken = (effect) => Effect.gen(function* () {
|
|
105
|
+
const names = yield* Headers.CurrentRedactedNames;
|
|
106
|
+
return yield* Effect.provideService(effect, Headers.CurrentRedactedNames, [
|
|
107
|
+
...names,
|
|
108
|
+
"x-vault-token"
|
|
109
|
+
]);
|
|
110
|
+
});
|
|
111
|
+
var overSocket = (socket, method, path) => (effect) => {
|
|
112
|
+
if (socket === undefined)
|
|
113
|
+
return effect;
|
|
114
|
+
if (typeof Bun === "undefined") {
|
|
115
|
+
return Effect.fail(new BaoError(0, method, path, ["a unix:// address needs Bun fetch; this is not Bun"]));
|
|
116
|
+
}
|
|
117
|
+
return Effect.flatMap(Effect.serviceOption(FetchHttpClient.RequestInit), (defaults) => {
|
|
118
|
+
const init = { ...Option.getOrElse(defaults, () => ({})), unix: socket };
|
|
119
|
+
return Effect.provideService(effect, FetchHttpClient.RequestInit, init);
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
var baoCall = (intent, method, path, body) => Effect.gen(function* () {
|
|
123
|
+
const env = yield* BaoEnv;
|
|
124
|
+
const address = resolveAddress(env);
|
|
125
|
+
const client = yield* HttpClient.HttpClient;
|
|
126
|
+
const request = HttpClientRequest.make(method)(`${address.base}/v1/${path}`).pipe(HttpClientRequest.setHeaders(headersFor(address, env)), body === undefined ? (self) => self : HttpClientRequest.bodyText(JSON.stringify(body), "application/json"));
|
|
127
|
+
const exchange = client.execute(request).pipe(Effect.flatMap((response) => Effect.map(response.text, (text2) => ({ status: response.status, text: text2 }))), Effect.timeout(TIMEOUT), Effect.mapError((cause) => new BaoError(0, method, path, [cause.message])));
|
|
128
|
+
const gate = yield* BaoGate;
|
|
129
|
+
const { status, text } = yield* retryTransport(exchange.pipe(overSocket(address.socket, method, path), redactingToken, Semaphore.withPermits(gate, 1)));
|
|
130
|
+
const outcome = settle(intent, method, path, status, text);
|
|
131
|
+
return "error" in outcome ? yield* Effect.fail(outcome.error) : outcome.body;
|
|
132
|
+
});
|
|
133
|
+
var baoRead = (path) => Effect.map(baoCall("read", "GET", path), (body) => {
|
|
134
|
+
if (body === undefined)
|
|
135
|
+
return;
|
|
136
|
+
const data = body["data"];
|
|
137
|
+
return typeof data === "object" && data !== null ? data : body;
|
|
138
|
+
});
|
|
139
|
+
var baoWrite = (method, path, body) => Effect.asVoid(baoCall("write", method, path, body));
|
|
140
|
+
var baoDelete = (path) => Effect.asVoid(baoCall("delete", "DELETE", path));
|
|
141
|
+
|
|
142
|
+
// src/openbao/mount-path.ts
|
|
143
|
+
var trimTrailingSlashes = (text) => {
|
|
144
|
+
let end = text.length;
|
|
145
|
+
while (end > 0 && text[end - 1] === "/")
|
|
146
|
+
end -= 1;
|
|
147
|
+
return text.slice(0, end);
|
|
148
|
+
};
|
|
149
|
+
var trimSlashes = (text) => {
|
|
150
|
+
let start = 0;
|
|
151
|
+
while (start < text.length && text[start] === "/")
|
|
152
|
+
start += 1;
|
|
153
|
+
return trimTrailingSlashes(text.slice(start));
|
|
154
|
+
};
|
|
155
|
+
var sshMountOf = (mount) => trimTrailingSlashes(mount ?? "ssh");
|
|
156
|
+
|
|
157
|
+
// src/openbao/approle-login-form.ts
|
|
158
|
+
var failureOf = (status) => status === 0 ? "unreachable" : status >= 200 && status < 300 ? "response" : "refused";
|
|
159
|
+
|
|
160
|
+
class BaoLoginError extends Error {
|
|
161
|
+
reason;
|
|
162
|
+
status;
|
|
163
|
+
errors;
|
|
164
|
+
constructor(reason, status, errors, operation) {
|
|
165
|
+
const code = status === 0 ? reason : `${reason} ${String(status)}`;
|
|
166
|
+
super(`${operation}: ${code}: ${errors.join("; ") || "(no errors given)"}`);
|
|
167
|
+
this.reason = reason;
|
|
168
|
+
this.status = status;
|
|
169
|
+
this.errors = errors;
|
|
170
|
+
this.name = "BaoLoginError";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
var refusal = (label, value) => {
|
|
174
|
+
if (typeof value !== "string" || value === "")
|
|
175
|
+
return `${label} is empty`;
|
|
176
|
+
if (value.trim() === "")
|
|
177
|
+
return `${label} is only whitespace`;
|
|
178
|
+
if (value.trim() !== value) {
|
|
179
|
+
return `${label} has leading or trailing whitespace (a file read keeps its newline; trim it)`;
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
};
|
|
183
|
+
var loginMount = (mount) => trimSlashes(mount ?? "approle");
|
|
184
|
+
var mountProblem = (mount) => {
|
|
185
|
+
const path = loginMount(mount);
|
|
186
|
+
if (path === "")
|
|
187
|
+
return "mount is empty";
|
|
188
|
+
const segments = path.split("/");
|
|
189
|
+
if (/[\s?#%\\]/.test(path) || segments.some((part) => part === "" || part === "." || part === "..")) {
|
|
190
|
+
return `mount ${JSON.stringify(path)} is not a plain mount path`;
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
};
|
|
194
|
+
var loginPath = (mount) => `auth/${loginMount(mount)}/login`;
|
|
195
|
+
var GO_ESCAPES = {
|
|
196
|
+
7: "a",
|
|
197
|
+
8: "b",
|
|
198
|
+
9: "t",
|
|
199
|
+
10: "n",
|
|
200
|
+
11: "v",
|
|
201
|
+
12: "f",
|
|
202
|
+
13: "r"
|
|
203
|
+
};
|
|
204
|
+
var goQuoted = (value) => [...value].map((char) => {
|
|
205
|
+
const code = char.codePointAt(0) ?? 0;
|
|
206
|
+
const named = GO_ESCAPES[code];
|
|
207
|
+
if (char === '"' || char === "\\")
|
|
208
|
+
return `\\${char}`;
|
|
209
|
+
if (named !== undefined)
|
|
210
|
+
return `\\${named}`;
|
|
211
|
+
if (code < 32 || code === 127)
|
|
212
|
+
return `\\x${code.toString(16).padStart(2, "0")}`;
|
|
213
|
+
return char;
|
|
214
|
+
}).join("");
|
|
215
|
+
var redact = (errors, secrets) => {
|
|
216
|
+
const forms = [...new Set(secrets.flatMap((secret) => [secret, goQuoted(secret)]))].filter((form) => form !== "").sort((a, b) => b.length - a.length);
|
|
217
|
+
return errors.map((entry) => forms.reduce((text, form) => text.split(form).join("[redacted]"), entry));
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// src/openbao/approle-login-result.ts
|
|
221
|
+
class LoginResult {
|
|
222
|
+
#token;
|
|
223
|
+
accessor;
|
|
224
|
+
policies;
|
|
225
|
+
leaseDurationSeconds;
|
|
226
|
+
constructor(token, accessor, policies, lease) {
|
|
227
|
+
this.#token = token;
|
|
228
|
+
this.accessor = accessor;
|
|
229
|
+
this.policies = Object.freeze([...policies]);
|
|
230
|
+
this.leaseDurationSeconds = lease;
|
|
231
|
+
Object.freeze(this);
|
|
232
|
+
}
|
|
233
|
+
get clientToken() {
|
|
234
|
+
return this.#token;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
var strings = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
238
|
+
var loginOf = (body) => {
|
|
239
|
+
const auth = body?.["auth"];
|
|
240
|
+
if (typeof auth !== "object" || auth === null || Array.isArray(auth)) {
|
|
241
|
+
return { problem: "the answer carried no auth block" };
|
|
242
|
+
}
|
|
243
|
+
const fields = auth;
|
|
244
|
+
const token = fields["client_token"];
|
|
245
|
+
if (typeof token !== "string" || token === "") {
|
|
246
|
+
const mfa = fields["mfa_requirement"];
|
|
247
|
+
return {
|
|
248
|
+
problem: mfa !== undefined && mfa !== null ? "the login requires MFA, which this helper does not complete" : "the auth block carried no client_token"
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
const lease = fields["lease_duration"];
|
|
252
|
+
return {
|
|
253
|
+
login: new LoginResult(token, typeof fields["accessor"] === "string" ? fields["accessor"] : "", strings(fields["policies"]), typeof lease === "number" && Number.isFinite(lease) ? lease : 0)
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// src/openbao/approle-login.ts
|
|
258
|
+
var LOGIN = "OpenBao AppRole login";
|
|
259
|
+
var REVOKE = "OpenBao revoke-self";
|
|
260
|
+
var failed = (operation, secrets) => (error) => new BaoLoginError(failureOf(error.status), error.status, redact(error.errors, secrets), operation);
|
|
261
|
+
var appRoleLoginEffect = (input) => Effect2.gen(function* () {
|
|
262
|
+
const problems = [
|
|
263
|
+
refusal("roleId", input.roleId),
|
|
264
|
+
refusal("secretId", input.secretId),
|
|
265
|
+
mountProblem(input.mount)
|
|
266
|
+
].filter((problem) => problem !== undefined);
|
|
267
|
+
if (problems.length > 0)
|
|
268
|
+
return yield* Effect2.fail(new BaoLoginError("input", 0, problems, LOGIN));
|
|
269
|
+
const env = yield* BaoEnv;
|
|
270
|
+
const body = yield* baoCall("write", "PUT", loginPath(input.mount), {
|
|
271
|
+
role_id: input.roleId,
|
|
272
|
+
secret_id: input.secretId
|
|
273
|
+
}).pipe(Effect2.provideService(BaoEnv, { ...env, BAO_TOKEN: "" }), Effect2.mapError(failed(LOGIN, [input.roleId, input.secretId])));
|
|
274
|
+
const read = loginOf(body);
|
|
275
|
+
if ("problem" in read) {
|
|
276
|
+
return yield* Effect2.fail(new BaoLoginError("response", 0, [read.problem], LOGIN));
|
|
277
|
+
}
|
|
278
|
+
return read.login;
|
|
279
|
+
});
|
|
280
|
+
var revokeSelfEffect = (token) => Effect2.gen(function* () {
|
|
281
|
+
const problem = refusal("token", token);
|
|
282
|
+
if (problem !== undefined) {
|
|
283
|
+
return yield* Effect2.fail(new BaoLoginError("input", 0, [problem], REVOKE));
|
|
284
|
+
}
|
|
285
|
+
const env = yield* BaoEnv;
|
|
286
|
+
yield* baoCall("write", "PUT", "auth/token/revoke-self").pipe(Effect2.provideService(BaoEnv, { ...env, BAO_TOKEN: token }), Effect2.mapError(failed(REVOKE, [token])));
|
|
287
|
+
});
|
|
288
|
+
var runWith = (env, effect) => {
|
|
289
|
+
const scoped = env === undefined ? effect : Effect2.provideService(effect, BaoEnv, env);
|
|
290
|
+
return Effect2.runPromise(scoped.pipe(Effect2.provide(FetchHttpClient2.layer)));
|
|
291
|
+
};
|
|
292
|
+
var appRoleLogin = (input) => runWith(input.env, appRoleLoginEffect(input));
|
|
293
|
+
var revokeSelf = (token, env) => runWith(env, revokeSelfEffect(token));
|
|
1
294
|
// src/openbao/auth-method.ts
|
|
2
295
|
import { Resource } from "alchemy";
|
|
3
296
|
import { isResolved } from "alchemy/Diff";
|
|
4
297
|
import * as Provider from "alchemy/Provider";
|
|
5
|
-
import * as
|
|
298
|
+
import * as Effect4 from "effect/Effect";
|
|
6
299
|
|
|
7
300
|
// src/openbao/digest.ts
|
|
8
301
|
var canonical = (text) => text.trimEnd();
|
|
@@ -143,151 +436,12 @@ var matches2 = (attributes, props) => attributes.type === props.type && (props.d
|
|
|
143
436
|
var wantsTune2 = (props) => props.description !== undefined || props.defaultLeaseTtl !== undefined || props.maxLeaseTtl !== undefined;
|
|
144
437
|
|
|
145
438
|
// src/openbao/auth-method-wire.ts
|
|
146
|
-
import * as
|
|
147
|
-
|
|
148
|
-
// src/openbao/bao-http.ts
|
|
149
|
-
import * as Context from "effect/Context";
|
|
150
|
-
import * as Effect from "effect/Effect";
|
|
151
|
-
import * as Option from "effect/Option";
|
|
152
|
-
import * as Schedule from "effect/Schedule";
|
|
153
|
-
import * as Semaphore from "effect/Semaphore";
|
|
154
|
-
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
155
|
-
import * as Headers from "effect/unstable/http/Headers";
|
|
156
|
-
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
157
|
-
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
|
|
158
|
-
|
|
159
|
-
// src/openbao/bao-address.ts
|
|
160
|
-
var DEFAULT_ADDRESS = "https://127.0.0.1:8200";
|
|
161
|
-
var UNIX = "unix://";
|
|
162
|
-
var readBaoVariable = (env, name) => env[name] ?? env[`VAULT_${name.slice("BAO_".length)}`] ?? "";
|
|
163
|
-
var resolveAddress = (env) => {
|
|
164
|
-
const agent = readBaoVariable(env, "BAO_AGENT_ADDR");
|
|
165
|
-
const direct = readBaoVariable(env, "BAO_ADDR");
|
|
166
|
-
const raw = agent !== "" ? agent : direct !== "" ? direct : DEFAULT_ADDRESS;
|
|
167
|
-
const namespace = readBaoVariable(env, "BAO_NAMESPACE");
|
|
168
|
-
const socket = raw.startsWith(UNIX) ? raw.slice(UNIX.length) : undefined;
|
|
169
|
-
return {
|
|
170
|
-
base: socket === undefined ? raw.replace(/\/+$/, "") : "http://localhost",
|
|
171
|
-
namespace: namespace === "" ? undefined : namespace,
|
|
172
|
-
socket
|
|
173
|
-
};
|
|
174
|
-
};
|
|
175
|
-
var headersFor = (address, env) => {
|
|
176
|
-
const headers = { "X-Vault-Request": "true" };
|
|
177
|
-
if (address.namespace !== undefined)
|
|
178
|
-
headers["X-Vault-Namespace"] = address.namespace;
|
|
179
|
-
const token = readBaoVariable(env, "BAO_TOKEN");
|
|
180
|
-
if (token !== "")
|
|
181
|
-
headers["X-Vault-Token"] = token;
|
|
182
|
-
return headers;
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
// src/openbao/bao-status.ts
|
|
186
|
-
class BaoError extends Error {
|
|
187
|
-
status;
|
|
188
|
-
method;
|
|
189
|
-
path;
|
|
190
|
-
errors;
|
|
191
|
-
constructor(status, method, path, errors) {
|
|
192
|
-
const code = status === 0 ? "no response" : String(status);
|
|
193
|
-
super(`OpenBao ${method} /v1/${path} -> ${code}: ${errors.join("; ") || "(no errors given)"}`);
|
|
194
|
-
this.status = status;
|
|
195
|
-
this.method = method;
|
|
196
|
-
this.path = path;
|
|
197
|
-
this.errors = errors;
|
|
198
|
-
this.name = "BaoError";
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
var objectOf = (text3) => {
|
|
202
|
-
try {
|
|
203
|
-
const parsed = JSON.parse(text3);
|
|
204
|
-
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : undefined;
|
|
205
|
-
} catch {
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
var errorsOf = (text3) => {
|
|
210
|
-
const raw = objectOf(text3)?.["errors"];
|
|
211
|
-
return Array.isArray(raw) && raw.every((entry) => typeof entry === "string") ? raw : undefined;
|
|
212
|
-
};
|
|
213
|
-
var settle = (intent, method, path, status, text3) => {
|
|
214
|
-
const fail = (errors) => ({
|
|
215
|
-
error: new BaoError(status, method, path, errors)
|
|
216
|
-
});
|
|
217
|
-
if (status < 200 || status >= 300) {
|
|
218
|
-
const errors = errorsOf(text3);
|
|
219
|
-
if (errors === undefined) {
|
|
220
|
-
return fail([`not an OpenBao error body (${String(text3.length)} bytes)`]);
|
|
221
|
-
}
|
|
222
|
-
if (status === 404 && intent !== "write")
|
|
223
|
-
return { body: undefined };
|
|
224
|
-
return fail(errors);
|
|
225
|
-
}
|
|
226
|
-
if (text3.trim() === "") {
|
|
227
|
-
return intent === "read" ? fail(["a successful read with an empty body"]) : { body: undefined };
|
|
228
|
-
}
|
|
229
|
-
const body = objectOf(text3);
|
|
230
|
-
return body === undefined ? fail(["a success body that is not a JSON object"]) : { body };
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
// src/openbao/bao-http.ts
|
|
234
|
-
var BaoEnv = Context.Reference("homeflare/openbao/BaoEnv", {
|
|
235
|
-
defaultValue: () => process.env
|
|
236
|
-
});
|
|
237
|
-
var MAX_IN_FLIGHT = 8;
|
|
238
|
-
var BaoGate = Context.Reference("homeflare/openbao/BaoGate", {
|
|
239
|
-
defaultValue: () => Semaphore.makeUnsafe(MAX_IN_FLIGHT)
|
|
240
|
-
});
|
|
241
|
-
var TIMEOUT = "60 seconds";
|
|
242
|
-
var retryTransport = (effect) => Effect.retry(effect, {
|
|
243
|
-
schedule: Schedule.spaced("750 millis"),
|
|
244
|
-
times: 2,
|
|
245
|
-
while: (error) => error.status === 0
|
|
246
|
-
});
|
|
247
|
-
var redactingToken = (effect) => Effect.gen(function* () {
|
|
248
|
-
const names = yield* Headers.CurrentRedactedNames;
|
|
249
|
-
return yield* Effect.provideService(effect, Headers.CurrentRedactedNames, [
|
|
250
|
-
...names,
|
|
251
|
-
"x-vault-token"
|
|
252
|
-
]);
|
|
253
|
-
});
|
|
254
|
-
var overSocket = (socket, method, path) => (effect) => {
|
|
255
|
-
if (socket === undefined)
|
|
256
|
-
return effect;
|
|
257
|
-
if (typeof Bun === "undefined") {
|
|
258
|
-
return Effect.fail(new BaoError(0, method, path, ["a unix:// address needs Bun fetch; this is not Bun"]));
|
|
259
|
-
}
|
|
260
|
-
return Effect.flatMap(Effect.serviceOption(FetchHttpClient.RequestInit), (defaults) => {
|
|
261
|
-
const init = { ...Option.getOrElse(defaults, () => ({})), unix: socket };
|
|
262
|
-
return Effect.provideService(effect, FetchHttpClient.RequestInit, init);
|
|
263
|
-
});
|
|
264
|
-
};
|
|
265
|
-
var baoCall = (intent, method, path, body) => Effect.gen(function* () {
|
|
266
|
-
const env = yield* BaoEnv;
|
|
267
|
-
const address = resolveAddress(env);
|
|
268
|
-
const client = yield* HttpClient.HttpClient;
|
|
269
|
-
const request = HttpClientRequest.make(method)(`${address.base}/v1/${path}`).pipe(HttpClientRequest.setHeaders(headersFor(address, env)), body === undefined ? (self) => self : HttpClientRequest.bodyText(JSON.stringify(body), "application/json"));
|
|
270
|
-
const exchange = client.execute(request).pipe(Effect.flatMap((response) => Effect.map(response.text, (text4) => ({ status: response.status, text: text4 }))), Effect.timeout(TIMEOUT), Effect.mapError((cause) => new BaoError(0, method, path, [cause.message])));
|
|
271
|
-
const gate = yield* BaoGate;
|
|
272
|
-
const { status, text: text3 } = yield* retryTransport(exchange.pipe(overSocket(address.socket, method, path), redactingToken, Semaphore.withPermits(gate, 1)));
|
|
273
|
-
const outcome = settle(intent, method, path, status, text3);
|
|
274
|
-
return "error" in outcome ? yield* Effect.fail(outcome.error) : outcome.body;
|
|
275
|
-
});
|
|
276
|
-
var baoRead = (path) => Effect.map(baoCall("read", "GET", path), (body) => {
|
|
277
|
-
if (body === undefined)
|
|
278
|
-
return;
|
|
279
|
-
const data = body["data"];
|
|
280
|
-
return typeof data === "object" && data !== null ? data : body;
|
|
281
|
-
});
|
|
282
|
-
var baoWrite = (method, path, body) => Effect.asVoid(baoCall("write", method, path, body));
|
|
283
|
-
var baoDelete = (path) => Effect.asVoid(baoCall("delete", "DELETE", path));
|
|
284
|
-
|
|
285
|
-
// src/openbao/auth-method-wire.ts
|
|
286
|
-
var absentFromTable = (path, asked) => baoRead("sys/auth").pipe(Effect2.mapError((listing) => new BaoError(asked.status, asked.method, asked.path, [
|
|
439
|
+
import * as Effect3 from "effect/Effect";
|
|
440
|
+
var absentFromTable = (path, asked) => baoRead("sys/auth").pipe(Effect3.mapError((listing) => new BaoError(asked.status, asked.method, asked.path, [
|
|
287
441
|
...asked.errors,
|
|
288
442
|
`sys/auth could not confirm the method is absent: ${listing.message}`
|
|
289
|
-
])),
|
|
290
|
-
var readAuthMethodData = (path) => baoRead(readPath2(path)).pipe(
|
|
443
|
+
])), Effect3.flatMap((table) => table !== undefined && !Object.hasOwn(table, `${authPath(path)}/`) ? Effect3.succeed(undefined) : Effect3.fail(asked)));
|
|
444
|
+
var readAuthMethodData = (path) => baoRead(readPath2(path)).pipe(Effect3.catchIf((error) => error.status === 400, (error) => absentFromTable(path, error)));
|
|
291
445
|
var enableAuthMethod = (props) => baoWrite("POST", readPath2(props.path), enableBody2(props));
|
|
292
446
|
var tuneAuthMethod = (props) => baoWrite("POST", tunePath2(props.path), tuneBody2(props));
|
|
293
447
|
var disableAuthMethod = (path) => baoDelete(readPath2(path));
|
|
@@ -296,18 +450,18 @@ var disableAuthMethod = (path) => baoDelete(readPath2(path));
|
|
|
296
450
|
var BaoAuthMethod = Resource("Bao.AuthMethod", {
|
|
297
451
|
defaultRemovalPolicy: "retain"
|
|
298
452
|
});
|
|
299
|
-
var readMethod = (props) =>
|
|
453
|
+
var readMethod = (props) => Effect4.gen(function* () {
|
|
300
454
|
const live = yield* readAuthMethodData(props.path);
|
|
301
455
|
if (live === undefined)
|
|
302
456
|
return;
|
|
303
457
|
return attributesOf2(props, live);
|
|
304
458
|
});
|
|
305
|
-
var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod,
|
|
306
|
-
list: () =>
|
|
307
|
-
read:
|
|
459
|
+
var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect4.succeed(BaoAuthMethod.Provider.of({
|
|
460
|
+
list: () => Effect4.succeed([]),
|
|
461
|
+
read: Effect4.fn(function* ({ olds }) {
|
|
308
462
|
return yield* readMethod(olds);
|
|
309
463
|
}),
|
|
310
|
-
diff:
|
|
464
|
+
diff: Effect4.fn(function* ({ news, output }) {
|
|
311
465
|
if (output === undefined || !isResolved(news))
|
|
312
466
|
return;
|
|
313
467
|
const live = yield* readMethod(news);
|
|
@@ -319,7 +473,7 @@ var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect3.succeed
|
|
|
319
473
|
return { action: "noop" };
|
|
320
474
|
return { action: "update" };
|
|
321
475
|
}),
|
|
322
|
-
reconcile:
|
|
476
|
+
reconcile: Effect4.fn(function* ({ news }) {
|
|
323
477
|
let live = yield* readMethod(news);
|
|
324
478
|
if (live === undefined) {
|
|
325
479
|
yield* enableAuthMethod(news);
|
|
@@ -327,17 +481,17 @@ var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect3.succeed
|
|
|
327
481
|
yield* tuneAuthMethod(news);
|
|
328
482
|
} else if (!matches2(live, news)) {
|
|
329
483
|
if (live.type !== news.type) {
|
|
330
|
-
return yield*
|
|
484
|
+
return yield* Effect4.die(new Error(`Bao.AuthMethod ${authPath(news.path)}: live type ${live.type} != ${news.type}. ` + "Auth method type is immutable — replace manually."));
|
|
331
485
|
}
|
|
332
486
|
yield* tuneAuthMethod(news);
|
|
333
487
|
}
|
|
334
488
|
live = yield* readMethod(news);
|
|
335
489
|
if (live === undefined) {
|
|
336
|
-
return yield*
|
|
490
|
+
return yield* Effect4.die(new Error(`Bao.AuthMethod ${authPath(news.path)}: write returned no error but the method is still absent.`));
|
|
337
491
|
}
|
|
338
492
|
return live;
|
|
339
493
|
}),
|
|
340
|
-
delete:
|
|
494
|
+
delete: Effect4.fn(function* ({ output }) {
|
|
341
495
|
yield* disableAuthMethod(output.path);
|
|
342
496
|
return;
|
|
343
497
|
})
|
|
@@ -346,7 +500,7 @@ var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect3.succeed
|
|
|
346
500
|
import { Resource as Resource2 } from "alchemy";
|
|
347
501
|
import { isResolved as isResolved2 } from "alchemy/Diff";
|
|
348
502
|
import * as Provider2 from "alchemy/Provider";
|
|
349
|
-
import * as
|
|
503
|
+
import * as Effect5 from "effect/Effect";
|
|
350
504
|
|
|
351
505
|
// src/openbao/auth-role-form.ts
|
|
352
506
|
var policiesOf = (live) => {
|
|
@@ -410,18 +564,18 @@ var matches3 = (attributes, props) => {
|
|
|
410
564
|
var BaoAuthRole = Resource2("Bao.AuthRole", {
|
|
411
565
|
defaultRemovalPolicy: "retain"
|
|
412
566
|
});
|
|
413
|
-
var readRole = (props) =>
|
|
567
|
+
var readRole = (props) => Effect5.gen(function* () {
|
|
414
568
|
const live = yield* baoRead(readPath3(props.name));
|
|
415
569
|
if (live === undefined)
|
|
416
570
|
return;
|
|
417
571
|
return attributesOf3(props, live);
|
|
418
572
|
});
|
|
419
|
-
var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole,
|
|
420
|
-
list: () =>
|
|
421
|
-
read:
|
|
573
|
+
var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect5.succeed(BaoAuthRole.Provider.of({
|
|
574
|
+
list: () => Effect5.succeed([]),
|
|
575
|
+
read: Effect5.fn(function* ({ olds }) {
|
|
422
576
|
return yield* readRole(olds);
|
|
423
577
|
}),
|
|
424
|
-
diff:
|
|
578
|
+
diff: Effect5.fn(function* ({ news, output }) {
|
|
425
579
|
if (output === undefined || !isResolved2(news))
|
|
426
580
|
return;
|
|
427
581
|
const live = yield* readRole(news);
|
|
@@ -431,18 +585,18 @@ var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect4.succeed(Ba
|
|
|
431
585
|
return { action: "noop" };
|
|
432
586
|
return { action: "update" };
|
|
433
587
|
}),
|
|
434
|
-
reconcile:
|
|
588
|
+
reconcile: Effect5.fn(function* ({ news }) {
|
|
435
589
|
const live = yield* readRole(news);
|
|
436
590
|
if (live === undefined || !matches3(live, news)) {
|
|
437
591
|
yield* baoWrite("PUT", readPath3(news.name), writeBody(news));
|
|
438
592
|
}
|
|
439
593
|
const after = yield* readRole(news);
|
|
440
594
|
if (after === undefined) {
|
|
441
|
-
return yield*
|
|
595
|
+
return yield* Effect5.die(new Error(`Bao.AuthRole ${news.name}: write returned no error but the role is still absent.`));
|
|
442
596
|
}
|
|
443
597
|
return after;
|
|
444
598
|
}),
|
|
445
|
-
delete:
|
|
599
|
+
delete: Effect5.fn(function* ({ output }) {
|
|
446
600
|
yield* baoDelete(readPath3(output.name));
|
|
447
601
|
return;
|
|
448
602
|
})
|
|
@@ -451,7 +605,7 @@ var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect4.succeed(Ba
|
|
|
451
605
|
import { Resource as Resource3 } from "alchemy";
|
|
452
606
|
import { isResolved as isResolved3 } from "alchemy/Diff";
|
|
453
607
|
import * as Provider3 from "alchemy/Provider";
|
|
454
|
-
import * as
|
|
608
|
+
import * as Effect8 from "effect/Effect";
|
|
455
609
|
|
|
456
610
|
// src/openbao/cloudflare-group-scope.ts
|
|
457
611
|
var zoneResource = (zoneId) => `com.cloudflare.api.account.zone.${zoneId}`;
|
|
@@ -471,7 +625,7 @@ var groupKey = (name, scope) => `${scope} ${name}`;
|
|
|
471
625
|
|
|
472
626
|
// src/openbao/cloudflare-permission-groups.ts
|
|
473
627
|
import * as Context2 from "effect/Context";
|
|
474
|
-
import * as
|
|
628
|
+
import * as Effect7 from "effect/Effect";
|
|
475
629
|
|
|
476
630
|
// src/openbao/cloudflare-permission-groups-solve.ts
|
|
477
631
|
var observationsOf = (declared, live) => declared.flatMap((role) => {
|
|
@@ -641,7 +795,7 @@ var samePolicies2 = (want, have) => want.length === have.length && want.every((w
|
|
|
641
795
|
});
|
|
642
796
|
|
|
643
797
|
// src/openbao/cloudflare-role-wire.ts
|
|
644
|
-
import * as
|
|
798
|
+
import * as Effect6 from "effect/Effect";
|
|
645
799
|
|
|
646
800
|
// src/openbao/cloudflare-role-form.ts
|
|
647
801
|
var rolePath = (mount, name) => `${mountPath(mount)}/roles/${name}`;
|
|
@@ -706,7 +860,7 @@ var differences = (props, policies, live) => {
|
|
|
706
860
|
};
|
|
707
861
|
|
|
708
862
|
// src/openbao/cloudflare-role-wire.ts
|
|
709
|
-
var readCloudflareRole = (mount, name) =>
|
|
863
|
+
var readCloudflareRole = (mount, name) => Effect6.map(baoRead(rolePath(mount, name)), (data) => data === undefined ? undefined : liveRoleOf(data));
|
|
710
864
|
var writeCloudflareRole = (mount, name, body) => baoWrite("PUT", rolePath(mount, name), body);
|
|
711
865
|
var deleteCloudflareRole = (mount, name) => baoDelete(rolePath(mount, name));
|
|
712
866
|
|
|
@@ -728,9 +882,9 @@ var pick = (mount, groups, refs, hint) => {
|
|
|
728
882
|
const unknown = refs.filter((ref) => !groups.has(groupKey(ref.name, ref.scope)));
|
|
729
883
|
if (unknown.length > 0) {
|
|
730
884
|
const problems = unknown.map((ref) => `unknown group ${JSON.stringify(ref.name)} at scope ${ref.scope} — ${hint}`);
|
|
731
|
-
return
|
|
885
|
+
return Effect7.fail(new CloudflareGroupsError(mount, problems));
|
|
732
886
|
}
|
|
733
|
-
return
|
|
887
|
+
return Effect7.succeed(new Map(refs.flatMap((ref) => {
|
|
734
888
|
const key = groupKey(ref.name, ref.scope);
|
|
735
889
|
const id = groups.get(key);
|
|
736
890
|
return id === undefined ? [] : [[key, id]];
|
|
@@ -738,15 +892,15 @@ var pick = (mount, groups, refs, hint) => {
|
|
|
738
892
|
};
|
|
739
893
|
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
740
894
|
var deniedHint = (mount) => `GET ${mountPath(mount)}/permission-groups answered 403 — this token's policy carries no ` + `\`path "${mountPath(mount)}/permission-groups" { capabilities = ["read"] }\` grant. ` + "It is declared in ../platform/secrets/vault/policies/homeflare-admin/" + "20-cloudflare-permission-groups.hcl and applies with `alchemy deploy` of this stack";
|
|
741
|
-
var deniedNamed = (mount, read) =>
|
|
742
|
-
var engineGroups = (mount) =>
|
|
895
|
+
var deniedNamed = (mount, read) => Effect7.catchIf(read, (error) => error.status === 403, () => Effect7.fail(new CloudflareGroupsError(mount, [deniedHint(mount)])));
|
|
896
|
+
var engineGroups = (mount) => Effect7.flatMap(deniedNamed(mount, baoRead(`${mountPath(mount)}/permission-groups`)), (data) => {
|
|
743
897
|
const list = data?.["permission_groups"];
|
|
744
898
|
if (data === undefined) {
|
|
745
899
|
const why = "GET <mount>/permission-groups answered 404 — the endpoint is not deployed here";
|
|
746
|
-
return
|
|
900
|
+
return Effect7.fail(new CloudflareGroupsError(mount, [why]));
|
|
747
901
|
}
|
|
748
902
|
if (!Array.isArray(list)) {
|
|
749
|
-
return
|
|
903
|
+
return Effect7.fail(new CloudflareGroupsError(mount, ["no permission_groups array in reply"]));
|
|
750
904
|
}
|
|
751
905
|
const scoped = new Map;
|
|
752
906
|
const idsOf = new Map;
|
|
@@ -773,24 +927,24 @@ var engineGroups = (mount) => Effect6.flatMap(deniedNamed(mount, baoRead(`${moun
|
|
|
773
927
|
const only = [...ids];
|
|
774
928
|
return ids.size === 1 && only[0] !== undefined ? [[name, only[0]]] : [];
|
|
775
929
|
}));
|
|
776
|
-
return problems.length > 0 ?
|
|
930
|
+
return problems.length > 0 ? Effect7.fail(new CloudflareGroupsError(mount, problems)) : Effect7.succeed(new Map([...atEveryScope(sole), ...scoped]));
|
|
777
931
|
});
|
|
778
|
-
var permissionGroupsFromEngine = () =>
|
|
932
|
+
var permissionGroupsFromEngine = () => Effect7.sync(() => {
|
|
779
933
|
const cache = new Map;
|
|
780
|
-
const cachedFor = (mount) =>
|
|
934
|
+
const cachedFor = (mount) => Effect7.suspend(() => {
|
|
781
935
|
const hit = cache.get(mountPath(mount));
|
|
782
936
|
if (hit !== undefined)
|
|
783
937
|
return hit;
|
|
784
|
-
return
|
|
938
|
+
return Effect7.flatMap(Effect7.cached(engineGroups(mount)), (lookup) => {
|
|
785
939
|
cache.set(mountPath(mount), lookup);
|
|
786
940
|
return lookup;
|
|
787
941
|
});
|
|
788
942
|
});
|
|
789
943
|
return CloudflarePermissionGroups.of({
|
|
790
|
-
resolve: (mount, refs) =>
|
|
944
|
+
resolve: (mount, refs) => Effect7.flatMap(cachedFor(mount), (groups) => pick(mount, groups, refs, "the engine does not list it for this account at that scope"))
|
|
791
945
|
});
|
|
792
946
|
});
|
|
793
|
-
var permissionGroupsFromLiveRoles = (declared) =>
|
|
947
|
+
var permissionGroupsFromLiveRoles = (declared) => Effect7.gen(function* () {
|
|
794
948
|
const accountOf = new Map;
|
|
795
949
|
const byAccount = new Map;
|
|
796
950
|
for (const [mount, roles] of declared) {
|
|
@@ -801,9 +955,9 @@ var permissionGroupsFromLiveRoles = (declared) => Effect6.gen(function* () {
|
|
|
801
955
|
}
|
|
802
956
|
const solved = new Map;
|
|
803
957
|
for (const [account, roles] of byAccount) {
|
|
804
|
-
const solve =
|
|
958
|
+
const solve = Effect7.gen(function* () {
|
|
805
959
|
const live = new Map;
|
|
806
|
-
yield*
|
|
960
|
+
yield* Effect7.all(roles.map((role) => Effect7.map(readCloudflareRole(role.mount, role.name), (read) => {
|
|
807
961
|
if (read?.policies !== undefined) {
|
|
808
962
|
live.set(`${role.mount}/${role.name}`, read.policies);
|
|
809
963
|
}
|
|
@@ -811,11 +965,11 @@ var permissionGroupsFromLiveRoles = (declared) => Effect6.gen(function* () {
|
|
|
811
965
|
const result = solveGroups(observationsOf(roles, live));
|
|
812
966
|
const problems = describeProblems(result);
|
|
813
967
|
if (problems.length > 0) {
|
|
814
|
-
return yield*
|
|
968
|
+
return yield* Effect7.fail(new CloudflareGroupsError(`account ${account}`, problems));
|
|
815
969
|
}
|
|
816
970
|
return atEveryScope(result.groups);
|
|
817
971
|
});
|
|
818
|
-
solved.set(account, yield*
|
|
972
|
+
solved.set(account, yield* Effect7.cached(solve));
|
|
819
973
|
}
|
|
820
974
|
return CloudflarePermissionGroups.of({
|
|
821
975
|
resolve: (mount, refs) => {
|
|
@@ -823,10 +977,10 @@ var permissionGroupsFromLiveRoles = (declared) => Effect6.gen(function* () {
|
|
|
823
977
|
const lookup = account === undefined ? undefined : solved.get(account);
|
|
824
978
|
if (account === undefined || lookup === undefined) {
|
|
825
979
|
const why = "no declared role on this mount, so no live role can vouch for its groups";
|
|
826
|
-
return
|
|
980
|
+
return Effect7.fail(new CloudflareGroupsError(mount, [why]));
|
|
827
981
|
}
|
|
828
982
|
const hint = `no live role on account ${account} carries it; a NEW group needs the engine endpoint`;
|
|
829
|
-
return
|
|
983
|
+
return Effect7.flatMap(lookup, (groups) => pick(mount, groups, refs, hint));
|
|
830
984
|
}
|
|
831
985
|
});
|
|
832
986
|
});
|
|
@@ -835,9 +989,9 @@ var permissionGroupsFromLiveRoles = (declared) => Effect6.gen(function* () {
|
|
|
835
989
|
var BaoCloudflareRole = Resource3("Bao.CloudflareRole", {
|
|
836
990
|
defaultRemovalPolicy: "retain"
|
|
837
991
|
});
|
|
838
|
-
var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole,
|
|
992
|
+
var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect8.gen(function* () {
|
|
839
993
|
const groups = yield* CloudflarePermissionGroups;
|
|
840
|
-
const resolved = (props) =>
|
|
994
|
+
const resolved = (props) => Effect8.gen(function* () {
|
|
841
995
|
const refs = new Map;
|
|
842
996
|
for (const policy of props.policies) {
|
|
843
997
|
const scope = policy.scope ?? scopeOfResource(policy.resource);
|
|
@@ -847,17 +1001,17 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
|
|
|
847
1001
|
const ids = yield* groups.resolve(props.mount, [...refs.values()]);
|
|
848
1002
|
const { missing, policies } = resolvePolicies(props.policies, ids);
|
|
849
1003
|
if (missing.length > 0) {
|
|
850
|
-
return yield*
|
|
1004
|
+
return yield* Effect8.die(new Error(`Bao.CloudflareRole ${rolePath(props.mount, props.name)}: groups ` + `${missing.join(", ")} came back unresolved from a lookup that reported success.`));
|
|
851
1005
|
}
|
|
852
1006
|
return policies;
|
|
853
1007
|
});
|
|
854
1008
|
return BaoCloudflareRole.Provider.of({
|
|
855
|
-
list: () =>
|
|
856
|
-
read:
|
|
1009
|
+
list: () => Effect8.succeed([]),
|
|
1010
|
+
read: Effect8.fn(function* ({ olds }) {
|
|
857
1011
|
const live = yield* readCloudflareRole(olds.mount, olds.name);
|
|
858
1012
|
return live === undefined ? undefined : attributesOf4(olds, live);
|
|
859
1013
|
}),
|
|
860
|
-
diff:
|
|
1014
|
+
diff: Effect8.fn(function* ({ news, output }) {
|
|
861
1015
|
if (output === undefined || !isResolved3(news))
|
|
862
1016
|
return;
|
|
863
1017
|
if (refusalOf(news) !== undefined)
|
|
@@ -868,11 +1022,11 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
|
|
|
868
1022
|
return { action: "update" };
|
|
869
1023
|
return differences(news, policies, live).length === 0 ? { action: "noop" } : { action: "update" };
|
|
870
1024
|
}),
|
|
871
|
-
reconcile:
|
|
1025
|
+
reconcile: Effect8.fn(function* ({ news }) {
|
|
872
1026
|
const path = rolePath(news.mount, news.name);
|
|
873
|
-
const
|
|
874
|
-
if (
|
|
875
|
-
return yield*
|
|
1027
|
+
const refusal2 = refusalOf(news);
|
|
1028
|
+
if (refusal2 !== undefined) {
|
|
1029
|
+
return yield* Effect8.die(new Error(`Bao.CloudflareRole ${path}: ${refusal2}.`));
|
|
876
1030
|
}
|
|
877
1031
|
const policies = yield* resolved(news);
|
|
878
1032
|
const live = yield* readCloudflareRole(news.mount, news.name);
|
|
@@ -881,15 +1035,15 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
|
|
|
881
1035
|
}
|
|
882
1036
|
const after = yield* readCloudflareRole(news.mount, news.name);
|
|
883
1037
|
if (after === undefined) {
|
|
884
|
-
return yield*
|
|
1038
|
+
return yield* Effect8.die(new Error(`Bao.CloudflareRole ${path}: the write succeeded but the role is absent.`));
|
|
885
1039
|
}
|
|
886
1040
|
const left = differences(news, policies, after);
|
|
887
1041
|
if (left.length > 0) {
|
|
888
|
-
return yield*
|
|
1042
|
+
return yield* Effect8.die(new Error(`Bao.CloudflareRole ${path}: wrote cleanly but reads back different in ` + `${left.map((d) => d.field).join(", ")}. Inspect the role before retrying.`));
|
|
889
1043
|
}
|
|
890
1044
|
return attributesOf4(news, after);
|
|
891
1045
|
}),
|
|
892
|
-
delete:
|
|
1046
|
+
delete: Effect8.fn(function* ({ output }) {
|
|
893
1047
|
yield* deleteCloudflareRole(output.mount, output.name);
|
|
894
1048
|
return;
|
|
895
1049
|
})
|
|
@@ -1083,15 +1237,15 @@ var parseRolesConfig = (yaml) => {
|
|
|
1083
1237
|
import { Resource as Resource4 } from "alchemy";
|
|
1084
1238
|
import { isResolved as isResolved4 } from "alchemy/Diff";
|
|
1085
1239
|
import * as Provider4 from "alchemy/Provider";
|
|
1086
|
-
import * as
|
|
1240
|
+
import * as Effect10 from "effect/Effect";
|
|
1087
1241
|
|
|
1088
1242
|
// src/openbao/mount-wire.ts
|
|
1089
|
-
import * as
|
|
1090
|
-
var absentFromTable2 = (path, asked) => baoRead("sys/mounts").pipe(
|
|
1243
|
+
import * as Effect9 from "effect/Effect";
|
|
1244
|
+
var absentFromTable2 = (path, asked) => baoRead("sys/mounts").pipe(Effect9.mapError((listing) => new BaoError(asked.status, asked.method, asked.path, [
|
|
1091
1245
|
...asked.errors,
|
|
1092
1246
|
`sys/mounts could not confirm the mount is absent: ${listing.message}`
|
|
1093
|
-
])),
|
|
1094
|
-
var readMountData = (path) => baoRead(readPath(path)).pipe(
|
|
1247
|
+
])), Effect9.flatMap((table) => table !== undefined && !Object.hasOwn(table, `${mountPath(path)}/`) ? Effect9.succeed(undefined) : Effect9.fail(asked)));
|
|
1248
|
+
var readMountData = (path) => baoRead(readPath(path)).pipe(Effect9.catchIf((error) => error.status === 400, (error) => absentFromTable2(path, error)));
|
|
1095
1249
|
var enableMount = (props) => baoWrite("POST", readPath(props.path), enableBody(props));
|
|
1096
1250
|
var tuneMount = (props) => baoWrite("POST", tunePath(props.path), tuneBody(props));
|
|
1097
1251
|
var disableMount = (path) => baoDelete(readPath(path));
|
|
@@ -1100,18 +1254,18 @@ var disableMount = (path) => baoDelete(readPath(path));
|
|
|
1100
1254
|
var BaoMount = Resource4("Bao.Mount", {
|
|
1101
1255
|
defaultRemovalPolicy: "retain"
|
|
1102
1256
|
});
|
|
1103
|
-
var readMount = (props) =>
|
|
1257
|
+
var readMount = (props) => Effect10.gen(function* () {
|
|
1104
1258
|
const live = yield* readMountData(props.path);
|
|
1105
1259
|
if (live === undefined)
|
|
1106
1260
|
return;
|
|
1107
1261
|
return attributesOf(props, live);
|
|
1108
1262
|
});
|
|
1109
|
-
var BaoMountProvider = () => Provider4.effect(BaoMount,
|
|
1110
|
-
list: () =>
|
|
1111
|
-
read:
|
|
1263
|
+
var BaoMountProvider = () => Provider4.effect(BaoMount, Effect10.succeed(BaoMount.Provider.of({
|
|
1264
|
+
list: () => Effect10.succeed([]),
|
|
1265
|
+
read: Effect10.fn(function* ({ olds }) {
|
|
1112
1266
|
return yield* readMount(olds);
|
|
1113
1267
|
}),
|
|
1114
|
-
diff:
|
|
1268
|
+
diff: Effect10.fn(function* ({ news, output }) {
|
|
1115
1269
|
if (output === undefined || !isResolved4(news))
|
|
1116
1270
|
return;
|
|
1117
1271
|
const live = yield* readMount(news);
|
|
@@ -1123,7 +1277,7 @@ var BaoMountProvider = () => Provider4.effect(BaoMount, Effect9.succeed(BaoMount
|
|
|
1123
1277
|
return { action: "noop" };
|
|
1124
1278
|
return { action: "update" };
|
|
1125
1279
|
}),
|
|
1126
|
-
reconcile:
|
|
1280
|
+
reconcile: Effect10.fn(function* ({ news }) {
|
|
1127
1281
|
let live = yield* readMount(news);
|
|
1128
1282
|
if (live === undefined) {
|
|
1129
1283
|
yield* enableMount(news);
|
|
@@ -1131,17 +1285,17 @@ var BaoMountProvider = () => Provider4.effect(BaoMount, Effect9.succeed(BaoMount
|
|
|
1131
1285
|
yield* tuneMount(news);
|
|
1132
1286
|
} else if (!matches(live, news)) {
|
|
1133
1287
|
if (live.type !== news.type) {
|
|
1134
|
-
return yield*
|
|
1288
|
+
return yield* Effect10.die(new Error(`Bao.Mount ${mountPath(news.path)}: live type ${live.type} != ${news.type}. ` + "Secrets engine type is immutable — replace manually."));
|
|
1135
1289
|
}
|
|
1136
1290
|
yield* tuneMount(news);
|
|
1137
1291
|
}
|
|
1138
1292
|
live = yield* readMount(news);
|
|
1139
1293
|
if (live === undefined) {
|
|
1140
|
-
return yield*
|
|
1294
|
+
return yield* Effect10.die(new Error(`Bao.Mount ${mountPath(news.path)}: write returned no error but the mount is still absent.`));
|
|
1141
1295
|
}
|
|
1142
1296
|
return live;
|
|
1143
1297
|
}),
|
|
1144
|
-
delete:
|
|
1298
|
+
delete: Effect10.fn(function* ({ output }) {
|
|
1145
1299
|
yield* disableMount(output.path);
|
|
1146
1300
|
return;
|
|
1147
1301
|
})
|
|
@@ -1150,7 +1304,7 @@ var BaoMountProvider = () => Provider4.effect(BaoMount, Effect9.succeed(BaoMount
|
|
|
1150
1304
|
import { Resource as Resource5 } from "alchemy";
|
|
1151
1305
|
import { isResolved as isResolved5 } from "alchemy/Diff";
|
|
1152
1306
|
import * as Provider5 from "alchemy/Provider";
|
|
1153
|
-
import * as
|
|
1307
|
+
import * as Effect11 from "effect/Effect";
|
|
1154
1308
|
|
|
1155
1309
|
// src/openbao/pki-role-form.ts
|
|
1156
1310
|
var sorted = (values) => [...values].sort();
|
|
@@ -1268,19 +1422,19 @@ var matches4 = (attributes, props) => attributes.digest === digestOf(canonicalFr
|
|
|
1268
1422
|
var BaoPkiRole = Resource5("Bao.PkiRole", {
|
|
1269
1423
|
defaultRemovalPolicy: "retain"
|
|
1270
1424
|
});
|
|
1271
|
-
var readRole2 = (props) =>
|
|
1425
|
+
var readRole2 = (props) => Effect11.gen(function* () {
|
|
1272
1426
|
const live = yield* baoRead(rolePath2(props));
|
|
1273
1427
|
if (live === undefined)
|
|
1274
1428
|
return;
|
|
1275
1429
|
return attributesOf5(props, live);
|
|
1276
1430
|
});
|
|
1277
1431
|
var badDurations = (props) => [["ttl", props.ttl], ["maxTtl", props.maxTtl]].filter(([, text4]) => parseDuration(text4) === undefined);
|
|
1278
|
-
var BaoPkiRoleProvider = () => Provider5.effect(BaoPkiRole,
|
|
1279
|
-
list: () =>
|
|
1280
|
-
read:
|
|
1432
|
+
var BaoPkiRoleProvider = () => Provider5.effect(BaoPkiRole, Effect11.succeed(BaoPkiRole.Provider.of({
|
|
1433
|
+
list: () => Effect11.succeed([]),
|
|
1434
|
+
read: Effect11.fn(function* ({ olds }) {
|
|
1281
1435
|
return yield* readRole2(olds);
|
|
1282
1436
|
}),
|
|
1283
|
-
diff:
|
|
1437
|
+
diff: Effect11.fn(function* ({ news, output }) {
|
|
1284
1438
|
if (output === undefined || !isResolved5(news))
|
|
1285
1439
|
return;
|
|
1286
1440
|
if (mountPath(news.mount ?? "pki") !== output.mount)
|
|
@@ -1293,14 +1447,14 @@ var BaoPkiRoleProvider = () => Provider5.effect(BaoPkiRole, Effect10.succeed(Bao
|
|
|
1293
1447
|
return { action: "update" };
|
|
1294
1448
|
return matches4(live, news) ? { action: "noop" } : { action: "update" };
|
|
1295
1449
|
}),
|
|
1296
|
-
reconcile:
|
|
1450
|
+
reconcile: Effect11.fn(function* ({ news }) {
|
|
1297
1451
|
const path = rolePath2(news);
|
|
1298
1452
|
if (news.allowedDomains.length === 0) {
|
|
1299
|
-
return yield*
|
|
1453
|
+
return yield* Effect11.die(new Error(`Bao.PkiRole ${path}: allowedDomains is empty. With allow_any_name false ` + "that role can issue no certificate at all."));
|
|
1300
1454
|
}
|
|
1301
1455
|
const bad = badDurations(news);
|
|
1302
1456
|
if (bad.length > 0) {
|
|
1303
|
-
return yield*
|
|
1457
|
+
return yield* Effect11.die(new Error(`Bao.PkiRole ${path}: ${bad.map(([k, v]) => `${k}=${v}`).join(", ")} ` + "is not a duration OpenBao parses (expect 30m, 720h, 8760h or 0)."));
|
|
1304
1458
|
}
|
|
1305
1459
|
const live = yield* readRole2(news);
|
|
1306
1460
|
if (live === undefined || !matches4(live, news)) {
|
|
@@ -1308,23 +1462,147 @@ var BaoPkiRoleProvider = () => Provider5.effect(BaoPkiRole, Effect10.succeed(Bao
|
|
|
1308
1462
|
}
|
|
1309
1463
|
const after = yield* readRole2(news);
|
|
1310
1464
|
if (after === undefined) {
|
|
1311
|
-
return yield*
|
|
1465
|
+
return yield* Effect11.die(new Error(`Bao.PkiRole ${path}: write reported success but the role is absent.`));
|
|
1312
1466
|
}
|
|
1313
1467
|
if (!matches4(after, news)) {
|
|
1314
|
-
return yield*
|
|
1468
|
+
return yield* Effect11.die(new Error(`Bao.PkiRole ${path}: wrote cleanly but the role read back different. ` + "OpenBao stored something other than what was declared — inspect with " + `\`bao read ${path}\` before retrying.`));
|
|
1315
1469
|
}
|
|
1316
1470
|
return after;
|
|
1317
1471
|
}),
|
|
1318
|
-
delete:
|
|
1472
|
+
delete: Effect11.fn(function* ({ output }) {
|
|
1319
1473
|
yield* baoDelete(rolePath2(output));
|
|
1320
1474
|
return;
|
|
1321
1475
|
})
|
|
1322
1476
|
})));
|
|
1323
|
-
// src/openbao/
|
|
1477
|
+
// src/openbao/plugin.ts
|
|
1324
1478
|
import { Resource as Resource6 } from "alchemy";
|
|
1325
1479
|
import { isResolved as isResolved6 } from "alchemy/Diff";
|
|
1326
1480
|
import * as Provider6 from "alchemy/Provider";
|
|
1481
|
+
import * as Effect13 from "effect/Effect";
|
|
1482
|
+
|
|
1483
|
+
// src/openbao/plugin-form.ts
|
|
1484
|
+
var resolve = (props) => ({
|
|
1485
|
+
args: props.args ?? [],
|
|
1486
|
+
command: props.command,
|
|
1487
|
+
name: props.name,
|
|
1488
|
+
sha256: props.sha256.toLowerCase(),
|
|
1489
|
+
type: props.type,
|
|
1490
|
+
version: props.version ?? ""
|
|
1491
|
+
});
|
|
1492
|
+
var CANONICAL_VERSION = /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/;
|
|
1493
|
+
var problems = (form) => {
|
|
1494
|
+
const found = [];
|
|
1495
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(form.name) || form.name.includes("..")) {
|
|
1496
|
+
found.push(`name ${JSON.stringify(form.name)} is not a plain catalog name`);
|
|
1497
|
+
}
|
|
1498
|
+
if (!/^[0-9a-f]{64}$/.test(form.sha256))
|
|
1499
|
+
found.push("sha256 is not 64 hex characters");
|
|
1500
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._+-]*$/.test(form.command) || form.command.includes("..")) {
|
|
1501
|
+
found.push(`command ${JSON.stringify(form.command)} is not a bare file name`);
|
|
1502
|
+
}
|
|
1503
|
+
if (form.version !== "" && !CANONICAL_VERSION.test(form.version)) {
|
|
1504
|
+
found.push(`version ${JSON.stringify(form.version)} is not canonical semver like v1.2.0`);
|
|
1505
|
+
}
|
|
1506
|
+
if (/\+builtin/.test(form.version))
|
|
1507
|
+
found.push("version metadata `builtin` is reserved");
|
|
1508
|
+
return found;
|
|
1509
|
+
};
|
|
1510
|
+
var versionQuery = (version) => version === "" ? "" : `?version=${encodeURIComponent(version)}`;
|
|
1511
|
+
var catalogPath = (type, name) => `sys/plugins/catalog/${type}/${name}`;
|
|
1512
|
+
var versionedPath = (type, name, version) => `${catalogPath(type, name)}${versionQuery(version)}`;
|
|
1513
|
+
var registerBody = (form) => ({
|
|
1514
|
+
command: form.command,
|
|
1515
|
+
sha256: form.sha256,
|
|
1516
|
+
...form.args.length > 0 ? { args: form.args } : {},
|
|
1517
|
+
...form.version === "" ? {} : { version: form.version }
|
|
1518
|
+
});
|
|
1519
|
+
var text4 = (value) => typeof value === "string" ? value : "";
|
|
1520
|
+
var list2 = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
1521
|
+
var attributesOf6 = (form, live) => {
|
|
1522
|
+
const attrs = {
|
|
1523
|
+
name: form.name,
|
|
1524
|
+
type: form.type,
|
|
1525
|
+
version: text4(live["version"]),
|
|
1526
|
+
sha256: text4(live["sha256"]).toLowerCase(),
|
|
1527
|
+
command: text4(live["command"]),
|
|
1528
|
+
args: list2(live["args"]),
|
|
1529
|
+
builtin: live["builtin"] === true
|
|
1530
|
+
};
|
|
1531
|
+
return { ...attrs, digest: sha256(JSON.stringify(attrs)) };
|
|
1532
|
+
};
|
|
1533
|
+
var sameArgs = (want, have) => want.length === have.length && want.every((entry, index) => entry === have[index]);
|
|
1534
|
+
var matches5 = (attributes, form) => !attributes.builtin && attributes.version === form.version && attributes.sha256 === form.sha256 && attributes.command === form.command && sameArgs(form.args, attributes.args);
|
|
1535
|
+
|
|
1536
|
+
// src/openbao/plugin-reconcile.ts
|
|
1327
1537
|
import * as Effect12 from "effect/Effect";
|
|
1538
|
+
|
|
1539
|
+
// src/openbao/plugin-wire.ts
|
|
1540
|
+
var readPluginData = (form) => baoRead(versionedPath(form.type, form.name, form.version));
|
|
1541
|
+
var registerPlugin = (form) => baoWrite("PUT", catalogPath(form.type, form.name), registerBody(form));
|
|
1542
|
+
var deregisterPlugin = (type, name, version) => baoDelete(versionedPath(type, name, version));
|
|
1543
|
+
|
|
1544
|
+
// src/openbao/plugin-reconcile.ts
|
|
1545
|
+
var readEntry = (form) => Effect12.map(readPluginData(form), (live) => live === undefined ? undefined : { attributes: attributesOf6(form, live), live });
|
|
1546
|
+
var refuse = (form, message) => Effect12.die(new Error(`Bao.Plugin ${versionedPath(form.type, form.name, form.version)}: ${message}`));
|
|
1547
|
+
var SELF_REPORTED = "the write succeeded but the read-back does not match. If the binary reports its own version, " + "OpenBao filed the registration under it (plugin_catalog.go setInternal) — declare `version` " + "as exactly the version the binary reports; that adopts the entry this write just made.";
|
|
1548
|
+
var reconcilePlugin = (props) => Effect12.gen(function* () {
|
|
1549
|
+
const form = resolve(props);
|
|
1550
|
+
const bad = problems(form);
|
|
1551
|
+
if (bad.length > 0)
|
|
1552
|
+
return yield* refuse(form, bad.join("; "));
|
|
1553
|
+
const found = yield* readEntry(form);
|
|
1554
|
+
if (found !== undefined && !matches5(found.attributes, form)) {
|
|
1555
|
+
if (found.live["declarative"] === true || found.live["oci"] === true) {
|
|
1556
|
+
return yield* refuse(form, "the live entry is declarative/OCI; manage it in server config.");
|
|
1557
|
+
}
|
|
1558
|
+
if (found.attributes.builtin) {
|
|
1559
|
+
return yield* refuse(form, "this name is a builtin; an unversioned write would shadow it.");
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
if (found === undefined || !matches5(found.attributes, form))
|
|
1563
|
+
yield* registerPlugin(form);
|
|
1564
|
+
const after = yield* readEntry(form);
|
|
1565
|
+
if (after === undefined || !matches5(after.attributes, form)) {
|
|
1566
|
+
return yield* refuse(form, SELF_REPORTED);
|
|
1567
|
+
}
|
|
1568
|
+
return after.attributes;
|
|
1569
|
+
});
|
|
1570
|
+
|
|
1571
|
+
// src/openbao/plugin.ts
|
|
1572
|
+
var BaoPlugin = Resource6("Bao.Plugin", {
|
|
1573
|
+
defaultRemovalPolicy: "retain"
|
|
1574
|
+
});
|
|
1575
|
+
var BaoPluginProvider = () => Provider6.effect(BaoPlugin, Effect13.succeed(BaoPlugin.Provider.of({
|
|
1576
|
+
list: () => Effect13.succeed([]),
|
|
1577
|
+
read: Effect13.fn(function* ({ olds }) {
|
|
1578
|
+
const found = yield* readEntry(resolve(olds));
|
|
1579
|
+
return found?.attributes;
|
|
1580
|
+
}),
|
|
1581
|
+
diff: Effect13.fn(function* ({ news, output }) {
|
|
1582
|
+
if (output === undefined || !isResolved6(news))
|
|
1583
|
+
return;
|
|
1584
|
+
const form = resolve(news);
|
|
1585
|
+
if (output.name !== form.name || output.type !== form.type || output.version !== form.version) {
|
|
1586
|
+
return { action: "replace" };
|
|
1587
|
+
}
|
|
1588
|
+
const found = yield* readEntry(form);
|
|
1589
|
+
if (found === undefined)
|
|
1590
|
+
return { action: "update" };
|
|
1591
|
+
return matches5(found.attributes, form) ? { action: "noop" } : { action: "update" };
|
|
1592
|
+
}),
|
|
1593
|
+
reconcile: Effect13.fn(function* ({ news }) {
|
|
1594
|
+
return yield* reconcilePlugin(news);
|
|
1595
|
+
}),
|
|
1596
|
+
delete: Effect13.fn(function* ({ output }) {
|
|
1597
|
+
yield* deregisterPlugin(output.type, output.name, output.version);
|
|
1598
|
+
return;
|
|
1599
|
+
})
|
|
1600
|
+
})));
|
|
1601
|
+
// src/openbao/policy.ts
|
|
1602
|
+
import { Resource as Resource7 } from "alchemy";
|
|
1603
|
+
import { isResolved as isResolved7 } from "alchemy/Diff";
|
|
1604
|
+
import * as Provider7 from "alchemy/Provider";
|
|
1605
|
+
import * as Effect15 from "effect/Effect";
|
|
1328
1606
|
import * as FileSystem from "effect/FileSystem";
|
|
1329
1607
|
import * as Path from "effect/Path";
|
|
1330
1608
|
|
|
@@ -1332,32 +1610,32 @@ import * as Path from "effect/Path";
|
|
|
1332
1610
|
var isEmptyAssembly = (parts, joined) => parts === 0 || !/^\s*path\s+"/m.test(joined);
|
|
1333
1611
|
|
|
1334
1612
|
// src/openbao/policy-wire.ts
|
|
1335
|
-
import * as
|
|
1613
|
+
import * as Effect14 from "effect/Effect";
|
|
1336
1614
|
var policyPath = (name) => `sys/policies/acl/${name}`;
|
|
1337
|
-
var readPolicy = (name) =>
|
|
1615
|
+
var readPolicy = (name) => Effect14.flatMap(baoRead(policyPath(name)), (data) => {
|
|
1338
1616
|
if (data === undefined)
|
|
1339
|
-
return
|
|
1340
|
-
const
|
|
1341
|
-
return typeof
|
|
1617
|
+
return Effect14.succeed("");
|
|
1618
|
+
const text5 = data["policy"];
|
|
1619
|
+
return typeof text5 === "string" ? Effect14.succeed(text5) : Effect14.fail(new BaoError(200, "GET", policyPath(name), ["no data.policy in the response"]));
|
|
1342
1620
|
});
|
|
1343
1621
|
var writePolicy = (name, hcl) => baoWrite("PUT", policyPath(name), { policy: hcl });
|
|
1344
1622
|
var deletePolicy = (name) => baoDelete(policyPath(name));
|
|
1345
1623
|
|
|
1346
1624
|
// src/openbao/policy.ts
|
|
1347
|
-
var BaoPolicy =
|
|
1625
|
+
var BaoPolicy = Resource7("Bao.Policy", {
|
|
1348
1626
|
defaultRemovalPolicy: "retain"
|
|
1349
1627
|
});
|
|
1350
1628
|
var grantsOf = (hcl) => (hcl.match(/^path /gm) ?? []).length;
|
|
1351
|
-
var
|
|
1629
|
+
var attributesOf7 = (name, hcl, parts) => ({
|
|
1352
1630
|
name,
|
|
1353
1631
|
digest: sha256(hcl),
|
|
1354
1632
|
grants: grantsOf(hcl),
|
|
1355
1633
|
parts
|
|
1356
1634
|
});
|
|
1357
|
-
var BaoPolicyProvider = () =>
|
|
1635
|
+
var BaoPolicyProvider = () => Provider7.effect(BaoPolicy, Effect15.gen(function* () {
|
|
1358
1636
|
const fs = yield* FileSystem.FileSystem;
|
|
1359
1637
|
const path = yield* Path.Path;
|
|
1360
|
-
const assemble = (dir) =>
|
|
1638
|
+
const assemble = (dir) => Effect15.gen(function* () {
|
|
1361
1639
|
const entries2 = (yield* fs.readDirectory(dir)).filter((f) => f.endsWith(".hcl")).sort();
|
|
1362
1640
|
let joined = "";
|
|
1363
1641
|
for (const f of entries2)
|
|
@@ -1366,44 +1644,44 @@ var BaoPolicyProvider = () => Provider6.effect(BaoPolicy, Effect12.gen(function*
|
|
|
1366
1644
|
return { joined, parts: entries2.length };
|
|
1367
1645
|
});
|
|
1368
1646
|
return BaoPolicy.Provider.of({
|
|
1369
|
-
list: () =>
|
|
1370
|
-
read:
|
|
1647
|
+
list: () => Effect15.succeed([]),
|
|
1648
|
+
read: Effect15.fn(function* ({ olds }) {
|
|
1371
1649
|
const live = yield* readPolicy(olds.name);
|
|
1372
|
-
return live.length === 0 ? undefined :
|
|
1650
|
+
return live.length === 0 ? undefined : attributesOf7(olds.name, live, 0);
|
|
1373
1651
|
}),
|
|
1374
|
-
diff:
|
|
1375
|
-
if (output === undefined || !
|
|
1652
|
+
diff: Effect15.fn(function* ({ news, output }) {
|
|
1653
|
+
if (output === undefined || !isResolved7(news))
|
|
1376
1654
|
return;
|
|
1377
1655
|
const { joined } = yield* assemble(news.fragments);
|
|
1378
1656
|
const live = yield* readPolicy(news.name);
|
|
1379
1657
|
return live.length > 0 && sha256(live) === sha256(joined) ? { action: "noop" } : { action: "update" };
|
|
1380
1658
|
}),
|
|
1381
|
-
reconcile:
|
|
1659
|
+
reconcile: Effect15.fn(function* ({ news }) {
|
|
1382
1660
|
const name = news.name;
|
|
1383
1661
|
const { joined, parts } = yield* assemble(news.fragments);
|
|
1384
1662
|
if (isEmptyAssembly(parts, joined))
|
|
1385
|
-
return yield*
|
|
1663
|
+
return yield* Effect15.die(new Error(`Bao.Policy ${name}: the fragments under ${news.fragments} carry no path grant ` + `(${String(parts)} file(s)). Writing it would revoke every grant the policy carries.`));
|
|
1386
1664
|
const current = yield* readPolicy(name);
|
|
1387
1665
|
if (current.length > 0 && sha256(current) === sha256(joined)) {
|
|
1388
|
-
return
|
|
1666
|
+
return attributesOf7(name, joined, parts);
|
|
1389
1667
|
}
|
|
1390
1668
|
yield* writePolicy(name, joined);
|
|
1391
|
-
return
|
|
1669
|
+
return attributesOf7(name, joined, parts);
|
|
1392
1670
|
}),
|
|
1393
|
-
delete:
|
|
1671
|
+
delete: Effect15.fn(function* ({ output }) {
|
|
1394
1672
|
yield* deletePolicy(output.name);
|
|
1395
1673
|
return;
|
|
1396
1674
|
})
|
|
1397
1675
|
});
|
|
1398
1676
|
}));
|
|
1399
1677
|
// src/openbao/proxmox-role.ts
|
|
1400
|
-
import { Resource as
|
|
1401
|
-
import { isResolved as
|
|
1402
|
-
import * as
|
|
1403
|
-
import * as
|
|
1678
|
+
import { Resource as Resource8 } from "alchemy";
|
|
1679
|
+
import { isResolved as isResolved8 } from "alchemy/Diff";
|
|
1680
|
+
import * as Provider8 from "alchemy/Provider";
|
|
1681
|
+
import * as Effect16 from "effect/Effect";
|
|
1404
1682
|
|
|
1405
1683
|
// src/openbao/proxmox-role-form.ts
|
|
1406
|
-
var
|
|
1684
|
+
var text5 = (value) => typeof value === "string" ? value : "";
|
|
1407
1685
|
var ttlText3 = (seconds3, fallback = "0") => {
|
|
1408
1686
|
if (seconds3 === undefined)
|
|
1409
1687
|
return fallback;
|
|
@@ -1416,11 +1694,11 @@ var ttlText3 = (seconds3, fallback = "0") => {
|
|
|
1416
1694
|
return `${String(seconds3)}s`;
|
|
1417
1695
|
};
|
|
1418
1696
|
var rolePath3 = (mount, name) => `${mountPath(mount)}/roles/${name}`;
|
|
1419
|
-
var
|
|
1697
|
+
var attributesOf8 = (props, live) => {
|
|
1420
1698
|
const attrs = {
|
|
1421
1699
|
mount: mountPath(props.mount),
|
|
1422
|
-
name:
|
|
1423
|
-
mintUser:
|
|
1700
|
+
name: text5(live["name"]) || props.name,
|
|
1701
|
+
mintUser: text5(live["mint_user"]),
|
|
1424
1702
|
ttl: ttlText3(ttlSeconds(live["ttl"])),
|
|
1425
1703
|
maxTtl: ttlText3(ttlSeconds(live["max_ttl"]))
|
|
1426
1704
|
};
|
|
@@ -1446,69 +1724,68 @@ var sameTtl5 = (want, have) => {
|
|
|
1446
1724
|
const haveSeconds = ttlSeconds(have);
|
|
1447
1725
|
return wantSeconds !== undefined && wantSeconds === haveSeconds;
|
|
1448
1726
|
};
|
|
1449
|
-
var
|
|
1727
|
+
var matches6 = (attributes, props) => attributes.mintUser === props.mintUser && sameTtl5(props.ttl, attributes.ttl) && sameTtl5(props.maxTtl, attributes.maxTtl);
|
|
1450
1728
|
|
|
1451
1729
|
// src/openbao/proxmox-role.ts
|
|
1452
|
-
var BaoProxmoxRole =
|
|
1730
|
+
var BaoProxmoxRole = Resource8("Bao.ProxmoxRole", {
|
|
1453
1731
|
defaultRemovalPolicy: "retain"
|
|
1454
1732
|
});
|
|
1455
|
-
var readRole3 = (props) =>
|
|
1733
|
+
var readRole3 = (props) => Effect16.gen(function* () {
|
|
1456
1734
|
const live = yield* baoRead(rolePath3(props.mount, props.name));
|
|
1457
1735
|
if (live === undefined)
|
|
1458
1736
|
return;
|
|
1459
|
-
return
|
|
1737
|
+
return attributesOf8(props, live);
|
|
1460
1738
|
});
|
|
1461
|
-
var BaoProxmoxRoleProvider = () =>
|
|
1462
|
-
list: () =>
|
|
1463
|
-
read:
|
|
1739
|
+
var BaoProxmoxRoleProvider = () => Provider8.effect(BaoProxmoxRole, Effect16.succeed(BaoProxmoxRole.Provider.of({
|
|
1740
|
+
list: () => Effect16.succeed([]),
|
|
1741
|
+
read: Effect16.fn(function* ({ olds }) {
|
|
1464
1742
|
return yield* readRole3(olds);
|
|
1465
1743
|
}),
|
|
1466
|
-
diff:
|
|
1467
|
-
if (output === undefined || !
|
|
1744
|
+
diff: Effect16.fn(function* ({ news, output }) {
|
|
1745
|
+
if (output === undefined || !isResolved8(news))
|
|
1468
1746
|
return;
|
|
1469
1747
|
const live = yield* readRole3(news);
|
|
1470
1748
|
if (live === undefined)
|
|
1471
1749
|
return { action: "update" };
|
|
1472
|
-
if (
|
|
1750
|
+
if (matches6(live, news))
|
|
1473
1751
|
return { action: "noop" };
|
|
1474
1752
|
return { action: "update" };
|
|
1475
1753
|
}),
|
|
1476
|
-
reconcile:
|
|
1754
|
+
reconcile: Effect16.fn(function* ({ news }) {
|
|
1477
1755
|
const live = yield* readRole3(news);
|
|
1478
1756
|
if (live !== undefined && live.mintUser !== news.mintUser && news.allowMintUserChange !== live.mintUser) {
|
|
1479
|
-
return yield*
|
|
1757
|
+
return yield* Effect16.die(new Error(`Bao.ProxmoxRole ${rolePath3(news.mount, news.name)}: live mint_user ` + `${live.mintUser} != declared ${news.mintUser}. Every token this role mints ` + "inherits the ACL of that PVE user. To re-scope deliberately, declare " + `allowMintUserChange: '${live.mintUser}'.`));
|
|
1480
1758
|
}
|
|
1481
|
-
if (live === undefined || !
|
|
1759
|
+
if (live === undefined || !matches6(live, news)) {
|
|
1482
1760
|
const write = writeBody4(news);
|
|
1483
1761
|
if (!write.ok) {
|
|
1484
|
-
return yield*
|
|
1762
|
+
return yield* Effect16.die(new Error(`Bao.ProxmoxRole ${rolePath3(news.mount, news.name)}: unparseable duration ` + `${write.bad.join(", ")}. Use 30s / 5m / 1h / 1d, or 0.`));
|
|
1485
1763
|
}
|
|
1486
1764
|
yield* baoWrite("PUT", rolePath3(news.mount, news.name), write.body);
|
|
1487
1765
|
}
|
|
1488
1766
|
const after = yield* readRole3(news);
|
|
1489
1767
|
if (after === undefined) {
|
|
1490
|
-
return yield*
|
|
1768
|
+
return yield* Effect16.die(new Error(`Bao.ProxmoxRole ${rolePath3(news.mount, news.name)}: write returned no error ` + "but the role is still absent."));
|
|
1491
1769
|
}
|
|
1492
1770
|
return after;
|
|
1493
1771
|
}),
|
|
1494
|
-
delete:
|
|
1772
|
+
delete: Effect16.fn(function* ({ output }) {
|
|
1495
1773
|
yield* baoDelete(rolePath3(output.mount, output.name));
|
|
1496
1774
|
return;
|
|
1497
1775
|
})
|
|
1498
1776
|
})));
|
|
1499
1777
|
// src/openbao/ssh-role.ts
|
|
1500
|
-
import { Resource as
|
|
1501
|
-
import { isResolved as
|
|
1502
|
-
import * as
|
|
1503
|
-
import * as
|
|
1778
|
+
import { Resource as Resource9 } from "alchemy";
|
|
1779
|
+
import { isResolved as isResolved9 } from "alchemy/Diff";
|
|
1780
|
+
import * as Provider9 from "alchemy/Provider";
|
|
1781
|
+
import * as Effect17 from "effect/Effect";
|
|
1504
1782
|
|
|
1505
1783
|
// src/openbao/ssh-role-form.ts
|
|
1506
|
-
var
|
|
1507
|
-
var
|
|
1508
|
-
var text5 = (value) => typeof value === "string" ? value : "";
|
|
1784
|
+
var readPath4 = (mount, name) => `${sshMountOf(mount)}/roles/${name}`;
|
|
1785
|
+
var text6 = (value) => typeof value === "string" ? value : "";
|
|
1509
1786
|
var bool3 = (value, fallback) => typeof value === "boolean" ? value : fallback;
|
|
1510
1787
|
var csvList = (value) => {
|
|
1511
|
-
const parts = Array.isArray(value) ? value.filter((entry) => typeof entry === "string") :
|
|
1788
|
+
const parts = Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : text6(value).split(",");
|
|
1512
1789
|
return [
|
|
1513
1790
|
...new Set(parts.map((entry) => entry.trim()).filter((entry) => entry.length > 0))
|
|
1514
1791
|
].sort();
|
|
@@ -1519,12 +1796,12 @@ var extMap = (value) => {
|
|
|
1519
1796
|
return out;
|
|
1520
1797
|
const source = value;
|
|
1521
1798
|
for (const key of Object.keys(source).sort())
|
|
1522
|
-
out[key] =
|
|
1799
|
+
out[key] = text6(source[key]);
|
|
1523
1800
|
return out;
|
|
1524
1801
|
};
|
|
1525
|
-
var
|
|
1802
|
+
var resolve2 = (props) => ({
|
|
1526
1803
|
name: props.name,
|
|
1527
|
-
mount:
|
|
1804
|
+
mount: sshMountOf(props.mount),
|
|
1528
1805
|
allowedUsers: csvList(props.allowedUsers),
|
|
1529
1806
|
defaultUser: props.defaultUser ?? "",
|
|
1530
1807
|
allowUserCertificates: props.allowUserCertificates ?? false,
|
|
@@ -1540,12 +1817,12 @@ var resolve = (props) => ({
|
|
|
1540
1817
|
ttl: props.ttl,
|
|
1541
1818
|
maxTtl: props.maxTtl
|
|
1542
1819
|
});
|
|
1543
|
-
var
|
|
1820
|
+
var attributesOf9 = (form, live) => {
|
|
1544
1821
|
const attrs = {
|
|
1545
1822
|
name: form.name,
|
|
1546
1823
|
mount: form.mount,
|
|
1547
1824
|
allowedUsers: csvList(live["allowed_users"]),
|
|
1548
|
-
defaultUser:
|
|
1825
|
+
defaultUser: text6(live["default_user"]),
|
|
1549
1826
|
allowUserCertificates: bool3(live["allow_user_certificates"], false),
|
|
1550
1827
|
allowHostCertificates: bool3(live["allow_host_certificates"], false),
|
|
1551
1828
|
allowedDomains: csvList(live["allowed_domains"]),
|
|
@@ -1556,13 +1833,13 @@ var attributesOf8 = (form, live) => {
|
|
|
1556
1833
|
allowedCriticalOptions: csvList(live["allowed_critical_options"]),
|
|
1557
1834
|
defaultCriticalOptions: extMap(live["default_critical_options"]),
|
|
1558
1835
|
allowEmptyPrincipals: bool3(live["allow_empty_principals"], false),
|
|
1559
|
-
keyType:
|
|
1836
|
+
keyType: text6(live["key_type"]) || "ca",
|
|
1560
1837
|
ttlSeconds: ttlSeconds(live["ttl"]) ?? 0,
|
|
1561
1838
|
maxTtlSeconds: ttlSeconds(live["max_ttl"]) ?? 0
|
|
1562
1839
|
};
|
|
1563
1840
|
return { ...attrs, digest: sha256(JSON.stringify(attrs)) };
|
|
1564
1841
|
};
|
|
1565
|
-
var mapValue = (
|
|
1842
|
+
var mapValue = (map5) => extMap(map5);
|
|
1566
1843
|
var writeBody5 = (form) => ({
|
|
1567
1844
|
key_type: "ca",
|
|
1568
1845
|
allowed_users: form.allowedUsers.join(","),
|
|
@@ -1587,17 +1864,17 @@ var sameMap = (want, have) => {
|
|
|
1587
1864
|
};
|
|
1588
1865
|
var sameTtl6 = (want, have) => parseDuration(want) === have;
|
|
1589
1866
|
var badDurations2 = (form) => ["ttl", "maxTtl"].filter((field) => parseDuration(form[field]) === undefined);
|
|
1590
|
-
var
|
|
1867
|
+
var matches7 = (attributes, form) => attributes.keyType === "ca" && sameList2(form.allowedUsers, attributes.allowedUsers) && attributes.defaultUser === form.defaultUser && attributes.allowUserCertificates === form.allowUserCertificates && attributes.allowHostCertificates === form.allowHostCertificates && sameList2(form.allowedDomains, attributes.allowedDomains) && attributes.allowSubdomains === form.allowSubdomains && attributes.allowBareDomains === form.allowBareDomains && sameList2(form.allowedExtensions, attributes.allowedExtensions) && sameMap(form.defaultExtensions, attributes.defaultExtensions) && sameList2(form.allowedCriticalOptions, attributes.allowedCriticalOptions) && sameMap(form.defaultCriticalOptions, attributes.defaultCriticalOptions) && attributes.allowEmptyPrincipals === form.allowEmptyPrincipals && sameTtl6(form.ttl, attributes.ttlSeconds) && sameTtl6(form.maxTtl, attributes.maxTtlSeconds);
|
|
1591
1868
|
|
|
1592
1869
|
// src/openbao/ssh-role.ts
|
|
1593
|
-
var BaoSshRole =
|
|
1870
|
+
var BaoSshRole = Resource9("Bao.SshRole", {
|
|
1594
1871
|
defaultRemovalPolicy: "retain"
|
|
1595
1872
|
});
|
|
1596
|
-
var readRole4 = (form) =>
|
|
1873
|
+
var readRole4 = (form) => Effect17.gen(function* () {
|
|
1597
1874
|
const live = yield* baoRead(readPath4(form.mount, form.name));
|
|
1598
1875
|
if (live === undefined)
|
|
1599
1876
|
return;
|
|
1600
|
-
return { attributes:
|
|
1877
|
+
return { attributes: attributesOf9(form, live), live };
|
|
1601
1878
|
});
|
|
1602
1879
|
var UNMANAGED_DEFAULTS = {
|
|
1603
1880
|
algorithm_signer: "default",
|
|
@@ -1619,52 +1896,52 @@ var wouldErase = (live) => {
|
|
|
1619
1896
|
}
|
|
1620
1897
|
return lost.sort();
|
|
1621
1898
|
};
|
|
1622
|
-
var
|
|
1623
|
-
var BaoSshRoleProvider = () =>
|
|
1624
|
-
list: () =>
|
|
1625
|
-
read:
|
|
1626
|
-
const found = yield* readRole4(
|
|
1899
|
+
var refuse2 = (message) => Effect17.die(new Error(message));
|
|
1900
|
+
var BaoSshRoleProvider = () => Provider9.effect(BaoSshRole, Effect17.succeed(BaoSshRole.Provider.of({
|
|
1901
|
+
list: () => Effect17.succeed([]),
|
|
1902
|
+
read: Effect17.fn(function* ({ olds }) {
|
|
1903
|
+
const found = yield* readRole4(resolve2(olds));
|
|
1627
1904
|
return found?.attributes;
|
|
1628
1905
|
}),
|
|
1629
|
-
diff:
|
|
1630
|
-
if (output === undefined || !
|
|
1906
|
+
diff: Effect17.fn(function* ({ news, output }) {
|
|
1907
|
+
if (output === undefined || !isResolved9(news))
|
|
1631
1908
|
return;
|
|
1632
|
-
const form =
|
|
1909
|
+
const form = resolve2(news);
|
|
1633
1910
|
if (output.mount !== form.mount || output.name !== form.name) {
|
|
1634
1911
|
return { action: "replace" };
|
|
1635
1912
|
}
|
|
1636
1913
|
const found = yield* readRole4(form);
|
|
1637
1914
|
if (found === undefined)
|
|
1638
1915
|
return { action: "update" };
|
|
1639
|
-
return
|
|
1916
|
+
return matches7(found.attributes, form) ? { action: "noop" } : { action: "update" };
|
|
1640
1917
|
}),
|
|
1641
|
-
reconcile:
|
|
1642
|
-
const form =
|
|
1918
|
+
reconcile: Effect17.fn(function* ({ news }) {
|
|
1919
|
+
const form = resolve2(news);
|
|
1643
1920
|
const path = readPath4(form.mount, form.name);
|
|
1644
1921
|
const bad = badDurations2(form);
|
|
1645
1922
|
if (bad.length > 0) {
|
|
1646
|
-
return yield*
|
|
1923
|
+
return yield* refuse2(`Bao.SshRole ${path}: ${bad.join(", ")} is not a duration parseDuration understands ` + "(`0` or `<n>s|m|h|d`). It would write, then report update on every plan forever.");
|
|
1647
1924
|
}
|
|
1648
1925
|
if (form.allowUserCertificates && form.allowedUsers.length === 0 && !form.allowEmptyPrincipals) {
|
|
1649
|
-
return yield*
|
|
1926
|
+
return yield* refuse2(`Bao.SshRole ${path}: allowUserCertificates with an empty allowedUsers and ` + "allowEmptyPrincipals false. Every sign request against it would be rejected.");
|
|
1650
1927
|
}
|
|
1651
1928
|
const found = yield* readRole4(form);
|
|
1652
|
-
if (found === undefined || !
|
|
1929
|
+
if (found === undefined || !matches7(found.attributes, form)) {
|
|
1653
1930
|
if (found !== undefined) {
|
|
1654
1931
|
const lost = wouldErase(found.live);
|
|
1655
1932
|
if (lost.length > 0) {
|
|
1656
|
-
return yield*
|
|
1933
|
+
return yield* refuse2(`Bao.SshRole ${path}: live role carries ${lost.join(", ")}, which this ` + "resource does not declare and the full-replace write would ERASE. " + "Fold those settings into the declaration, or clear them by hand first.");
|
|
1657
1934
|
}
|
|
1658
1935
|
}
|
|
1659
1936
|
yield* baoWrite("PUT", path, writeBody5(form));
|
|
1660
1937
|
}
|
|
1661
1938
|
const after = yield* readRole4(form);
|
|
1662
1939
|
if (after === undefined) {
|
|
1663
|
-
return yield*
|
|
1940
|
+
return yield* refuse2(`Bao.SshRole ${path}: write returned no error but the role is still absent.`);
|
|
1664
1941
|
}
|
|
1665
1942
|
return after.attributes;
|
|
1666
1943
|
}),
|
|
1667
|
-
delete:
|
|
1944
|
+
delete: Effect17.fn(function* ({ output }) {
|
|
1668
1945
|
yield* baoDelete(readPath4(output.mount, output.name));
|
|
1669
1946
|
return;
|
|
1670
1947
|
})
|
|
@@ -1676,10 +1953,13 @@ export {
|
|
|
1676
1953
|
BaoAuthRoleProvider,
|
|
1677
1954
|
BaoCloudflareRole,
|
|
1678
1955
|
BaoCloudflareRoleProvider,
|
|
1956
|
+
BaoLoginError,
|
|
1679
1957
|
BaoMount,
|
|
1680
1958
|
BaoMountProvider,
|
|
1681
1959
|
BaoPkiRole,
|
|
1682
1960
|
BaoPkiRoleProvider,
|
|
1961
|
+
BaoPlugin,
|
|
1962
|
+
BaoPluginProvider,
|
|
1683
1963
|
BaoPolicy,
|
|
1684
1964
|
BaoPolicyProvider,
|
|
1685
1965
|
BaoProxmoxRole,
|
|
@@ -1687,13 +1967,17 @@ export {
|
|
|
1687
1967
|
BaoSshRole,
|
|
1688
1968
|
BaoSshRoleProvider,
|
|
1689
1969
|
CloudflarePermissionGroups,
|
|
1970
|
+
appRoleLogin,
|
|
1971
|
+
appRoleLoginEffect,
|
|
1690
1972
|
expandAccount,
|
|
1691
1973
|
expandAll,
|
|
1692
1974
|
mountName,
|
|
1693
1975
|
parseRolesConfig,
|
|
1694
1976
|
permissionGroupsFromEngine,
|
|
1695
|
-
permissionGroupsFromLiveRoles
|
|
1977
|
+
permissionGroupsFromLiveRoles,
|
|
1978
|
+
revokeSelf,
|
|
1979
|
+
revokeSelfEffect
|
|
1696
1980
|
};
|
|
1697
1981
|
|
|
1698
|
-
//# debugId=
|
|
1982
|
+
//# debugId=1C57EE110DEE3DA764756E2164756E21
|
|
1699
1983
|
//# sourceMappingURL=index.js.map
|