@homeflare/alchemy 0.3.2 → 0.4.1
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/dist/openbao/auth-method-form.d.ts +43 -0
- package/dist/openbao/auth-method-form.d.ts.map +1 -0
- package/dist/openbao/auth-method-wire.d.ts +21 -0
- package/dist/openbao/auth-method-wire.d.ts.map +1 -0
- package/dist/openbao/auth-method.d.ts +20 -0
- package/dist/openbao/auth-method.d.ts.map +1 -0
- package/dist/openbao/index.d.ts +1 -0
- package/dist/openbao/index.d.ts.map +1 -1
- package/dist/openbao/index.js +367 -242
- package/dist/openbao/index.js.map +8 -5
- 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/auth-method-form.test.ts +62 -0
- package/src/openbao/auth-method-form.ts +112 -0
- package/src/openbao/auth-method-wire.test.ts +127 -0
- package/src/openbao/auth-method-wire.ts +66 -0
- package/src/openbao/auth-method.ts +119 -0
- package/src/openbao/index.ts +1 -0
- package/src/proxmox/index.test.ts +53 -0
- package/src/proxmox/index.ts +26 -20
package/dist/openbao/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// src/openbao/auth-
|
|
1
|
+
// src/openbao/auth-method.ts
|
|
2
2
|
import { Resource } from "alchemy";
|
|
3
3
|
import { isResolved } from "alchemy/Diff";
|
|
4
4
|
import * as Provider from "alchemy/Provider";
|
|
5
|
-
import * as
|
|
5
|
+
import * as Effect3 from "effect/Effect";
|
|
6
6
|
|
|
7
7
|
// src/openbao/digest.ts
|
|
8
8
|
var canonical = (text) => text.trimEnd();
|
|
@@ -88,16 +88,11 @@ var sameTtl = (want, have) => {
|
|
|
88
88
|
var matches = (attributes, props) => attributes.type === props.type && (props.description === undefined || attributes.description === props.description) && sameTtl(props.defaultLeaseTtl, attributes.defaultLeaseTtl) && sameTtl(props.maxLeaseTtl, attributes.maxLeaseTtl);
|
|
89
89
|
var wantsTune = (props) => props.description !== undefined || props.defaultLeaseTtl !== undefined || props.maxLeaseTtl !== undefined;
|
|
90
90
|
|
|
91
|
-
// src/openbao/auth-
|
|
92
|
-
var
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return raw.filter((entry) => typeof entry === "string").slice().sort();
|
|
97
|
-
};
|
|
98
|
-
var bool = (value, fallback) => typeof value === "boolean" ? value : fallback;
|
|
99
|
-
var int = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
100
|
-
var ttlText = (seconds, fallback = "") => {
|
|
91
|
+
// src/openbao/auth-method-form.ts
|
|
92
|
+
var authPath = (path) => mountPath(path);
|
|
93
|
+
var configOf2 = (live) => typeof live["config"] === "object" && live["config"] !== null ? live["config"] : {};
|
|
94
|
+
var text2 = (value) => typeof value === "string" ? value : "";
|
|
95
|
+
var ttlProp2 = (seconds, fallback = "") => {
|
|
101
96
|
if (seconds === undefined)
|
|
102
97
|
return fallback;
|
|
103
98
|
if (seconds % 86400 === 0)
|
|
@@ -109,42 +104,46 @@ var ttlText = (seconds, fallback = "") => {
|
|
|
109
104
|
return `${String(seconds)}s`;
|
|
110
105
|
};
|
|
111
106
|
var attributesOf2 = (props, live) => {
|
|
107
|
+
const config = configOf2(live);
|
|
112
108
|
const attrs = {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
bindSecretId: bool(live["bind_secret_id"], true),
|
|
119
|
-
secretIdNumUses: int(live["secret_id_num_uses"], 0)
|
|
109
|
+
path: authPath(props.path),
|
|
110
|
+
type: text2(live["type"]) || props.type,
|
|
111
|
+
description: text2(live["description"]),
|
|
112
|
+
defaultLeaseTtl: ttlProp2(ttlSeconds(config["default_lease_ttl"])),
|
|
113
|
+
maxLeaseTtl: ttlProp2(ttlSeconds(config["max_lease_ttl"]))
|
|
120
114
|
};
|
|
121
115
|
return { ...attrs, digest: sha256(JSON.stringify(attrs)) };
|
|
122
116
|
};
|
|
123
|
-
var readPath2 = (
|
|
124
|
-
var
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (props.
|
|
134
|
-
body["
|
|
135
|
-
|
|
117
|
+
var readPath2 = (path) => `sys/auth/${authPath(path)}`;
|
|
118
|
+
var tunePath2 = (path) => `${readPath2(path)}/tune`;
|
|
119
|
+
var enableBody2 = (props) => {
|
|
120
|
+
const body = { type: props.type };
|
|
121
|
+
if (props.description !== undefined)
|
|
122
|
+
body["description"] = props.description;
|
|
123
|
+
return body;
|
|
124
|
+
};
|
|
125
|
+
var tuneBody2 = (props) => {
|
|
126
|
+
const body = {};
|
|
127
|
+
if (props.description !== undefined)
|
|
128
|
+
body["description"] = props.description;
|
|
129
|
+
if (props.defaultLeaseTtl !== undefined)
|
|
130
|
+
body["default_lease_ttl"] = props.defaultLeaseTtl;
|
|
131
|
+
if (props.maxLeaseTtl !== undefined)
|
|
132
|
+
body["max_lease_ttl"] = props.maxLeaseTtl;
|
|
136
133
|
return body;
|
|
137
134
|
};
|
|
138
|
-
var samePolicies = (want, have) => want.length === have.length && want.every((policy, index) => policy === have[index]);
|
|
139
135
|
var sameTtl2 = (want, have) => {
|
|
140
|
-
|
|
136
|
+
if (want === undefined)
|
|
137
|
+
return true;
|
|
138
|
+
const wantSeconds = ttlSeconds(want);
|
|
141
139
|
const haveSeconds = ttlSeconds(have);
|
|
142
140
|
return wantSeconds !== undefined && wantSeconds === haveSeconds;
|
|
143
141
|
};
|
|
144
|
-
var matches2 = (attributes, props) =>
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
var matches2 = (attributes, props) => attributes.type === props.type && (props.description === undefined || attributes.description === props.description) && sameTtl2(props.defaultLeaseTtl, attributes.defaultLeaseTtl) && sameTtl2(props.maxLeaseTtl, attributes.maxLeaseTtl);
|
|
143
|
+
var wantsTune2 = (props) => props.description !== undefined || props.defaultLeaseTtl !== undefined || props.maxLeaseTtl !== undefined;
|
|
144
|
+
|
|
145
|
+
// src/openbao/auth-method-wire.ts
|
|
146
|
+
import * as Effect2 from "effect/Effect";
|
|
148
147
|
|
|
149
148
|
// src/openbao/bao-http.ts
|
|
150
149
|
import * as Context from "effect/Context";
|
|
@@ -199,35 +198,35 @@ class BaoError extends Error {
|
|
|
199
198
|
this.name = "BaoError";
|
|
200
199
|
}
|
|
201
200
|
}
|
|
202
|
-
var objectOf = (
|
|
201
|
+
var objectOf = (text3) => {
|
|
203
202
|
try {
|
|
204
|
-
const parsed = JSON.parse(
|
|
203
|
+
const parsed = JSON.parse(text3);
|
|
205
204
|
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : undefined;
|
|
206
205
|
} catch {
|
|
207
206
|
return;
|
|
208
207
|
}
|
|
209
208
|
};
|
|
210
|
-
var errorsOf = (
|
|
211
|
-
const raw = objectOf(
|
|
209
|
+
var errorsOf = (text3) => {
|
|
210
|
+
const raw = objectOf(text3)?.["errors"];
|
|
212
211
|
return Array.isArray(raw) && raw.every((entry) => typeof entry === "string") ? raw : undefined;
|
|
213
212
|
};
|
|
214
|
-
var settle = (intent, method, path, status,
|
|
213
|
+
var settle = (intent, method, path, status, text3) => {
|
|
215
214
|
const fail = (errors) => ({
|
|
216
215
|
error: new BaoError(status, method, path, errors)
|
|
217
216
|
});
|
|
218
217
|
if (status < 200 || status >= 300) {
|
|
219
|
-
const errors = errorsOf(
|
|
218
|
+
const errors = errorsOf(text3);
|
|
220
219
|
if (errors === undefined) {
|
|
221
|
-
return fail([`not an OpenBao error body (${String(
|
|
220
|
+
return fail([`not an OpenBao error body (${String(text3.length)} bytes)`]);
|
|
222
221
|
}
|
|
223
222
|
if (status === 404 && intent !== "write")
|
|
224
223
|
return { body: undefined };
|
|
225
224
|
return fail(errors);
|
|
226
225
|
}
|
|
227
|
-
if (
|
|
226
|
+
if (text3.trim() === "") {
|
|
228
227
|
return intent === "read" ? fail(["a successful read with an empty body"]) : { body: undefined };
|
|
229
228
|
}
|
|
230
|
-
const body = objectOf(
|
|
229
|
+
const body = objectOf(text3);
|
|
231
230
|
return body === undefined ? fail(["a success body that is not a JSON object"]) : { body };
|
|
232
231
|
};
|
|
233
232
|
|
|
@@ -268,10 +267,10 @@ var baoCall = (intent, method, path, body) => Effect.gen(function* () {
|
|
|
268
267
|
const address = resolveAddress(env);
|
|
269
268
|
const client = yield* HttpClient.HttpClient;
|
|
270
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"));
|
|
271
|
-
const exchange = client.execute(request).pipe(Effect.flatMap((response) => Effect.map(response.text, (
|
|
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])));
|
|
272
271
|
const gate = yield* BaoGate;
|
|
273
|
-
const { status, text:
|
|
274
|
-
const outcome = settle(intent, method, path, status,
|
|
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);
|
|
275
274
|
return "error" in outcome ? yield* Effect.fail(outcome.error) : outcome.body;
|
|
276
275
|
});
|
|
277
276
|
var baoRead = (path) => Effect.map(baoCall("read", "GET", path), (body) => {
|
|
@@ -283,52 +282,176 @@ var baoRead = (path) => Effect.map(baoCall("read", "GET", path), (body) => {
|
|
|
283
282
|
var baoWrite = (method, path, body) => Effect.asVoid(baoCall("write", method, path, body));
|
|
284
283
|
var baoDelete = (path) => Effect.asVoid(baoCall("delete", "DELETE", path));
|
|
285
284
|
|
|
286
|
-
// src/openbao/auth-
|
|
287
|
-
var
|
|
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, [
|
|
287
|
+
...asked.errors,
|
|
288
|
+
`sys/auth could not confirm the method is absent: ${listing.message}`
|
|
289
|
+
])), Effect2.flatMap((table) => table !== undefined && !Object.hasOwn(table, `${authPath(path)}/`) ? Effect2.succeed(undefined) : Effect2.fail(asked)));
|
|
290
|
+
var readAuthMethodData = (path) => baoRead(readPath2(path)).pipe(Effect2.catchIf((error) => error.status === 400, (error) => absentFromTable(path, error)));
|
|
291
|
+
var enableAuthMethod = (props) => baoWrite("POST", readPath2(props.path), enableBody2(props));
|
|
292
|
+
var tuneAuthMethod = (props) => baoWrite("POST", tunePath2(props.path), tuneBody2(props));
|
|
293
|
+
var disableAuthMethod = (path) => baoDelete(readPath2(path));
|
|
294
|
+
|
|
295
|
+
// src/openbao/auth-method.ts
|
|
296
|
+
var BaoAuthMethod = Resource("Bao.AuthMethod", {
|
|
288
297
|
defaultRemovalPolicy: "retain"
|
|
289
298
|
});
|
|
290
|
-
var
|
|
291
|
-
const live = yield*
|
|
299
|
+
var readMethod = (props) => Effect3.gen(function* () {
|
|
300
|
+
const live = yield* readAuthMethodData(props.path);
|
|
292
301
|
if (live === undefined)
|
|
293
302
|
return;
|
|
294
303
|
return attributesOf2(props, live);
|
|
295
304
|
});
|
|
296
|
-
var
|
|
297
|
-
list: () =>
|
|
298
|
-
read:
|
|
299
|
-
return yield*
|
|
305
|
+
var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect3.succeed(BaoAuthMethod.Provider.of({
|
|
306
|
+
list: () => Effect3.succeed([]),
|
|
307
|
+
read: Effect3.fn(function* ({ olds }) {
|
|
308
|
+
return yield* readMethod(olds);
|
|
300
309
|
}),
|
|
301
|
-
diff:
|
|
310
|
+
diff: Effect3.fn(function* ({ news, output }) {
|
|
302
311
|
if (output === undefined || !isResolved(news))
|
|
303
312
|
return;
|
|
304
|
-
const live = yield*
|
|
313
|
+
const live = yield* readMethod(news);
|
|
305
314
|
if (live === undefined)
|
|
306
315
|
return { action: "update" };
|
|
316
|
+
if (live.type !== news.type)
|
|
317
|
+
return { action: "replace" };
|
|
307
318
|
if (matches2(live, news))
|
|
308
319
|
return { action: "noop" };
|
|
309
320
|
return { action: "update" };
|
|
310
321
|
}),
|
|
311
|
-
reconcile:
|
|
322
|
+
reconcile: Effect3.fn(function* ({ news }) {
|
|
323
|
+
let live = yield* readMethod(news);
|
|
324
|
+
if (live === undefined) {
|
|
325
|
+
yield* enableAuthMethod(news);
|
|
326
|
+
if (wantsTune2(news))
|
|
327
|
+
yield* tuneAuthMethod(news);
|
|
328
|
+
} else if (!matches2(live, news)) {
|
|
329
|
+
if (live.type !== news.type) {
|
|
330
|
+
return yield* Effect3.die(new Error(`Bao.AuthMethod ${authPath(news.path)}: live type ${live.type} != ${news.type}. ` + "Auth method type is immutable — replace manually."));
|
|
331
|
+
}
|
|
332
|
+
yield* tuneAuthMethod(news);
|
|
333
|
+
}
|
|
334
|
+
live = yield* readMethod(news);
|
|
335
|
+
if (live === undefined) {
|
|
336
|
+
return yield* Effect3.die(new Error(`Bao.AuthMethod ${authPath(news.path)}: write returned no error but the method is still absent.`));
|
|
337
|
+
}
|
|
338
|
+
return live;
|
|
339
|
+
}),
|
|
340
|
+
delete: Effect3.fn(function* ({ output }) {
|
|
341
|
+
yield* disableAuthMethod(output.path);
|
|
342
|
+
return;
|
|
343
|
+
})
|
|
344
|
+
})));
|
|
345
|
+
// src/openbao/auth-role.ts
|
|
346
|
+
import { Resource as Resource2 } from "alchemy";
|
|
347
|
+
import { isResolved as isResolved2 } from "alchemy/Diff";
|
|
348
|
+
import * as Provider2 from "alchemy/Provider";
|
|
349
|
+
import * as Effect4 from "effect/Effect";
|
|
350
|
+
|
|
351
|
+
// src/openbao/auth-role-form.ts
|
|
352
|
+
var policiesOf = (live) => {
|
|
353
|
+
const raw = live["token_policies"];
|
|
354
|
+
if (!Array.isArray(raw))
|
|
355
|
+
return [];
|
|
356
|
+
return raw.filter((entry) => typeof entry === "string").slice().sort();
|
|
357
|
+
};
|
|
358
|
+
var bool = (value, fallback) => typeof value === "boolean" ? value : fallback;
|
|
359
|
+
var int = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
360
|
+
var ttlText = (seconds, fallback = "") => {
|
|
361
|
+
if (seconds === undefined)
|
|
362
|
+
return fallback;
|
|
363
|
+
if (seconds % 86400 === 0)
|
|
364
|
+
return `${String(seconds / 86400)}d`;
|
|
365
|
+
if (seconds % 3600 === 0)
|
|
366
|
+
return `${String(seconds / 3600)}h`;
|
|
367
|
+
if (seconds % 60 === 0)
|
|
368
|
+
return `${String(seconds / 60)}m`;
|
|
369
|
+
return `${String(seconds)}s`;
|
|
370
|
+
};
|
|
371
|
+
var attributesOf3 = (props, live) => {
|
|
372
|
+
const attrs = {
|
|
373
|
+
name: props.name,
|
|
374
|
+
tokenPolicies: policiesOf(live),
|
|
375
|
+
tokenTtl: ttlText(ttlSeconds(live["token_ttl"])),
|
|
376
|
+
tokenMaxTtl: ttlText(ttlSeconds(live["token_max_ttl"])),
|
|
377
|
+
secretIdTtl: ttlText(ttlSeconds(live["secret_id_ttl"]), "0"),
|
|
378
|
+
bindSecretId: bool(live["bind_secret_id"], true),
|
|
379
|
+
secretIdNumUses: int(live["secret_id_num_uses"], 0)
|
|
380
|
+
};
|
|
381
|
+
return { ...attrs, digest: sha256(JSON.stringify(attrs)) };
|
|
382
|
+
};
|
|
383
|
+
var readPath3 = (name) => `auth/approle/role/${name}`;
|
|
384
|
+
var writeBody = (props) => {
|
|
385
|
+
const body = {
|
|
386
|
+
token_policies: props.tokenPolicies.join(","),
|
|
387
|
+
token_ttl: props.tokenTtl,
|
|
388
|
+
token_max_ttl: props.tokenMaxTtl,
|
|
389
|
+
secret_id_ttl: props.secretIdTtl
|
|
390
|
+
};
|
|
391
|
+
if (props.bindSecretId !== undefined)
|
|
392
|
+
body["bind_secret_id"] = String(props.bindSecretId);
|
|
393
|
+
if (props.secretIdNumUses !== undefined) {
|
|
394
|
+
body["secret_id_num_uses"] = String(props.secretIdNumUses);
|
|
395
|
+
}
|
|
396
|
+
return body;
|
|
397
|
+
};
|
|
398
|
+
var samePolicies = (want, have) => want.length === have.length && want.every((policy, index) => policy === have[index]);
|
|
399
|
+
var sameTtl3 = (want, have) => {
|
|
400
|
+
const wantSeconds = parseDuration(want);
|
|
401
|
+
const haveSeconds = ttlSeconds(have);
|
|
402
|
+
return wantSeconds !== undefined && wantSeconds === haveSeconds;
|
|
403
|
+
};
|
|
404
|
+
var matches3 = (attributes, props) => {
|
|
405
|
+
const wantPolicies = props.tokenPolicies.slice().sort();
|
|
406
|
+
return samePolicies(wantPolicies, attributes.tokenPolicies) && sameTtl3(props.tokenTtl, attributes.tokenTtl) && sameTtl3(props.tokenMaxTtl, attributes.tokenMaxTtl) && sameTtl3(props.secretIdTtl, attributes.secretIdTtl) && (props.bindSecretId === undefined || attributes.bindSecretId === props.bindSecretId) && (props.secretIdNumUses === undefined || attributes.secretIdNumUses === props.secretIdNumUses);
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// src/openbao/auth-role.ts
|
|
410
|
+
var BaoAuthRole = Resource2("Bao.AuthRole", {
|
|
411
|
+
defaultRemovalPolicy: "retain"
|
|
412
|
+
});
|
|
413
|
+
var readRole = (props) => Effect4.gen(function* () {
|
|
414
|
+
const live = yield* baoRead(readPath3(props.name));
|
|
415
|
+
if (live === undefined)
|
|
416
|
+
return;
|
|
417
|
+
return attributesOf3(props, live);
|
|
418
|
+
});
|
|
419
|
+
var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect4.succeed(BaoAuthRole.Provider.of({
|
|
420
|
+
list: () => Effect4.succeed([]),
|
|
421
|
+
read: Effect4.fn(function* ({ olds }) {
|
|
422
|
+
return yield* readRole(olds);
|
|
423
|
+
}),
|
|
424
|
+
diff: Effect4.fn(function* ({ news, output }) {
|
|
425
|
+
if (output === undefined || !isResolved2(news))
|
|
426
|
+
return;
|
|
312
427
|
const live = yield* readRole(news);
|
|
313
|
-
if (live === undefined
|
|
314
|
-
|
|
428
|
+
if (live === undefined)
|
|
429
|
+
return { action: "update" };
|
|
430
|
+
if (matches3(live, news))
|
|
431
|
+
return { action: "noop" };
|
|
432
|
+
return { action: "update" };
|
|
433
|
+
}),
|
|
434
|
+
reconcile: Effect4.fn(function* ({ news }) {
|
|
435
|
+
const live = yield* readRole(news);
|
|
436
|
+
if (live === undefined || !matches3(live, news)) {
|
|
437
|
+
yield* baoWrite("PUT", readPath3(news.name), writeBody(news));
|
|
315
438
|
}
|
|
316
439
|
const after = yield* readRole(news);
|
|
317
440
|
if (after === undefined) {
|
|
318
|
-
return yield*
|
|
441
|
+
return yield* Effect4.die(new Error(`Bao.AuthRole ${news.name}: write returned no error but the role is still absent.`));
|
|
319
442
|
}
|
|
320
443
|
return after;
|
|
321
444
|
}),
|
|
322
|
-
delete:
|
|
323
|
-
yield* baoDelete(
|
|
445
|
+
delete: Effect4.fn(function* ({ output }) {
|
|
446
|
+
yield* baoDelete(readPath3(output.name));
|
|
324
447
|
return;
|
|
325
448
|
})
|
|
326
449
|
})));
|
|
327
450
|
// src/openbao/cloudflare-role.ts
|
|
328
|
-
import { Resource as
|
|
329
|
-
import { isResolved as
|
|
330
|
-
import * as
|
|
331
|
-
import * as
|
|
451
|
+
import { Resource as Resource3 } from "alchemy";
|
|
452
|
+
import { isResolved as isResolved3 } from "alchemy/Diff";
|
|
453
|
+
import * as Provider3 from "alchemy/Provider";
|
|
454
|
+
import * as Effect7 from "effect/Effect";
|
|
332
455
|
|
|
333
456
|
// src/openbao/cloudflare-group-scope.ts
|
|
334
457
|
var zoneResource = (zoneId) => `com.cloudflare.api.account.zone.${zoneId}`;
|
|
@@ -348,7 +471,7 @@ var groupKey = (name, scope) => `${scope} ${name}`;
|
|
|
348
471
|
|
|
349
472
|
// src/openbao/cloudflare-permission-groups.ts
|
|
350
473
|
import * as Context2 from "effect/Context";
|
|
351
|
-
import * as
|
|
474
|
+
import * as Effect6 from "effect/Effect";
|
|
352
475
|
|
|
353
476
|
// src/openbao/cloudflare-permission-groups-solve.ts
|
|
354
477
|
var observationsOf = (declared, live) => declared.flatMap((role) => {
|
|
@@ -518,7 +641,7 @@ var samePolicies2 = (want, have) => want.length === have.length && want.every((w
|
|
|
518
641
|
});
|
|
519
642
|
|
|
520
643
|
// src/openbao/cloudflare-role-wire.ts
|
|
521
|
-
import * as
|
|
644
|
+
import * as Effect5 from "effect/Effect";
|
|
522
645
|
|
|
523
646
|
// src/openbao/cloudflare-role-form.ts
|
|
524
647
|
var rolePath = (mount, name) => `${mountPath(mount)}/roles/${name}`;
|
|
@@ -531,7 +654,7 @@ var liveRoleOf = (data) => ({
|
|
|
531
654
|
policiesText: typeof data["policies"] === "string" ? data["policies"] : "",
|
|
532
655
|
ttl: seconds(data["ttl"])
|
|
533
656
|
});
|
|
534
|
-
var
|
|
657
|
+
var attributesOf4 = (props, live) => {
|
|
535
658
|
const attrs = {
|
|
536
659
|
description: live.description,
|
|
537
660
|
maxTtl: live.maxTtl ?? 0,
|
|
@@ -561,7 +684,7 @@ var writeBody2 = (props, policies) => ({
|
|
|
561
684
|
policies: policiesJson(policies),
|
|
562
685
|
ttl: props.ttl
|
|
563
686
|
});
|
|
564
|
-
var
|
|
687
|
+
var sameTtl4 = (want, have) => {
|
|
565
688
|
const wantSeconds = parseDuration(want);
|
|
566
689
|
return wantSeconds !== undefined && wantSeconds === have;
|
|
567
690
|
};
|
|
@@ -570,10 +693,10 @@ var differences = (props, policies, live) => {
|
|
|
570
693
|
if (props.description !== live.description) {
|
|
571
694
|
out.push({ field: "description", have: live.description, want: props.description });
|
|
572
695
|
}
|
|
573
|
-
if (!
|
|
696
|
+
if (!sameTtl4(props.ttl, live.ttl)) {
|
|
574
697
|
out.push({ field: "ttl", have: String(live.ttl), want: props.ttl });
|
|
575
698
|
}
|
|
576
|
-
if (!
|
|
699
|
+
if (!sameTtl4(props.maxTtl, live.maxTtl)) {
|
|
577
700
|
out.push({ field: "max_ttl", have: String(live.maxTtl), want: props.maxTtl });
|
|
578
701
|
}
|
|
579
702
|
if (live.policies === undefined || !samePolicies2(policies, live.policies)) {
|
|
@@ -583,7 +706,7 @@ var differences = (props, policies, live) => {
|
|
|
583
706
|
};
|
|
584
707
|
|
|
585
708
|
// src/openbao/cloudflare-role-wire.ts
|
|
586
|
-
var readCloudflareRole = (mount, name) =>
|
|
709
|
+
var readCloudflareRole = (mount, name) => Effect5.map(baoRead(rolePath(mount, name)), (data) => data === undefined ? undefined : liveRoleOf(data));
|
|
587
710
|
var writeCloudflareRole = (mount, name, body) => baoWrite("PUT", rolePath(mount, name), body);
|
|
588
711
|
var deleteCloudflareRole = (mount, name) => baoDelete(rolePath(mount, name));
|
|
589
712
|
|
|
@@ -605,9 +728,9 @@ var pick = (mount, groups, refs, hint) => {
|
|
|
605
728
|
const unknown = refs.filter((ref) => !groups.has(groupKey(ref.name, ref.scope)));
|
|
606
729
|
if (unknown.length > 0) {
|
|
607
730
|
const problems = unknown.map((ref) => `unknown group ${JSON.stringify(ref.name)} at scope ${ref.scope} — ${hint}`);
|
|
608
|
-
return
|
|
731
|
+
return Effect6.fail(new CloudflareGroupsError(mount, problems));
|
|
609
732
|
}
|
|
610
|
-
return
|
|
733
|
+
return Effect6.succeed(new Map(refs.flatMap((ref) => {
|
|
611
734
|
const key = groupKey(ref.name, ref.scope);
|
|
612
735
|
const id = groups.get(key);
|
|
613
736
|
return id === undefined ? [] : [[key, id]];
|
|
@@ -615,15 +738,15 @@ var pick = (mount, groups, refs, hint) => {
|
|
|
615
738
|
};
|
|
616
739
|
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
617
740
|
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";
|
|
618
|
-
var deniedNamed = (mount, read) =>
|
|
619
|
-
var engineGroups = (mount) =>
|
|
741
|
+
var deniedNamed = (mount, read) => Effect6.catchIf(read, (error) => error.status === 403, () => Effect6.fail(new CloudflareGroupsError(mount, [deniedHint(mount)])));
|
|
742
|
+
var engineGroups = (mount) => Effect6.flatMap(deniedNamed(mount, baoRead(`${mountPath(mount)}/permission-groups`)), (data) => {
|
|
620
743
|
const list = data?.["permission_groups"];
|
|
621
744
|
if (data === undefined) {
|
|
622
745
|
const why = "GET <mount>/permission-groups answered 404 — the endpoint is not deployed here";
|
|
623
|
-
return
|
|
746
|
+
return Effect6.fail(new CloudflareGroupsError(mount, [why]));
|
|
624
747
|
}
|
|
625
748
|
if (!Array.isArray(list)) {
|
|
626
|
-
return
|
|
749
|
+
return Effect6.fail(new CloudflareGroupsError(mount, ["no permission_groups array in reply"]));
|
|
627
750
|
}
|
|
628
751
|
const scoped = new Map;
|
|
629
752
|
const idsOf = new Map;
|
|
@@ -650,24 +773,24 @@ var engineGroups = (mount) => Effect4.flatMap(deniedNamed(mount, baoRead(`${moun
|
|
|
650
773
|
const only = [...ids];
|
|
651
774
|
return ids.size === 1 && only[0] !== undefined ? [[name, only[0]]] : [];
|
|
652
775
|
}));
|
|
653
|
-
return problems.length > 0 ?
|
|
776
|
+
return problems.length > 0 ? Effect6.fail(new CloudflareGroupsError(mount, problems)) : Effect6.succeed(new Map([...atEveryScope(sole), ...scoped]));
|
|
654
777
|
});
|
|
655
|
-
var permissionGroupsFromEngine = () =>
|
|
778
|
+
var permissionGroupsFromEngine = () => Effect6.sync(() => {
|
|
656
779
|
const cache = new Map;
|
|
657
|
-
const cachedFor = (mount) =>
|
|
780
|
+
const cachedFor = (mount) => Effect6.suspend(() => {
|
|
658
781
|
const hit = cache.get(mountPath(mount));
|
|
659
782
|
if (hit !== undefined)
|
|
660
783
|
return hit;
|
|
661
|
-
return
|
|
784
|
+
return Effect6.flatMap(Effect6.cached(engineGroups(mount)), (lookup) => {
|
|
662
785
|
cache.set(mountPath(mount), lookup);
|
|
663
786
|
return lookup;
|
|
664
787
|
});
|
|
665
788
|
});
|
|
666
789
|
return CloudflarePermissionGroups.of({
|
|
667
|
-
resolve: (mount, refs) =>
|
|
790
|
+
resolve: (mount, refs) => Effect6.flatMap(cachedFor(mount), (groups) => pick(mount, groups, refs, "the engine does not list it for this account at that scope"))
|
|
668
791
|
});
|
|
669
792
|
});
|
|
670
|
-
var permissionGroupsFromLiveRoles = (declared) =>
|
|
793
|
+
var permissionGroupsFromLiveRoles = (declared) => Effect6.gen(function* () {
|
|
671
794
|
const accountOf = new Map;
|
|
672
795
|
const byAccount = new Map;
|
|
673
796
|
for (const [mount, roles] of declared) {
|
|
@@ -678,9 +801,9 @@ var permissionGroupsFromLiveRoles = (declared) => Effect4.gen(function* () {
|
|
|
678
801
|
}
|
|
679
802
|
const solved = new Map;
|
|
680
803
|
for (const [account, roles] of byAccount) {
|
|
681
|
-
const solve =
|
|
804
|
+
const solve = Effect6.gen(function* () {
|
|
682
805
|
const live = new Map;
|
|
683
|
-
yield*
|
|
806
|
+
yield* Effect6.all(roles.map((role) => Effect6.map(readCloudflareRole(role.mount, role.name), (read) => {
|
|
684
807
|
if (read?.policies !== undefined) {
|
|
685
808
|
live.set(`${role.mount}/${role.name}`, read.policies);
|
|
686
809
|
}
|
|
@@ -688,11 +811,11 @@ var permissionGroupsFromLiveRoles = (declared) => Effect4.gen(function* () {
|
|
|
688
811
|
const result = solveGroups(observationsOf(roles, live));
|
|
689
812
|
const problems = describeProblems(result);
|
|
690
813
|
if (problems.length > 0) {
|
|
691
|
-
return yield*
|
|
814
|
+
return yield* Effect6.fail(new CloudflareGroupsError(`account ${account}`, problems));
|
|
692
815
|
}
|
|
693
816
|
return atEveryScope(result.groups);
|
|
694
817
|
});
|
|
695
|
-
solved.set(account, yield*
|
|
818
|
+
solved.set(account, yield* Effect6.cached(solve));
|
|
696
819
|
}
|
|
697
820
|
return CloudflarePermissionGroups.of({
|
|
698
821
|
resolve: (mount, refs) => {
|
|
@@ -700,21 +823,21 @@ var permissionGroupsFromLiveRoles = (declared) => Effect4.gen(function* () {
|
|
|
700
823
|
const lookup = account === undefined ? undefined : solved.get(account);
|
|
701
824
|
if (account === undefined || lookup === undefined) {
|
|
702
825
|
const why = "no declared role on this mount, so no live role can vouch for its groups";
|
|
703
|
-
return
|
|
826
|
+
return Effect6.fail(new CloudflareGroupsError(mount, [why]));
|
|
704
827
|
}
|
|
705
828
|
const hint = `no live role on account ${account} carries it; a NEW group needs the engine endpoint`;
|
|
706
|
-
return
|
|
829
|
+
return Effect6.flatMap(lookup, (groups) => pick(mount, groups, refs, hint));
|
|
707
830
|
}
|
|
708
831
|
});
|
|
709
832
|
});
|
|
710
833
|
|
|
711
834
|
// src/openbao/cloudflare-role.ts
|
|
712
|
-
var BaoCloudflareRole =
|
|
835
|
+
var BaoCloudflareRole = Resource3("Bao.CloudflareRole", {
|
|
713
836
|
defaultRemovalPolicy: "retain"
|
|
714
837
|
});
|
|
715
|
-
var BaoCloudflareRoleProvider = () =>
|
|
838
|
+
var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect7.gen(function* () {
|
|
716
839
|
const groups = yield* CloudflarePermissionGroups;
|
|
717
|
-
const resolved = (props) =>
|
|
840
|
+
const resolved = (props) => Effect7.gen(function* () {
|
|
718
841
|
const refs = new Map;
|
|
719
842
|
for (const policy of props.policies) {
|
|
720
843
|
const scope = policy.scope ?? scopeOfResource(policy.resource);
|
|
@@ -724,18 +847,18 @@ var BaoCloudflareRoleProvider = () => Provider2.effect(BaoCloudflareRole, Effect
|
|
|
724
847
|
const ids = yield* groups.resolve(props.mount, [...refs.values()]);
|
|
725
848
|
const { missing, policies } = resolvePolicies(props.policies, ids);
|
|
726
849
|
if (missing.length > 0) {
|
|
727
|
-
return yield*
|
|
850
|
+
return yield* Effect7.die(new Error(`Bao.CloudflareRole ${rolePath(props.mount, props.name)}: groups ` + `${missing.join(", ")} came back unresolved from a lookup that reported success.`));
|
|
728
851
|
}
|
|
729
852
|
return policies;
|
|
730
853
|
});
|
|
731
854
|
return BaoCloudflareRole.Provider.of({
|
|
732
|
-
list: () =>
|
|
733
|
-
read:
|
|
855
|
+
list: () => Effect7.succeed([]),
|
|
856
|
+
read: Effect7.fn(function* ({ olds }) {
|
|
734
857
|
const live = yield* readCloudflareRole(olds.mount, olds.name);
|
|
735
|
-
return live === undefined ? undefined :
|
|
858
|
+
return live === undefined ? undefined : attributesOf4(olds, live);
|
|
736
859
|
}),
|
|
737
|
-
diff:
|
|
738
|
-
if (output === undefined || !
|
|
860
|
+
diff: Effect7.fn(function* ({ news, output }) {
|
|
861
|
+
if (output === undefined || !isResolved3(news))
|
|
739
862
|
return;
|
|
740
863
|
if (refusalOf(news) !== undefined)
|
|
741
864
|
return { action: "update" };
|
|
@@ -745,11 +868,11 @@ var BaoCloudflareRoleProvider = () => Provider2.effect(BaoCloudflareRole, Effect
|
|
|
745
868
|
return { action: "update" };
|
|
746
869
|
return differences(news, policies, live).length === 0 ? { action: "noop" } : { action: "update" };
|
|
747
870
|
}),
|
|
748
|
-
reconcile:
|
|
871
|
+
reconcile: Effect7.fn(function* ({ news }) {
|
|
749
872
|
const path = rolePath(news.mount, news.name);
|
|
750
873
|
const refusal = refusalOf(news);
|
|
751
874
|
if (refusal !== undefined) {
|
|
752
|
-
return yield*
|
|
875
|
+
return yield* Effect7.die(new Error(`Bao.CloudflareRole ${path}: ${refusal}.`));
|
|
753
876
|
}
|
|
754
877
|
const policies = yield* resolved(news);
|
|
755
878
|
const live = yield* readCloudflareRole(news.mount, news.name);
|
|
@@ -758,15 +881,15 @@ var BaoCloudflareRoleProvider = () => Provider2.effect(BaoCloudflareRole, Effect
|
|
|
758
881
|
}
|
|
759
882
|
const after = yield* readCloudflareRole(news.mount, news.name);
|
|
760
883
|
if (after === undefined) {
|
|
761
|
-
return yield*
|
|
884
|
+
return yield* Effect7.die(new Error(`Bao.CloudflareRole ${path}: the write succeeded but the role is absent.`));
|
|
762
885
|
}
|
|
763
886
|
const left = differences(news, policies, after);
|
|
764
887
|
if (left.length > 0) {
|
|
765
|
-
return yield*
|
|
888
|
+
return yield* Effect7.die(new Error(`Bao.CloudflareRole ${path}: wrote cleanly but reads back different in ` + `${left.map((d) => d.field).join(", ")}. Inspect the role before retrying.`));
|
|
766
889
|
}
|
|
767
|
-
return
|
|
890
|
+
return attributesOf4(news, after);
|
|
768
891
|
}),
|
|
769
|
-
delete:
|
|
892
|
+
delete: Effect7.fn(function* ({ output }) {
|
|
770
893
|
yield* deleteCloudflareRole(output.mount, output.name);
|
|
771
894
|
return;
|
|
772
895
|
})
|
|
@@ -873,7 +996,7 @@ var record = (value, where) => {
|
|
|
873
996
|
throw new RolesConfigError(where, "expected a mapping");
|
|
874
997
|
return value;
|
|
875
998
|
};
|
|
876
|
-
var
|
|
999
|
+
var text3 = (raw, key, where) => {
|
|
877
1000
|
const value = raw[key];
|
|
878
1001
|
if (typeof value === "string")
|
|
879
1002
|
return value;
|
|
@@ -901,24 +1024,24 @@ var template = (value, where) => {
|
|
|
901
1024
|
const groups = texts(raw, "groups", where);
|
|
902
1025
|
if (groups === undefined)
|
|
903
1026
|
throw new RolesConfigError(`${where}.groups`, "is required");
|
|
904
|
-
const bucket = raw["bucket"] === undefined ? undefined :
|
|
1027
|
+
const bucket = raw["bucket"] === undefined ? undefined : text3(raw, "bucket", where);
|
|
905
1028
|
if (bucket !== undefined && bucket.length === 0) {
|
|
906
1029
|
throw new RolesConfigError(`${where}.bucket`, "is empty; omit it to stay account-wide");
|
|
907
1030
|
}
|
|
908
|
-
const groupsScope = raw["groups_scope"] === undefined ? undefined : groupScopeNamed(
|
|
1031
|
+
const groupsScope = raw["groups_scope"] === undefined ? undefined : groupScopeNamed(text3(raw, "groups_scope", where));
|
|
909
1032
|
if (raw["groups_scope"] !== undefined && groupsScope === undefined) {
|
|
910
1033
|
throw new RolesConfigError(`${where}.groups_scope`, `expected one of ${Object.keys(GROUP_SCOPES).join(", ")}`);
|
|
911
1034
|
}
|
|
912
1035
|
return {
|
|
913
1036
|
accountGroups: texts(raw, "account_groups", where) ?? [],
|
|
914
1037
|
bucket,
|
|
915
|
-
bucketJurisdiction: raw["bucket_jurisdiction"] === undefined ? "default" :
|
|
916
|
-
desc: raw["desc"] === undefined ? "" :
|
|
1038
|
+
bucketJurisdiction: raw["bucket_jurisdiction"] === undefined ? "default" : text3(raw, "bucket_jurisdiction", where),
|
|
1039
|
+
desc: raw["desc"] === undefined ? "" : text3(raw, "desc", where),
|
|
917
1040
|
groups,
|
|
918
1041
|
groupsScope,
|
|
919
|
-
maxTtl:
|
|
920
|
-
name:
|
|
921
|
-
ttl:
|
|
1042
|
+
maxTtl: text3(raw, "max_ttl", where),
|
|
1043
|
+
name: text3(raw, "name", where),
|
|
1044
|
+
ttl: text3(raw, "ttl", where),
|
|
922
1045
|
zoneTypes: texts(raw, "zone_types", where) ?? [],
|
|
923
1046
|
zones: texts(raw, "zones", where)
|
|
924
1047
|
};
|
|
@@ -941,7 +1064,7 @@ var account = (key, value, surfaces) => {
|
|
|
941
1064
|
}
|
|
942
1065
|
}
|
|
943
1066
|
const zones = raw["zones"] === undefined || raw["zones"] === "all" ? "all" : texts(raw, "zones", where);
|
|
944
|
-
return { id:
|
|
1067
|
+
return { id: text3(raw, "id", where), key, surfaces: names, zones: zones ?? "all" };
|
|
945
1068
|
};
|
|
946
1069
|
var parseRolesConfig = (yaml) => {
|
|
947
1070
|
const root = record(Bun.YAML.parse(yaml), "(document)");
|
|
@@ -952,44 +1075,44 @@ var parseRolesConfig = (yaml) => {
|
|
|
952
1075
|
const accounts = Object.entries(record(root["accounts"], "accounts")).map(([key, value]) => account(key, value, surfaces));
|
|
953
1076
|
return {
|
|
954
1077
|
accounts,
|
|
955
|
-
namespace:
|
|
1078
|
+
namespace: text3(record(root["defaults"], "defaults"), "namespace", "defaults"),
|
|
956
1079
|
surfaces
|
|
957
1080
|
};
|
|
958
1081
|
};
|
|
959
1082
|
// src/openbao/mount.ts
|
|
960
|
-
import { Resource as
|
|
961
|
-
import { isResolved as
|
|
962
|
-
import * as
|
|
963
|
-
import * as
|
|
1083
|
+
import { Resource as Resource4 } from "alchemy";
|
|
1084
|
+
import { isResolved as isResolved4 } from "alchemy/Diff";
|
|
1085
|
+
import * as Provider4 from "alchemy/Provider";
|
|
1086
|
+
import * as Effect9 from "effect/Effect";
|
|
964
1087
|
|
|
965
1088
|
// src/openbao/mount-wire.ts
|
|
966
|
-
import * as
|
|
967
|
-
var
|
|
1089
|
+
import * as Effect8 from "effect/Effect";
|
|
1090
|
+
var absentFromTable2 = (path, asked) => baoRead("sys/mounts").pipe(Effect8.mapError((listing) => new BaoError(asked.status, asked.method, asked.path, [
|
|
968
1091
|
...asked.errors,
|
|
969
1092
|
`sys/mounts could not confirm the mount is absent: ${listing.message}`
|
|
970
|
-
])),
|
|
971
|
-
var readMountData = (path) => baoRead(readPath(path)).pipe(
|
|
1093
|
+
])), Effect8.flatMap((table) => table !== undefined && !Object.hasOwn(table, `${mountPath(path)}/`) ? Effect8.succeed(undefined) : Effect8.fail(asked)));
|
|
1094
|
+
var readMountData = (path) => baoRead(readPath(path)).pipe(Effect8.catchIf((error) => error.status === 400, (error) => absentFromTable2(path, error)));
|
|
972
1095
|
var enableMount = (props) => baoWrite("POST", readPath(props.path), enableBody(props));
|
|
973
1096
|
var tuneMount = (props) => baoWrite("POST", tunePath(props.path), tuneBody(props));
|
|
974
1097
|
var disableMount = (path) => baoDelete(readPath(path));
|
|
975
1098
|
|
|
976
1099
|
// src/openbao/mount.ts
|
|
977
|
-
var BaoMount =
|
|
1100
|
+
var BaoMount = Resource4("Bao.Mount", {
|
|
978
1101
|
defaultRemovalPolicy: "retain"
|
|
979
1102
|
});
|
|
980
|
-
var readMount = (props) =>
|
|
1103
|
+
var readMount = (props) => Effect9.gen(function* () {
|
|
981
1104
|
const live = yield* readMountData(props.path);
|
|
982
1105
|
if (live === undefined)
|
|
983
1106
|
return;
|
|
984
1107
|
return attributesOf(props, live);
|
|
985
1108
|
});
|
|
986
|
-
var BaoMountProvider = () =>
|
|
987
|
-
list: () =>
|
|
988
|
-
read:
|
|
1109
|
+
var BaoMountProvider = () => Provider4.effect(BaoMount, Effect9.succeed(BaoMount.Provider.of({
|
|
1110
|
+
list: () => Effect9.succeed([]),
|
|
1111
|
+
read: Effect9.fn(function* ({ olds }) {
|
|
989
1112
|
return yield* readMount(olds);
|
|
990
1113
|
}),
|
|
991
|
-
diff:
|
|
992
|
-
if (output === undefined || !
|
|
1114
|
+
diff: Effect9.fn(function* ({ news, output }) {
|
|
1115
|
+
if (output === undefined || !isResolved4(news))
|
|
993
1116
|
return;
|
|
994
1117
|
const live = yield* readMount(news);
|
|
995
1118
|
if (live === undefined)
|
|
@@ -1000,7 +1123,7 @@ var BaoMountProvider = () => Provider3.effect(BaoMount, Effect7.succeed(BaoMount
|
|
|
1000
1123
|
return { action: "noop" };
|
|
1001
1124
|
return { action: "update" };
|
|
1002
1125
|
}),
|
|
1003
|
-
reconcile:
|
|
1126
|
+
reconcile: Effect9.fn(function* ({ news }) {
|
|
1004
1127
|
let live = yield* readMount(news);
|
|
1005
1128
|
if (live === undefined) {
|
|
1006
1129
|
yield* enableMount(news);
|
|
@@ -1008,26 +1131,26 @@ var BaoMountProvider = () => Provider3.effect(BaoMount, Effect7.succeed(BaoMount
|
|
|
1008
1131
|
yield* tuneMount(news);
|
|
1009
1132
|
} else if (!matches(live, news)) {
|
|
1010
1133
|
if (live.type !== news.type) {
|
|
1011
|
-
return yield*
|
|
1134
|
+
return yield* Effect9.die(new Error(`Bao.Mount ${mountPath(news.path)}: live type ${live.type} != ${news.type}. ` + "Secrets engine type is immutable — replace manually."));
|
|
1012
1135
|
}
|
|
1013
1136
|
yield* tuneMount(news);
|
|
1014
1137
|
}
|
|
1015
1138
|
live = yield* readMount(news);
|
|
1016
1139
|
if (live === undefined) {
|
|
1017
|
-
return yield*
|
|
1140
|
+
return yield* Effect9.die(new Error(`Bao.Mount ${mountPath(news.path)}: write returned no error but the mount is still absent.`));
|
|
1018
1141
|
}
|
|
1019
1142
|
return live;
|
|
1020
1143
|
}),
|
|
1021
|
-
delete:
|
|
1144
|
+
delete: Effect9.fn(function* ({ output }) {
|
|
1022
1145
|
yield* disableMount(output.path);
|
|
1023
1146
|
return;
|
|
1024
1147
|
})
|
|
1025
1148
|
})));
|
|
1026
1149
|
// src/openbao/pki-role.ts
|
|
1027
|
-
import { Resource as
|
|
1028
|
-
import { isResolved as
|
|
1029
|
-
import * as
|
|
1030
|
-
import * as
|
|
1150
|
+
import { Resource as Resource5 } from "alchemy";
|
|
1151
|
+
import { isResolved as isResolved5 } from "alchemy/Diff";
|
|
1152
|
+
import * as Provider5 from "alchemy/Provider";
|
|
1153
|
+
import * as Effect10 from "effect/Effect";
|
|
1031
1154
|
|
|
1032
1155
|
// src/openbao/pki-role-form.ts
|
|
1033
1156
|
var sorted = (values) => [...values].sort();
|
|
@@ -1050,7 +1173,7 @@ var canonical2 = (input) => ({
|
|
|
1050
1173
|
signatureBits: input.signatureBits,
|
|
1051
1174
|
ttlSeconds: input.ttlSeconds
|
|
1052
1175
|
});
|
|
1053
|
-
var seconds2 = (
|
|
1176
|
+
var seconds2 = (text4) => parseDuration(text4) ?? -1;
|
|
1054
1177
|
var canonicalFromProps = (props) => canonical2({
|
|
1055
1178
|
allowBareDomains: props.allowBareDomains ?? false,
|
|
1056
1179
|
allowGlobDomains: props.allowGlobDomains ?? false,
|
|
@@ -1104,7 +1227,7 @@ var ttlText2 = (value) => {
|
|
|
1104
1227
|
};
|
|
1105
1228
|
var digestOf = (form) => sha256(JSON.stringify(form));
|
|
1106
1229
|
var rolePath2 = (props) => `${mountPath(props.mount ?? "pki")}/roles/${props.name}`;
|
|
1107
|
-
var
|
|
1230
|
+
var attributesOf5 = (props, live) => {
|
|
1108
1231
|
const form = canonicalFromLive(live);
|
|
1109
1232
|
return {
|
|
1110
1233
|
...form,
|
|
@@ -1139,26 +1262,26 @@ var writeBody3 = (props) => {
|
|
|
1139
1262
|
body["ext_key_usage"] = usages.join(",");
|
|
1140
1263
|
return body;
|
|
1141
1264
|
};
|
|
1142
|
-
var
|
|
1265
|
+
var matches4 = (attributes, props) => attributes.digest === digestOf(canonicalFromProps(props));
|
|
1143
1266
|
|
|
1144
1267
|
// src/openbao/pki-role.ts
|
|
1145
|
-
var BaoPkiRole =
|
|
1268
|
+
var BaoPkiRole = Resource5("Bao.PkiRole", {
|
|
1146
1269
|
defaultRemovalPolicy: "retain"
|
|
1147
1270
|
});
|
|
1148
|
-
var readRole2 = (props) =>
|
|
1271
|
+
var readRole2 = (props) => Effect10.gen(function* () {
|
|
1149
1272
|
const live = yield* baoRead(rolePath2(props));
|
|
1150
1273
|
if (live === undefined)
|
|
1151
1274
|
return;
|
|
1152
|
-
return
|
|
1275
|
+
return attributesOf5(props, live);
|
|
1153
1276
|
});
|
|
1154
|
-
var badDurations = (props) => [["ttl", props.ttl], ["maxTtl", props.maxTtl]].filter(([,
|
|
1155
|
-
var BaoPkiRoleProvider = () =>
|
|
1156
|
-
list: () =>
|
|
1157
|
-
read:
|
|
1277
|
+
var badDurations = (props) => [["ttl", props.ttl], ["maxTtl", props.maxTtl]].filter(([, text4]) => parseDuration(text4) === undefined);
|
|
1278
|
+
var BaoPkiRoleProvider = () => Provider5.effect(BaoPkiRole, Effect10.succeed(BaoPkiRole.Provider.of({
|
|
1279
|
+
list: () => Effect10.succeed([]),
|
|
1280
|
+
read: Effect10.fn(function* ({ olds }) {
|
|
1158
1281
|
return yield* readRole2(olds);
|
|
1159
1282
|
}),
|
|
1160
|
-
diff:
|
|
1161
|
-
if (output === undefined || !
|
|
1283
|
+
diff: Effect10.fn(function* ({ news, output }) {
|
|
1284
|
+
if (output === undefined || !isResolved5(news))
|
|
1162
1285
|
return;
|
|
1163
1286
|
if (mountPath(news.mount ?? "pki") !== output.mount)
|
|
1164
1287
|
return { action: "replace" };
|
|
@@ -1168,40 +1291,40 @@ var BaoPkiRoleProvider = () => Provider4.effect(BaoPkiRole, Effect8.succeed(BaoP
|
|
|
1168
1291
|
const live = yield* readRole2(news);
|
|
1169
1292
|
if (live === undefined)
|
|
1170
1293
|
return { action: "update" };
|
|
1171
|
-
return
|
|
1294
|
+
return matches4(live, news) ? { action: "noop" } : { action: "update" };
|
|
1172
1295
|
}),
|
|
1173
|
-
reconcile:
|
|
1296
|
+
reconcile: Effect10.fn(function* ({ news }) {
|
|
1174
1297
|
const path = rolePath2(news);
|
|
1175
1298
|
if (news.allowedDomains.length === 0) {
|
|
1176
|
-
return yield*
|
|
1299
|
+
return yield* Effect10.die(new Error(`Bao.PkiRole ${path}: allowedDomains is empty. With allow_any_name false ` + "that role can issue no certificate at all."));
|
|
1177
1300
|
}
|
|
1178
1301
|
const bad = badDurations(news);
|
|
1179
1302
|
if (bad.length > 0) {
|
|
1180
|
-
return yield*
|
|
1303
|
+
return yield* Effect10.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)."));
|
|
1181
1304
|
}
|
|
1182
1305
|
const live = yield* readRole2(news);
|
|
1183
|
-
if (live === undefined || !
|
|
1306
|
+
if (live === undefined || !matches4(live, news)) {
|
|
1184
1307
|
yield* baoWrite("PUT", path, writeBody3(news));
|
|
1185
1308
|
}
|
|
1186
1309
|
const after = yield* readRole2(news);
|
|
1187
1310
|
if (after === undefined) {
|
|
1188
|
-
return yield*
|
|
1311
|
+
return yield* Effect10.die(new Error(`Bao.PkiRole ${path}: write reported success but the role is absent.`));
|
|
1189
1312
|
}
|
|
1190
|
-
if (!
|
|
1191
|
-
return yield*
|
|
1313
|
+
if (!matches4(after, news)) {
|
|
1314
|
+
return yield* Effect10.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.`));
|
|
1192
1315
|
}
|
|
1193
1316
|
return after;
|
|
1194
1317
|
}),
|
|
1195
|
-
delete:
|
|
1318
|
+
delete: Effect10.fn(function* ({ output }) {
|
|
1196
1319
|
yield* baoDelete(rolePath2(output));
|
|
1197
1320
|
return;
|
|
1198
1321
|
})
|
|
1199
1322
|
})));
|
|
1200
1323
|
// src/openbao/policy.ts
|
|
1201
|
-
import { Resource as
|
|
1202
|
-
import { isResolved as
|
|
1203
|
-
import * as
|
|
1204
|
-
import * as
|
|
1324
|
+
import { Resource as Resource6 } from "alchemy";
|
|
1325
|
+
import { isResolved as isResolved6 } from "alchemy/Diff";
|
|
1326
|
+
import * as Provider6 from "alchemy/Provider";
|
|
1327
|
+
import * as Effect12 from "effect/Effect";
|
|
1205
1328
|
import * as FileSystem from "effect/FileSystem";
|
|
1206
1329
|
import * as Path from "effect/Path";
|
|
1207
1330
|
|
|
@@ -1209,32 +1332,32 @@ import * as Path from "effect/Path";
|
|
|
1209
1332
|
var isEmptyAssembly = (parts, joined) => parts === 0 || !/^\s*path\s+"/m.test(joined);
|
|
1210
1333
|
|
|
1211
1334
|
// src/openbao/policy-wire.ts
|
|
1212
|
-
import * as
|
|
1335
|
+
import * as Effect11 from "effect/Effect";
|
|
1213
1336
|
var policyPath = (name) => `sys/policies/acl/${name}`;
|
|
1214
|
-
var readPolicy = (name) =>
|
|
1337
|
+
var readPolicy = (name) => Effect11.flatMap(baoRead(policyPath(name)), (data) => {
|
|
1215
1338
|
if (data === undefined)
|
|
1216
|
-
return
|
|
1217
|
-
const
|
|
1218
|
-
return typeof
|
|
1339
|
+
return Effect11.succeed("");
|
|
1340
|
+
const text4 = data["policy"];
|
|
1341
|
+
return typeof text4 === "string" ? Effect11.succeed(text4) : Effect11.fail(new BaoError(200, "GET", policyPath(name), ["no data.policy in the response"]));
|
|
1219
1342
|
});
|
|
1220
1343
|
var writePolicy = (name, hcl) => baoWrite("PUT", policyPath(name), { policy: hcl });
|
|
1221
1344
|
var deletePolicy = (name) => baoDelete(policyPath(name));
|
|
1222
1345
|
|
|
1223
1346
|
// src/openbao/policy.ts
|
|
1224
|
-
var BaoPolicy =
|
|
1347
|
+
var BaoPolicy = Resource6("Bao.Policy", {
|
|
1225
1348
|
defaultRemovalPolicy: "retain"
|
|
1226
1349
|
});
|
|
1227
1350
|
var grantsOf = (hcl) => (hcl.match(/^path /gm) ?? []).length;
|
|
1228
|
-
var
|
|
1351
|
+
var attributesOf6 = (name, hcl, parts) => ({
|
|
1229
1352
|
name,
|
|
1230
1353
|
digest: sha256(hcl),
|
|
1231
1354
|
grants: grantsOf(hcl),
|
|
1232
1355
|
parts
|
|
1233
1356
|
});
|
|
1234
|
-
var BaoPolicyProvider = () =>
|
|
1357
|
+
var BaoPolicyProvider = () => Provider6.effect(BaoPolicy, Effect12.gen(function* () {
|
|
1235
1358
|
const fs = yield* FileSystem.FileSystem;
|
|
1236
1359
|
const path = yield* Path.Path;
|
|
1237
|
-
const assemble = (dir) =>
|
|
1360
|
+
const assemble = (dir) => Effect12.gen(function* () {
|
|
1238
1361
|
const entries2 = (yield* fs.readDirectory(dir)).filter((f) => f.endsWith(".hcl")).sort();
|
|
1239
1362
|
let joined = "";
|
|
1240
1363
|
for (const f of entries2)
|
|
@@ -1243,44 +1366,44 @@ var BaoPolicyProvider = () => Provider5.effect(BaoPolicy, Effect10.gen(function*
|
|
|
1243
1366
|
return { joined, parts: entries2.length };
|
|
1244
1367
|
});
|
|
1245
1368
|
return BaoPolicy.Provider.of({
|
|
1246
|
-
list: () =>
|
|
1247
|
-
read:
|
|
1369
|
+
list: () => Effect12.succeed([]),
|
|
1370
|
+
read: Effect12.fn(function* ({ olds }) {
|
|
1248
1371
|
const live = yield* readPolicy(olds.name);
|
|
1249
|
-
return live.length === 0 ? undefined :
|
|
1372
|
+
return live.length === 0 ? undefined : attributesOf6(olds.name, live, 0);
|
|
1250
1373
|
}),
|
|
1251
|
-
diff:
|
|
1252
|
-
if (output === undefined || !
|
|
1374
|
+
diff: Effect12.fn(function* ({ news, output }) {
|
|
1375
|
+
if (output === undefined || !isResolved6(news))
|
|
1253
1376
|
return;
|
|
1254
1377
|
const { joined } = yield* assemble(news.fragments);
|
|
1255
1378
|
const live = yield* readPolicy(news.name);
|
|
1256
1379
|
return live.length > 0 && sha256(live) === sha256(joined) ? { action: "noop" } : { action: "update" };
|
|
1257
1380
|
}),
|
|
1258
|
-
reconcile:
|
|
1381
|
+
reconcile: Effect12.fn(function* ({ news }) {
|
|
1259
1382
|
const name = news.name;
|
|
1260
1383
|
const { joined, parts } = yield* assemble(news.fragments);
|
|
1261
1384
|
if (isEmptyAssembly(parts, joined))
|
|
1262
|
-
return yield*
|
|
1385
|
+
return yield* Effect12.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.`));
|
|
1263
1386
|
const current = yield* readPolicy(name);
|
|
1264
1387
|
if (current.length > 0 && sha256(current) === sha256(joined)) {
|
|
1265
|
-
return
|
|
1388
|
+
return attributesOf6(name, joined, parts);
|
|
1266
1389
|
}
|
|
1267
1390
|
yield* writePolicy(name, joined);
|
|
1268
|
-
return
|
|
1391
|
+
return attributesOf6(name, joined, parts);
|
|
1269
1392
|
}),
|
|
1270
|
-
delete:
|
|
1393
|
+
delete: Effect12.fn(function* ({ output }) {
|
|
1271
1394
|
yield* deletePolicy(output.name);
|
|
1272
1395
|
return;
|
|
1273
1396
|
})
|
|
1274
1397
|
});
|
|
1275
1398
|
}));
|
|
1276
1399
|
// src/openbao/proxmox-role.ts
|
|
1277
|
-
import { Resource as
|
|
1278
|
-
import { isResolved as
|
|
1279
|
-
import * as
|
|
1280
|
-
import * as
|
|
1400
|
+
import { Resource as Resource7 } from "alchemy";
|
|
1401
|
+
import { isResolved as isResolved7 } from "alchemy/Diff";
|
|
1402
|
+
import * as Provider7 from "alchemy/Provider";
|
|
1403
|
+
import * as Effect13 from "effect/Effect";
|
|
1281
1404
|
|
|
1282
1405
|
// src/openbao/proxmox-role-form.ts
|
|
1283
|
-
var
|
|
1406
|
+
var text4 = (value) => typeof value === "string" ? value : "";
|
|
1284
1407
|
var ttlText3 = (seconds3, fallback = "0") => {
|
|
1285
1408
|
if (seconds3 === undefined)
|
|
1286
1409
|
return fallback;
|
|
@@ -1293,11 +1416,11 @@ var ttlText3 = (seconds3, fallback = "0") => {
|
|
|
1293
1416
|
return `${String(seconds3)}s`;
|
|
1294
1417
|
};
|
|
1295
1418
|
var rolePath3 = (mount, name) => `${mountPath(mount)}/roles/${name}`;
|
|
1296
|
-
var
|
|
1419
|
+
var attributesOf7 = (props, live) => {
|
|
1297
1420
|
const attrs = {
|
|
1298
1421
|
mount: mountPath(props.mount),
|
|
1299
|
-
name:
|
|
1300
|
-
mintUser:
|
|
1422
|
+
name: text4(live["name"]) || props.name,
|
|
1423
|
+
mintUser: text4(live["mint_user"]),
|
|
1301
1424
|
ttl: ttlText3(ttlSeconds(live["ttl"])),
|
|
1302
1425
|
maxTtl: ttlText3(ttlSeconds(live["max_ttl"]))
|
|
1303
1426
|
};
|
|
@@ -1318,74 +1441,74 @@ var writeBody4 = (props) => {
|
|
|
1318
1441
|
body: { mint_user: props.mintUser, ttl: String(ttl), max_ttl: String(maxTtl) }
|
|
1319
1442
|
};
|
|
1320
1443
|
};
|
|
1321
|
-
var
|
|
1444
|
+
var sameTtl5 = (want, have) => {
|
|
1322
1445
|
const wantSeconds = parseDuration(want);
|
|
1323
1446
|
const haveSeconds = ttlSeconds(have);
|
|
1324
1447
|
return wantSeconds !== undefined && wantSeconds === haveSeconds;
|
|
1325
1448
|
};
|
|
1326
|
-
var
|
|
1449
|
+
var matches5 = (attributes, props) => attributes.mintUser === props.mintUser && sameTtl5(props.ttl, attributes.ttl) && sameTtl5(props.maxTtl, attributes.maxTtl);
|
|
1327
1450
|
|
|
1328
1451
|
// src/openbao/proxmox-role.ts
|
|
1329
|
-
var BaoProxmoxRole =
|
|
1452
|
+
var BaoProxmoxRole = Resource7("Bao.ProxmoxRole", {
|
|
1330
1453
|
defaultRemovalPolicy: "retain"
|
|
1331
1454
|
});
|
|
1332
|
-
var readRole3 = (props) =>
|
|
1455
|
+
var readRole3 = (props) => Effect13.gen(function* () {
|
|
1333
1456
|
const live = yield* baoRead(rolePath3(props.mount, props.name));
|
|
1334
1457
|
if (live === undefined)
|
|
1335
1458
|
return;
|
|
1336
|
-
return
|
|
1459
|
+
return attributesOf7(props, live);
|
|
1337
1460
|
});
|
|
1338
|
-
var BaoProxmoxRoleProvider = () =>
|
|
1339
|
-
list: () =>
|
|
1340
|
-
read:
|
|
1461
|
+
var BaoProxmoxRoleProvider = () => Provider7.effect(BaoProxmoxRole, Effect13.succeed(BaoProxmoxRole.Provider.of({
|
|
1462
|
+
list: () => Effect13.succeed([]),
|
|
1463
|
+
read: Effect13.fn(function* ({ olds }) {
|
|
1341
1464
|
return yield* readRole3(olds);
|
|
1342
1465
|
}),
|
|
1343
|
-
diff:
|
|
1344
|
-
if (output === undefined || !
|
|
1466
|
+
diff: Effect13.fn(function* ({ news, output }) {
|
|
1467
|
+
if (output === undefined || !isResolved7(news))
|
|
1345
1468
|
return;
|
|
1346
1469
|
const live = yield* readRole3(news);
|
|
1347
1470
|
if (live === undefined)
|
|
1348
1471
|
return { action: "update" };
|
|
1349
|
-
if (
|
|
1472
|
+
if (matches5(live, news))
|
|
1350
1473
|
return { action: "noop" };
|
|
1351
1474
|
return { action: "update" };
|
|
1352
1475
|
}),
|
|
1353
|
-
reconcile:
|
|
1476
|
+
reconcile: Effect13.fn(function* ({ news }) {
|
|
1354
1477
|
const live = yield* readRole3(news);
|
|
1355
1478
|
if (live !== undefined && live.mintUser !== news.mintUser && news.allowMintUserChange !== live.mintUser) {
|
|
1356
|
-
return yield*
|
|
1479
|
+
return yield* Effect13.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}'.`));
|
|
1357
1480
|
}
|
|
1358
|
-
if (live === undefined || !
|
|
1481
|
+
if (live === undefined || !matches5(live, news)) {
|
|
1359
1482
|
const write = writeBody4(news);
|
|
1360
1483
|
if (!write.ok) {
|
|
1361
|
-
return yield*
|
|
1484
|
+
return yield* Effect13.die(new Error(`Bao.ProxmoxRole ${rolePath3(news.mount, news.name)}: unparseable duration ` + `${write.bad.join(", ")}. Use 30s / 5m / 1h / 1d, or 0.`));
|
|
1362
1485
|
}
|
|
1363
1486
|
yield* baoWrite("PUT", rolePath3(news.mount, news.name), write.body);
|
|
1364
1487
|
}
|
|
1365
1488
|
const after = yield* readRole3(news);
|
|
1366
1489
|
if (after === undefined) {
|
|
1367
|
-
return yield*
|
|
1490
|
+
return yield* Effect13.die(new Error(`Bao.ProxmoxRole ${rolePath3(news.mount, news.name)}: write returned no error ` + "but the role is still absent."));
|
|
1368
1491
|
}
|
|
1369
1492
|
return after;
|
|
1370
1493
|
}),
|
|
1371
|
-
delete:
|
|
1494
|
+
delete: Effect13.fn(function* ({ output }) {
|
|
1372
1495
|
yield* baoDelete(rolePath3(output.mount, output.name));
|
|
1373
1496
|
return;
|
|
1374
1497
|
})
|
|
1375
1498
|
})));
|
|
1376
1499
|
// src/openbao/ssh-role.ts
|
|
1377
|
-
import { Resource as
|
|
1378
|
-
import { isResolved as
|
|
1379
|
-
import * as
|
|
1380
|
-
import * as
|
|
1500
|
+
import { Resource as Resource8 } from "alchemy";
|
|
1501
|
+
import { isResolved as isResolved8 } from "alchemy/Diff";
|
|
1502
|
+
import * as Provider8 from "alchemy/Provider";
|
|
1503
|
+
import * as Effect14 from "effect/Effect";
|
|
1381
1504
|
|
|
1382
1505
|
// src/openbao/ssh-role-form.ts
|
|
1383
1506
|
var mountOf = (mount) => (mount ?? "ssh").replace(/\/+$/, "");
|
|
1384
|
-
var
|
|
1385
|
-
var
|
|
1507
|
+
var readPath4 = (mount, name) => `${mountOf(mount)}/roles/${name}`;
|
|
1508
|
+
var text5 = (value) => typeof value === "string" ? value : "";
|
|
1386
1509
|
var bool3 = (value, fallback) => typeof value === "boolean" ? value : fallback;
|
|
1387
1510
|
var csvList = (value) => {
|
|
1388
|
-
const parts = Array.isArray(value) ? value.filter((entry) => typeof entry === "string") :
|
|
1511
|
+
const parts = Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : text5(value).split(",");
|
|
1389
1512
|
return [
|
|
1390
1513
|
...new Set(parts.map((entry) => entry.trim()).filter((entry) => entry.length > 0))
|
|
1391
1514
|
].sort();
|
|
@@ -1396,7 +1519,7 @@ var extMap = (value) => {
|
|
|
1396
1519
|
return out;
|
|
1397
1520
|
const source = value;
|
|
1398
1521
|
for (const key of Object.keys(source).sort())
|
|
1399
|
-
out[key] =
|
|
1522
|
+
out[key] = text5(source[key]);
|
|
1400
1523
|
return out;
|
|
1401
1524
|
};
|
|
1402
1525
|
var resolve = (props) => ({
|
|
@@ -1417,12 +1540,12 @@ var resolve = (props) => ({
|
|
|
1417
1540
|
ttl: props.ttl,
|
|
1418
1541
|
maxTtl: props.maxTtl
|
|
1419
1542
|
});
|
|
1420
|
-
var
|
|
1543
|
+
var attributesOf8 = (form, live) => {
|
|
1421
1544
|
const attrs = {
|
|
1422
1545
|
name: form.name,
|
|
1423
1546
|
mount: form.mount,
|
|
1424
1547
|
allowedUsers: csvList(live["allowed_users"]),
|
|
1425
|
-
defaultUser:
|
|
1548
|
+
defaultUser: text5(live["default_user"]),
|
|
1426
1549
|
allowUserCertificates: bool3(live["allow_user_certificates"], false),
|
|
1427
1550
|
allowHostCertificates: bool3(live["allow_host_certificates"], false),
|
|
1428
1551
|
allowedDomains: csvList(live["allowed_domains"]),
|
|
@@ -1433,7 +1556,7 @@ var attributesOf7 = (form, live) => {
|
|
|
1433
1556
|
allowedCriticalOptions: csvList(live["allowed_critical_options"]),
|
|
1434
1557
|
defaultCriticalOptions: extMap(live["default_critical_options"]),
|
|
1435
1558
|
allowEmptyPrincipals: bool3(live["allow_empty_principals"], false),
|
|
1436
|
-
keyType:
|
|
1559
|
+
keyType: text5(live["key_type"]) || "ca",
|
|
1437
1560
|
ttlSeconds: ttlSeconds(live["ttl"]) ?? 0,
|
|
1438
1561
|
maxTtlSeconds: ttlSeconds(live["max_ttl"]) ?? 0
|
|
1439
1562
|
};
|
|
@@ -1462,19 +1585,19 @@ var sameMap = (want, have) => {
|
|
|
1462
1585
|
const keys = Object.keys(want);
|
|
1463
1586
|
return keys.length === Object.keys(have).length && keys.every((key) => have[key] === want[key]);
|
|
1464
1587
|
};
|
|
1465
|
-
var
|
|
1588
|
+
var sameTtl6 = (want, have) => parseDuration(want) === have;
|
|
1466
1589
|
var badDurations2 = (form) => ["ttl", "maxTtl"].filter((field) => parseDuration(form[field]) === undefined);
|
|
1467
|
-
var
|
|
1590
|
+
var matches6 = (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);
|
|
1468
1591
|
|
|
1469
1592
|
// src/openbao/ssh-role.ts
|
|
1470
|
-
var BaoSshRole =
|
|
1593
|
+
var BaoSshRole = Resource8("Bao.SshRole", {
|
|
1471
1594
|
defaultRemovalPolicy: "retain"
|
|
1472
1595
|
});
|
|
1473
|
-
var readRole4 = (form) =>
|
|
1474
|
-
const live = yield* baoRead(
|
|
1596
|
+
var readRole4 = (form) => Effect14.gen(function* () {
|
|
1597
|
+
const live = yield* baoRead(readPath4(form.mount, form.name));
|
|
1475
1598
|
if (live === undefined)
|
|
1476
1599
|
return;
|
|
1477
|
-
return { attributes:
|
|
1600
|
+
return { attributes: attributesOf8(form, live), live };
|
|
1478
1601
|
});
|
|
1479
1602
|
var UNMANAGED_DEFAULTS = {
|
|
1480
1603
|
algorithm_signer: "default",
|
|
@@ -1496,15 +1619,15 @@ var wouldErase = (live) => {
|
|
|
1496
1619
|
}
|
|
1497
1620
|
return lost.sort();
|
|
1498
1621
|
};
|
|
1499
|
-
var refuse = (message) =>
|
|
1500
|
-
var BaoSshRoleProvider = () =>
|
|
1501
|
-
list: () =>
|
|
1502
|
-
read:
|
|
1622
|
+
var refuse = (message) => Effect14.die(new Error(message));
|
|
1623
|
+
var BaoSshRoleProvider = () => Provider8.effect(BaoSshRole, Effect14.succeed(BaoSshRole.Provider.of({
|
|
1624
|
+
list: () => Effect14.succeed([]),
|
|
1625
|
+
read: Effect14.fn(function* ({ olds }) {
|
|
1503
1626
|
const found = yield* readRole4(resolve(olds));
|
|
1504
1627
|
return found?.attributes;
|
|
1505
1628
|
}),
|
|
1506
|
-
diff:
|
|
1507
|
-
if (output === undefined || !
|
|
1629
|
+
diff: Effect14.fn(function* ({ news, output }) {
|
|
1630
|
+
if (output === undefined || !isResolved8(news))
|
|
1508
1631
|
return;
|
|
1509
1632
|
const form = resolve(news);
|
|
1510
1633
|
if (output.mount !== form.mount || output.name !== form.name) {
|
|
@@ -1513,11 +1636,11 @@ var BaoSshRoleProvider = () => Provider7.effect(BaoSshRole, Effect12.succeed(Bao
|
|
|
1513
1636
|
const found = yield* readRole4(form);
|
|
1514
1637
|
if (found === undefined)
|
|
1515
1638
|
return { action: "update" };
|
|
1516
|
-
return
|
|
1639
|
+
return matches6(found.attributes, form) ? { action: "noop" } : { action: "update" };
|
|
1517
1640
|
}),
|
|
1518
|
-
reconcile:
|
|
1641
|
+
reconcile: Effect14.fn(function* ({ news }) {
|
|
1519
1642
|
const form = resolve(news);
|
|
1520
|
-
const path =
|
|
1643
|
+
const path = readPath4(form.mount, form.name);
|
|
1521
1644
|
const bad = badDurations2(form);
|
|
1522
1645
|
if (bad.length > 0) {
|
|
1523
1646
|
return yield* refuse(`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.");
|
|
@@ -1526,7 +1649,7 @@ var BaoSshRoleProvider = () => Provider7.effect(BaoSshRole, Effect12.succeed(Bao
|
|
|
1526
1649
|
return yield* refuse(`Bao.SshRole ${path}: allowUserCertificates with an empty allowedUsers and ` + "allowEmptyPrincipals false. Every sign request against it would be rejected.");
|
|
1527
1650
|
}
|
|
1528
1651
|
const found = yield* readRole4(form);
|
|
1529
|
-
if (found === undefined || !
|
|
1652
|
+
if (found === undefined || !matches6(found.attributes, form)) {
|
|
1530
1653
|
if (found !== undefined) {
|
|
1531
1654
|
const lost = wouldErase(found.live);
|
|
1532
1655
|
if (lost.length > 0) {
|
|
@@ -1541,12 +1664,14 @@ var BaoSshRoleProvider = () => Provider7.effect(BaoSshRole, Effect12.succeed(Bao
|
|
|
1541
1664
|
}
|
|
1542
1665
|
return after.attributes;
|
|
1543
1666
|
}),
|
|
1544
|
-
delete:
|
|
1545
|
-
yield* baoDelete(
|
|
1667
|
+
delete: Effect14.fn(function* ({ output }) {
|
|
1668
|
+
yield* baoDelete(readPath4(output.mount, output.name));
|
|
1546
1669
|
return;
|
|
1547
1670
|
})
|
|
1548
1671
|
})));
|
|
1549
1672
|
export {
|
|
1673
|
+
BaoAuthMethod,
|
|
1674
|
+
BaoAuthMethodProvider,
|
|
1550
1675
|
BaoAuthRole,
|
|
1551
1676
|
BaoAuthRoleProvider,
|
|
1552
1677
|
BaoCloudflareRole,
|
|
@@ -1570,5 +1695,5 @@ export {
|
|
|
1570
1695
|
permissionGroupsFromLiveRoles
|
|
1571
1696
|
};
|
|
1572
1697
|
|
|
1573
|
-
//# debugId=
|
|
1698
|
+
//# debugId=8FBBE010DA02328C64756E2164756E21
|
|
1574
1699
|
//# sourceMappingURL=index.js.map
|