@homeflare/alchemy 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -48
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/launchd/fake-runner.d.ts +36 -0
- package/dist/launchd/fake-runner.d.ts.map +1 -0
- package/dist/launchd/host-effect.d.ts +19 -0
- package/dist/launchd/host-effect.d.ts.map +1 -0
- package/dist/launchd/host-file-form.d.ts +24 -0
- package/dist/launchd/host-file-form.d.ts.map +1 -0
- package/dist/launchd/host-file-lifecycle.d.ts +29 -0
- package/dist/launchd/host-file-lifecycle.d.ts.map +1 -0
- package/dist/launchd/host-file.d.ts +25 -0
- package/dist/launchd/host-file.d.ts.map +1 -0
- package/dist/launchd/host-lookup.d.ts +27 -0
- package/dist/launchd/host-lookup.d.ts.map +1 -0
- package/dist/launchd/index.d.ts +21 -0
- package/dist/launchd/index.d.ts.map +1 -0
- package/dist/launchd/index.js +969 -0
- package/dist/launchd/index.js.map +25 -0
- package/dist/launchd/job-form.d.ts +90 -0
- package/dist/launchd/job-form.d.ts.map +1 -0
- package/dist/launchd/job-lifecycle.d.ts +34 -0
- package/dist/launchd/job-lifecycle.d.ts.map +1 -0
- package/dist/launchd/job-preflight.d.ts +50 -0
- package/dist/launchd/job-preflight.d.ts.map +1 -0
- package/dist/launchd/job-validate.d.ts +25 -0
- package/dist/launchd/job-validate.d.ts.map +1 -0
- package/dist/launchd/job.d.ts +34 -0
- package/dist/launchd/job.d.ts.map +1 -0
- package/dist/launchd/launchctl.d.ts +62 -0
- package/dist/launchd/launchctl.d.ts.map +1 -0
- package/dist/launchd/local-runner.d.ts +7 -0
- package/dist/launchd/local-runner.d.ts.map +1 -0
- package/dist/launchd/plist.d.ts +41 -0
- package/dist/launchd/plist.d.ts.map +1 -0
- package/dist/launchd/providers.d.ts +15 -0
- package/dist/launchd/providers.d.ts.map +1 -0
- package/dist/launchd/runner.d.ts +91 -0
- package/dist/launchd/runner.d.ts.map +1 -0
- package/dist/launchd/secret-tripwire.d.ts +23 -0
- package/dist/launchd/secret-tripwire.d.ts.map +1 -0
- package/dist/openbao/auth-method-form.d.ts +7 -0
- package/dist/openbao/auth-method-form.d.ts.map +1 -1
- package/dist/openbao/auth-method-reconcile.d.ts +15 -0
- package/dist/openbao/auth-method-reconcile.d.ts.map +1 -0
- package/dist/openbao/auth-method.d.ts +6 -0
- package/dist/openbao/auth-method.d.ts.map +1 -1
- package/dist/openbao/auth-role-form.d.ts +9 -0
- package/dist/openbao/auth-role-form.d.ts.map +1 -1
- package/dist/openbao/auth-role.d.ts.map +1 -1
- package/dist/openbao/auth-token-form.d.ts +48 -0
- package/dist/openbao/auth-token-form.d.ts.map +1 -0
- package/dist/openbao/bao-identity.d.ts +81 -0
- package/dist/openbao/bao-identity.d.ts.map +1 -0
- package/dist/openbao/bao-namespace.d.ts +20 -0
- package/dist/openbao/bao-namespace.d.ts.map +1 -0
- package/dist/openbao/cloudflare-roles-expand.d.ts.map +1 -1
- package/dist/openbao/host-approles.d.ts +62 -0
- package/dist/openbao/host-approles.d.ts.map +1 -0
- package/dist/openbao/index.d.ts +19 -0
- package/dist/openbao/index.d.ts.map +1 -1
- package/dist/openbao/index.js +1433 -305
- package/dist/openbao/index.js.map +34 -13
- package/dist/openbao/jwt-config-form.d.ts +51 -0
- package/dist/openbao/jwt-config-form.d.ts.map +1 -0
- package/dist/openbao/jwt-config.d.ts +25 -0
- package/dist/openbao/jwt-config.d.ts.map +1 -0
- package/dist/openbao/jwt-role-form.d.ts +91 -0
- package/dist/openbao/jwt-role-form.d.ts.map +1 -0
- package/dist/openbao/jwt-role.d.ts +31 -0
- package/dist/openbao/jwt-role.d.ts.map +1 -0
- package/dist/openbao/kubernetes-role-form.d.ts +68 -0
- package/dist/openbao/kubernetes-role-form.d.ts.map +1 -0
- package/dist/openbao/kubernetes-role.d.ts +25 -0
- package/dist/openbao/kubernetes-role.d.ts.map +1 -0
- package/dist/openbao/mfa-enforcement-form.d.ts +46 -0
- package/dist/openbao/mfa-enforcement-form.d.ts.map +1 -0
- package/dist/openbao/mfa-enforcement.d.ts +41 -0
- package/dist/openbao/mfa-enforcement.d.ts.map +1 -0
- package/dist/openbao/mfa-totp-form.d.ts +52 -0
- package/dist/openbao/mfa-totp-form.d.ts.map +1 -0
- package/dist/openbao/mfa-totp.d.ts +33 -0
- package/dist/openbao/mfa-totp.d.ts.map +1 -0
- package/dist/openbao/mfa-wire.d.ts +63 -0
- package/dist/openbao/mfa-wire.d.ts.map +1 -0
- package/dist/openbao/mount-form.d.ts +7 -0
- package/dist/openbao/mount-form.d.ts.map +1 -1
- package/dist/openbao/mount-move.d.ts +49 -0
- package/dist/openbao/mount-move.d.ts.map +1 -0
- package/dist/openbao/mount-path.d.ts +8 -4
- package/dist/openbao/mount-path.d.ts.map +1 -1
- package/dist/openbao/mount-reconcile.d.ts +16 -0
- package/dist/openbao/mount-reconcile.d.ts.map +1 -0
- package/dist/openbao/mount.d.ts +9 -0
- package/dist/openbao/mount.d.ts.map +1 -1
- package/dist/openbao/pki-role-canonical.d.ts +46 -0
- package/dist/openbao/pki-role-canonical.d.ts.map +1 -0
- package/dist/openbao/pki-role-form.d.ts +38 -30
- package/dist/openbao/pki-role-form.d.ts.map +1 -1
- package/dist/openbao/pki-role.d.ts.map +1 -1
- package/dist/openbao/remount-wire.d.ts +44 -0
- package/dist/openbao/remount-wire.d.ts.map +1 -0
- package/dist/openbao/role-reconcile.d.ts +40 -0
- package/dist/openbao/role-reconcile.d.ts.map +1 -0
- package/docs/launchd.md +167 -0
- package/package.json +7 -2
- package/src/index.ts +1 -0
- package/src/launchd/fake-runner.ts +176 -0
- package/src/launchd/host-effect.ts +28 -0
- package/src/launchd/host-file-form.ts +71 -0
- package/src/launchd/host-file-lifecycle.test.ts +198 -0
- package/src/launchd/host-file-lifecycle.ts +164 -0
- package/src/launchd/host-file.ts +64 -0
- package/src/launchd/host-lookup.test.ts +92 -0
- package/src/launchd/host-lookup.ts +94 -0
- package/src/launchd/index.ts +26 -0
- package/src/launchd/job-form.test.ts +171 -0
- package/src/launchd/job-form.ts +180 -0
- package/src/launchd/job-identity.test.ts +147 -0
- package/src/launchd/job-lifecycle.test.ts +220 -0
- package/src/launchd/job-lifecycle.ts +194 -0
- package/src/launchd/job-preflight.ts +152 -0
- package/src/launchd/job-validate.ts +134 -0
- package/src/launchd/job.ts +96 -0
- package/src/launchd/launchctl.test.ts +181 -0
- package/src/launchd/launchctl.ts +188 -0
- package/src/launchd/local-runner.test.ts +125 -0
- package/src/launchd/local-runner.ts +143 -0
- package/src/launchd/plist.test.ts +186 -0
- package/src/launchd/plist.ts +156 -0
- package/src/launchd/providers.test.ts +222 -0
- package/src/launchd/providers.ts +21 -0
- package/src/launchd/runner.ts +95 -0
- package/src/launchd/secret-tripwire.test.ts +110 -0
- package/src/launchd/secret-tripwire.ts +81 -0
- package/src/openbao/README.md +181 -0
- package/src/openbao/REPLACE.md +87 -0
- package/src/openbao/auth-method-form.ts +7 -0
- package/src/openbao/auth-method-reconcile.ts +74 -0
- package/src/openbao/auth-method.ts +20 -41
- package/src/openbao/auth-role-form.test.ts +16 -1
- package/src/openbao/auth-role-form.ts +11 -0
- package/src/openbao/auth-role.ts +12 -0
- package/src/openbao/auth-token-form.ts +161 -0
- package/src/openbao/bao-identity.test.ts +131 -0
- package/src/openbao/bao-identity.ts +188 -0
- package/src/openbao/bao-namespace.ts +32 -0
- package/src/openbao/cloudflare-roles-expand.ts +2 -5
- package/src/openbao/host-approles.test.ts +121 -0
- package/src/openbao/host-approles.ts +126 -0
- package/src/openbao/index.ts +19 -0
- package/src/openbao/jwt-config-form.ts +161 -0
- package/src/openbao/jwt-config.ts +85 -0
- package/src/openbao/jwt-role-form.test.ts +130 -0
- package/src/openbao/jwt-role-form.ts +231 -0
- package/src/openbao/jwt-role.ts +102 -0
- package/src/openbao/kubernetes-role-form.test.ts +82 -0
- package/src/openbao/kubernetes-role-form.ts +149 -0
- package/src/openbao/kubernetes-role.ts +94 -0
- package/src/openbao/mfa-enforcement-form.ts +147 -0
- package/src/openbao/mfa-enforcement.ts +143 -0
- package/src/openbao/mfa-totp-form.ts +155 -0
- package/src/openbao/mfa-totp.ts +118 -0
- package/src/openbao/mfa-wire.ts +117 -0
- package/src/openbao/mfa.test.ts +200 -0
- package/src/openbao/mount-form.ts +9 -1
- package/src/openbao/mount-move.test.ts +164 -0
- package/src/openbao/mount-move.ts +113 -0
- package/src/openbao/mount-path.test.ts +11 -1
- package/src/openbao/mount-path.ts +17 -11
- package/src/openbao/mount-reconcile.ts +75 -0
- package/src/openbao/mount.ts +26 -43
- package/src/openbao/pki-role-canonical.ts +163 -0
- package/src/openbao/pki-role-form.test.ts +112 -0
- package/src/openbao/pki-role-form.ts +76 -110
- package/src/openbao/pki-role.ts +12 -27
- package/src/openbao/remount-wire.test.ts +87 -0
- package/src/openbao/remount-wire.ts +112 -0
- package/src/openbao/role-reconcile.test.ts +104 -0
- package/src/openbao/role-reconcile.ts +84 -0
package/dist/openbao/index.js
CHANGED
|
@@ -140,18 +140,20 @@ var baoWrite = (method, path, body) => Effect.asVoid(baoCall("write", method, pa
|
|
|
140
140
|
var baoDelete = (path) => Effect.asVoid(baoCall("delete", "DELETE", path));
|
|
141
141
|
|
|
142
142
|
// src/openbao/mount-path.ts
|
|
143
|
-
var
|
|
143
|
+
var trimTrailing = (text, char) => {
|
|
144
144
|
let end = text.length;
|
|
145
|
-
while (end > 0 && text[end - 1] ===
|
|
145
|
+
while (end > 0 && text[end - 1] === char)
|
|
146
146
|
end -= 1;
|
|
147
147
|
return text.slice(0, end);
|
|
148
148
|
};
|
|
149
|
-
var
|
|
149
|
+
var trimRuns = (text, char) => {
|
|
150
150
|
let start = 0;
|
|
151
|
-
while (start < text.length && text[start] ===
|
|
151
|
+
while (start < text.length && text[start] === char)
|
|
152
152
|
start += 1;
|
|
153
|
-
return
|
|
153
|
+
return trimTrailing(text.slice(start), char);
|
|
154
154
|
};
|
|
155
|
+
var trimTrailingSlashes = (text) => trimTrailing(text, "/");
|
|
156
|
+
var trimSlashes = (text) => trimRuns(text, "/");
|
|
155
157
|
var sshMountOf = (mount) => trimTrailingSlashes(mount ?? "ssh");
|
|
156
158
|
|
|
157
159
|
// src/openbao/approle-login-form.ts
|
|
@@ -295,14 +297,14 @@ var revokeSelf = (token, env) => runWith(env, revokeSelfEffect(token));
|
|
|
295
297
|
import { Resource } from "alchemy";
|
|
296
298
|
import { isResolved } from "alchemy/Diff";
|
|
297
299
|
import * as Provider from "alchemy/Provider";
|
|
298
|
-
import * as
|
|
300
|
+
import * as Effect7 from "effect/Effect";
|
|
299
301
|
|
|
300
302
|
// src/openbao/digest.ts
|
|
301
303
|
var canonical = (text) => text.trimEnd();
|
|
302
304
|
var sha256 = (text) => new Bun.CryptoHasher("sha256").update(canonical(text)).digest("hex");
|
|
303
305
|
|
|
304
306
|
// src/openbao/mount-form.ts
|
|
305
|
-
var mountPath = (path) => path
|
|
307
|
+
var mountPath = (path) => trimTrailingSlashes(path);
|
|
306
308
|
var DURATION = /^(\d+)([smhd])$/;
|
|
307
309
|
var parseDuration = (text) => {
|
|
308
310
|
const trimmed = text.trim();
|
|
@@ -435,6 +437,9 @@ var sameTtl2 = (want, have) => {
|
|
|
435
437
|
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);
|
|
436
438
|
var wantsTune2 = (props) => props.description !== undefined || props.defaultLeaseTtl !== undefined || props.maxLeaseTtl !== undefined;
|
|
437
439
|
|
|
440
|
+
// src/openbao/auth-method-reconcile.ts
|
|
441
|
+
import * as Effect6 from "effect/Effect";
|
|
442
|
+
|
|
438
443
|
// src/openbao/auth-method-wire.ts
|
|
439
444
|
import * as Effect3 from "effect/Effect";
|
|
440
445
|
var absentFromTable = (path, asked) => baoRead("sys/auth").pipe(Effect3.mapError((listing) => new BaoError(asked.status, asked.method, asked.path, [
|
|
@@ -446,24 +451,150 @@ var enableAuthMethod = (props) => baoWrite("POST", readPath2(props.path), enable
|
|
|
446
451
|
var tuneAuthMethod = (props) => baoWrite("POST", tunePath2(props.path), tuneBody2(props));
|
|
447
452
|
var disableAuthMethod = (path) => baoDelete(readPath2(path));
|
|
448
453
|
|
|
449
|
-
// src/openbao/
|
|
450
|
-
|
|
451
|
-
|
|
454
|
+
// src/openbao/mount-move.ts
|
|
455
|
+
import * as Effect5 from "effect/Effect";
|
|
456
|
+
|
|
457
|
+
// src/openbao/remount-wire.ts
|
|
458
|
+
import * as Effect4 from "effect/Effect";
|
|
459
|
+
class RemountError extends Error {
|
|
460
|
+
constructor(message) {
|
|
461
|
+
super(message);
|
|
462
|
+
this.name = "RemountError";
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
var record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
466
|
+
var startRemount = (from, to) => Effect4.flatMap(baoCall("write", "POST", "sys/remount", { from, to }), (body) => {
|
|
467
|
+
const id = record(body?.["data"])?.["migration_id"];
|
|
468
|
+
return typeof id === "string" && id !== "" ? Effect4.succeed(id) : Effect4.fail(new RemountError(`sys/remount ${from} -> ${to} answered no migration_id.`));
|
|
469
|
+
});
|
|
470
|
+
var remountStatus = (id) => Effect4.map(baoRead(`sys/remount/status/${encodeURIComponent(id)}`), (data) => {
|
|
471
|
+
if (data === undefined)
|
|
472
|
+
return;
|
|
473
|
+
const status = record(data["migration_info"])?.["status"];
|
|
474
|
+
return typeof status === "string" ? status : "unknown";
|
|
475
|
+
});
|
|
476
|
+
var remountAndWait = (from, to, confirm, options = {}) => Effect4.gen(function* () {
|
|
477
|
+
const interval = options.intervalMillis ?? 1000;
|
|
478
|
+
const attempts = Math.max(1, options.attempts ?? 120);
|
|
479
|
+
const id = yield* startRemount(from, to);
|
|
480
|
+
const named = `sys/remount ${from} -> ${to} (migration ${id})`;
|
|
481
|
+
for (let attempt = 1;attempt <= attempts; attempt += 1) {
|
|
482
|
+
const status = yield* remountStatus(id);
|
|
483
|
+
if (status === "success")
|
|
484
|
+
return;
|
|
485
|
+
if (status === "failure") {
|
|
486
|
+
return yield* Effect4.fail(new RemountError(`${named} FAILED. The server log carries the reason, tagged with that migration_id. ` + "Read sys/mounts (or sys/auth) before retrying: the source may be tainted."));
|
|
487
|
+
}
|
|
488
|
+
if (status === undefined) {
|
|
489
|
+
if (yield* confirm)
|
|
490
|
+
return;
|
|
491
|
+
return yield* Effect4.fail(new RemountError(`${named}: the server no longer knows the migration (its tracker is in memory, lost ` + "on a restart or leader change), and the mount table does not show the move done."));
|
|
492
|
+
}
|
|
493
|
+
if (attempt < attempts)
|
|
494
|
+
yield* Effect4.sleep(interval);
|
|
495
|
+
}
|
|
496
|
+
return yield* Effect4.fail(new RemountError(`${named} is still in progress after ${String(attempts)} status reads. Do not re-run ` + `until sys/remount/status/${id} settles.`));
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
// src/openbao/mount-move.ts
|
|
500
|
+
var planMove = (family, stated, path, remountFrom) => {
|
|
501
|
+
const to = mountPath(path);
|
|
502
|
+
const from = remountFrom === undefined ? undefined : mountPath(remountFrom);
|
|
503
|
+
if (stated === undefined) {
|
|
504
|
+
return from === undefined || from === to ? { kind: "stay" } : { kind: "move", from };
|
|
505
|
+
}
|
|
506
|
+
const was = mountPath(stated);
|
|
507
|
+
if (was === to)
|
|
508
|
+
return { kind: "stay" };
|
|
509
|
+
if (from === undefined) {
|
|
510
|
+
return {
|
|
511
|
+
kind: "refuse",
|
|
512
|
+
message: `${family}: path changed from ${was} to ${to}. That would enable an EMPTY mount at ${to} ` + `and strand everything under ${was}. To move it with its data, declare ` + `\`remountFrom: '${was}'\` (leases under it are revoked; policies are not rewritten). ` + "For a new, empty mount, give it a new resource id instead."
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
if (from !== was) {
|
|
516
|
+
return {
|
|
517
|
+
kind: "refuse",
|
|
518
|
+
message: `${family}: remountFrom is ${from}, but the mount in state is ${was}.`
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
return { kind: "move", from };
|
|
522
|
+
};
|
|
523
|
+
var refuse = (message) => Effect5.die(new Error(message));
|
|
524
|
+
var performMove = (target, from, options) => Effect5.gen(function* () {
|
|
525
|
+
const to = mountPath(target.path);
|
|
526
|
+
const source = yield* target.read(from);
|
|
527
|
+
const landed = yield* target.read(to);
|
|
528
|
+
if (source !== undefined && landed !== undefined) {
|
|
529
|
+
return yield* refuse(`${target.family}: both ${from} and ${to} exist, so remountFrom cannot move one onto the ` + "other. Decide which holds the data, and retire the other by hand.");
|
|
530
|
+
}
|
|
531
|
+
if (source === undefined && landed === undefined) {
|
|
532
|
+
return yield* refuse(`${target.family}: remountFrom ${from} names no mount, and ${to} does not exist either. ` + "Refusing to enable an empty mount where data was expected.");
|
|
533
|
+
}
|
|
534
|
+
if (source === undefined)
|
|
535
|
+
return;
|
|
536
|
+
if (source.type !== target.type) {
|
|
537
|
+
return yield* refuse(`${target.family}: ${from} is type ${source.type}, not ${target.type}. A remount keeps the ` + "type; fix the declaration first.");
|
|
538
|
+
}
|
|
539
|
+
const confirm = Effect5.map(Effect5.all([target.read(from), target.read(to)]), ([gone, there]) => gone === undefined && there !== undefined);
|
|
540
|
+
yield* remountAndWait(target.wire(from), target.wire(to), confirm, options);
|
|
452
541
|
});
|
|
453
|
-
|
|
542
|
+
|
|
543
|
+
// src/openbao/auth-method-reconcile.ts
|
|
544
|
+
var readMethod = (props) => Effect6.gen(function* () {
|
|
454
545
|
const live = yield* readAuthMethodData(props.path);
|
|
455
546
|
if (live === undefined)
|
|
456
547
|
return;
|
|
457
548
|
return attributesOf2(props, live);
|
|
458
549
|
});
|
|
459
|
-
var
|
|
460
|
-
|
|
461
|
-
|
|
550
|
+
var moveTarget = (props) => ({
|
|
551
|
+
family: "Bao.AuthMethod",
|
|
552
|
+
path: props.path,
|
|
553
|
+
read: (path) => readMethod({ ...props, path }),
|
|
554
|
+
type: props.type,
|
|
555
|
+
wire: (path) => `auth/${authPath(path)}`
|
|
556
|
+
});
|
|
557
|
+
var reconcileAuthMethod = (news, stated, options) => Effect6.gen(function* () {
|
|
558
|
+
const move = planMove("Bao.AuthMethod", stated, news.path, news.remountFrom);
|
|
559
|
+
if (move.kind === "refuse")
|
|
560
|
+
return yield* Effect6.die(new Error(move.message));
|
|
561
|
+
if (move.kind === "move")
|
|
562
|
+
yield* performMove(moveTarget(news), move.from, options);
|
|
563
|
+
let live = yield* readMethod(news);
|
|
564
|
+
if (live === undefined) {
|
|
565
|
+
yield* enableAuthMethod(news);
|
|
566
|
+
if (wantsTune2(news))
|
|
567
|
+
yield* tuneAuthMethod(news);
|
|
568
|
+
} else if (!matches2(live, news)) {
|
|
569
|
+
if (live.type !== news.type) {
|
|
570
|
+
return yield* Effect6.die(new Error(`Bao.AuthMethod ${authPath(news.path)}: live type ${live.type} != ${news.type}. ` + "Auth method type is immutable — replace manually."));
|
|
571
|
+
}
|
|
572
|
+
yield* tuneAuthMethod(news);
|
|
573
|
+
}
|
|
574
|
+
live = yield* readMethod(news);
|
|
575
|
+
if (live === undefined) {
|
|
576
|
+
return yield* Effect6.die(new Error(`Bao.AuthMethod ${authPath(news.path)}: write returned no error but the method is still absent.`));
|
|
577
|
+
}
|
|
578
|
+
return live;
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// src/openbao/auth-method.ts
|
|
582
|
+
var BaoAuthMethod = Resource("Bao.AuthMethod", {
|
|
583
|
+
defaultRemovalPolicy: "retain"
|
|
584
|
+
});
|
|
585
|
+
var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect7.succeed(BaoAuthMethod.Provider.of({
|
|
586
|
+
list: () => Effect7.succeed([]),
|
|
587
|
+
read: Effect7.fn(function* ({ olds }) {
|
|
462
588
|
return yield* readMethod(olds);
|
|
463
589
|
}),
|
|
464
|
-
diff:
|
|
590
|
+
diff: Effect7.fn(function* ({ news, output }) {
|
|
465
591
|
if (output === undefined || !isResolved(news))
|
|
466
592
|
return;
|
|
593
|
+
const move = planMove("Bao.AuthMethod", output.path, news.path, news.remountFrom);
|
|
594
|
+
if (move.kind === "refuse")
|
|
595
|
+
return yield* Effect7.die(new Error(move.message));
|
|
596
|
+
if (move.kind === "move")
|
|
597
|
+
return { action: "update" };
|
|
467
598
|
const live = yield* readMethod(news);
|
|
468
599
|
if (live === undefined)
|
|
469
600
|
return { action: "update" };
|
|
@@ -473,25 +604,10 @@ var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect4.succeed
|
|
|
473
604
|
return { action: "noop" };
|
|
474
605
|
return { action: "update" };
|
|
475
606
|
}),
|
|
476
|
-
reconcile:
|
|
477
|
-
|
|
478
|
-
if (live === undefined) {
|
|
479
|
-
yield* enableAuthMethod(news);
|
|
480
|
-
if (wantsTune2(news))
|
|
481
|
-
yield* tuneAuthMethod(news);
|
|
482
|
-
} else if (!matches2(live, news)) {
|
|
483
|
-
if (live.type !== news.type) {
|
|
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."));
|
|
485
|
-
}
|
|
486
|
-
yield* tuneAuthMethod(news);
|
|
487
|
-
}
|
|
488
|
-
live = yield* readMethod(news);
|
|
489
|
-
if (live === undefined) {
|
|
490
|
-
return yield* Effect4.die(new Error(`Bao.AuthMethod ${authPath(news.path)}: write returned no error but the method is still absent.`));
|
|
491
|
-
}
|
|
492
|
-
return live;
|
|
607
|
+
reconcile: Effect7.fn(function* ({ news, output }) {
|
|
608
|
+
return yield* reconcileAuthMethod(news, output?.path);
|
|
493
609
|
}),
|
|
494
|
-
delete:
|
|
610
|
+
delete: Effect7.fn(function* ({ output }) {
|
|
495
611
|
yield* disableAuthMethod(output.path);
|
|
496
612
|
return;
|
|
497
613
|
})
|
|
@@ -500,7 +616,7 @@ var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect4.succeed
|
|
|
500
616
|
import { Resource as Resource2 } from "alchemy";
|
|
501
617
|
import { isResolved as isResolved2 } from "alchemy/Diff";
|
|
502
618
|
import * as Provider2 from "alchemy/Provider";
|
|
503
|
-
import * as
|
|
619
|
+
import * as Effect8 from "effect/Effect";
|
|
504
620
|
|
|
505
621
|
// src/openbao/auth-role-form.ts
|
|
506
622
|
var policiesOf = (live) => {
|
|
@@ -549,6 +665,7 @@ var writeBody = (props) => {
|
|
|
549
665
|
}
|
|
550
666
|
return body;
|
|
551
667
|
};
|
|
668
|
+
var isRename = (from, to) => from.toLowerCase() !== to.toLowerCase();
|
|
552
669
|
var samePolicies = (want, have) => want.length === have.length && want.every((policy, index) => policy === have[index]);
|
|
553
670
|
var sameTtl3 = (want, have) => {
|
|
554
671
|
const wantSeconds = parseDuration(want);
|
|
@@ -564,20 +681,22 @@ var matches3 = (attributes, props) => {
|
|
|
564
681
|
var BaoAuthRole = Resource2("Bao.AuthRole", {
|
|
565
682
|
defaultRemovalPolicy: "retain"
|
|
566
683
|
});
|
|
567
|
-
var readRole = (props) =>
|
|
684
|
+
var readRole = (props) => Effect8.gen(function* () {
|
|
568
685
|
const live = yield* baoRead(readPath3(props.name));
|
|
569
686
|
if (live === undefined)
|
|
570
687
|
return;
|
|
571
688
|
return attributesOf3(props, live);
|
|
572
689
|
});
|
|
573
|
-
var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole,
|
|
574
|
-
list: () =>
|
|
575
|
-
read:
|
|
690
|
+
var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect8.succeed(BaoAuthRole.Provider.of({
|
|
691
|
+
list: () => Effect8.succeed([]),
|
|
692
|
+
read: Effect8.fn(function* ({ olds }) {
|
|
576
693
|
return yield* readRole(olds);
|
|
577
694
|
}),
|
|
578
|
-
diff:
|
|
695
|
+
diff: Effect8.fn(function* ({ news, output }) {
|
|
579
696
|
if (output === undefined || !isResolved2(news))
|
|
580
697
|
return;
|
|
698
|
+
if (isRename(output.name, news.name))
|
|
699
|
+
return { action: "replace" };
|
|
581
700
|
const live = yield* readRole(news);
|
|
582
701
|
if (live === undefined)
|
|
583
702
|
return { action: "update" };
|
|
@@ -585,27 +704,107 @@ var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect5.succeed(Ba
|
|
|
585
704
|
return { action: "noop" };
|
|
586
705
|
return { action: "update" };
|
|
587
706
|
}),
|
|
588
|
-
reconcile:
|
|
707
|
+
reconcile: Effect8.fn(function* ({ news }) {
|
|
589
708
|
const live = yield* readRole(news);
|
|
590
709
|
if (live === undefined || !matches3(live, news)) {
|
|
591
710
|
yield* baoWrite("PUT", readPath3(news.name), writeBody(news));
|
|
592
711
|
}
|
|
593
712
|
const after = yield* readRole(news);
|
|
594
713
|
if (after === undefined) {
|
|
595
|
-
return yield*
|
|
714
|
+
return yield* Effect8.die(new Error(`Bao.AuthRole ${news.name}: write returned no error but the role is still absent.`));
|
|
596
715
|
}
|
|
597
716
|
return after;
|
|
598
717
|
}),
|
|
599
|
-
delete:
|
|
718
|
+
delete: Effect8.fn(function* ({ output }) {
|
|
600
719
|
yield* baoDelete(readPath3(output.name));
|
|
601
720
|
return;
|
|
602
721
|
})
|
|
603
722
|
})));
|
|
723
|
+
// src/openbao/bao-identity.ts
|
|
724
|
+
import * as Effect9 from "effect/Effect";
|
|
725
|
+
import * as FetchHttpClient3 from "effect/unstable/http/FetchHttpClient";
|
|
726
|
+
|
|
727
|
+
// src/openbao/bao-namespace.ts
|
|
728
|
+
var canonicalNamespace = (namespace) => {
|
|
729
|
+
const bare = trimSlashes((namespace ?? "").trim());
|
|
730
|
+
return bare === "root" ? "" : bare;
|
|
731
|
+
};
|
|
732
|
+
var namespacePath = (namespace) => {
|
|
733
|
+
const bare = canonicalNamespace(namespace);
|
|
734
|
+
return bare === "" ? "" : `${bare}/`;
|
|
735
|
+
};
|
|
736
|
+
var envNamespace = (env) => canonicalNamespace(resolveAddress(env).namespace);
|
|
737
|
+
var namespaceLabel = (namespace) => namespace === "" ? "root" : namespace;
|
|
738
|
+
|
|
739
|
+
// src/openbao/bao-identity.ts
|
|
740
|
+
class BaoIdentityError extends Error {
|
|
741
|
+
reason;
|
|
742
|
+
constructor(reason, detail) {
|
|
743
|
+
super(`REFUSING: ${detail}`);
|
|
744
|
+
this.reason = reason;
|
|
745
|
+
this.name = "BaoIdentityError";
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
var HEALTH_PATH = "sys/health?standbyok=true&sealedcode=200&uninitcode=200";
|
|
749
|
+
var NAMESPACE_PROBE_PATH = "sys/internal/ui/mounts";
|
|
750
|
+
var addressOf = (env) => {
|
|
751
|
+
const address = resolveAddress(env);
|
|
752
|
+
return address.socket === undefined ? address.base : `unix://${address.socket}`;
|
|
753
|
+
};
|
|
754
|
+
var unreachable = (address, what) => (error) => new BaoIdentityError("unreachable", `${address} did not answer ${what}: ${error.message}`);
|
|
755
|
+
var text3 = (value) => typeof value === "string" ? value : "";
|
|
756
|
+
var checkHealth = (address, expectedCluster, body) => {
|
|
757
|
+
if (body === undefined || typeof body["sealed"] !== "boolean") {
|
|
758
|
+
return new BaoIdentityError("unconfirmed", `${address} answered sys/health, but not as OpenBao.`);
|
|
759
|
+
}
|
|
760
|
+
if (body["initialized"] === false) {
|
|
761
|
+
return new BaoIdentityError("unconfirmed", `${address} is not initialised.`);
|
|
762
|
+
}
|
|
763
|
+
if (body["sealed"] === true) {
|
|
764
|
+
return new BaoIdentityError("unconfirmed", `${address} is sealed, and a sealed vault does not report its cluster_name.`);
|
|
765
|
+
}
|
|
766
|
+
const clusterName = text3(body["cluster_name"]);
|
|
767
|
+
if (clusterName !== expectedCluster) {
|
|
768
|
+
return new BaoIdentityError("mismatch", `${address} answers as cluster \`${clusterName || "(none)"}\`, but this stack expects ` + `\`${expectedCluster}\`. Check BAO_ADDR and BAO_AGENT_ADDR (the agent address wins).`);
|
|
769
|
+
}
|
|
770
|
+
return { clusterId: text3(body["cluster_id"]), version: text3(body["version"]) };
|
|
771
|
+
};
|
|
772
|
+
var assertBaoIdentityEffect = (expected) => Effect9.gen(function* () {
|
|
773
|
+
const env = yield* BaoEnv;
|
|
774
|
+
const address = addressOf(env);
|
|
775
|
+
const clusterName = expected.clusterName.trim();
|
|
776
|
+
if (clusterName === "") {
|
|
777
|
+
return yield* Effect9.fail(new BaoIdentityError("input", "clusterName is empty, so there is nothing to compare."));
|
|
778
|
+
}
|
|
779
|
+
const namespace = canonicalNamespace(expected.namespace);
|
|
780
|
+
const sent = envNamespace(env);
|
|
781
|
+
if (sent !== namespace) {
|
|
782
|
+
return yield* Effect9.fail(new BaoIdentityError("mismatch", `BAO_NAMESPACE resolves to \`${namespaceLabel(sent)}\`, but this stack expects ` + `\`${namespaceLabel(namespace)}\`.`));
|
|
783
|
+
}
|
|
784
|
+
const anonymous = { ...env, BAO_NAMESPACE: "", BAO_TOKEN: "" };
|
|
785
|
+
const body = yield* baoCall("read", "GET", HEALTH_PATH).pipe(Effect9.provideService(BaoEnv, anonymous), Effect9.mapError(unreachable(address, "sys/health")));
|
|
786
|
+
const health = checkHealth(address, clusterName, body);
|
|
787
|
+
if (health instanceof BaoIdentityError)
|
|
788
|
+
return yield* Effect9.fail(health);
|
|
789
|
+
if (namespace !== "") {
|
|
790
|
+
const scoped = { ...env, BAO_NAMESPACE: namespace, BAO_TOKEN: "" };
|
|
791
|
+
const probe = yield* baoCall("read", "GET", NAMESPACE_PROBE_PATH).pipe(Effect9.provideService(BaoEnv, scoped), Effect9.mapError(unreachable(address, `the namespace probe for \`${namespace}\``)));
|
|
792
|
+
if (probe === undefined) {
|
|
793
|
+
return yield* Effect9.fail(new BaoIdentityError("mismatch", `cluster \`${clusterName}\` at ${address} has no namespace \`${namespace}\`.`));
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return { address, clusterName, namespace, ...health };
|
|
797
|
+
});
|
|
798
|
+
var assertBaoIdentity = (input) => {
|
|
799
|
+
const effect3 = assertBaoIdentityEffect(input);
|
|
800
|
+
const scoped = input.env === undefined ? effect3 : Effect9.provideService(effect3, BaoEnv, input.env);
|
|
801
|
+
return Effect9.runPromise(scoped.pipe(Effect9.provide(FetchHttpClient3.layer)));
|
|
802
|
+
};
|
|
604
803
|
// src/openbao/cloudflare-role.ts
|
|
605
804
|
import { Resource as Resource3 } from "alchemy";
|
|
606
805
|
import { isResolved as isResolved3 } from "alchemy/Diff";
|
|
607
806
|
import * as Provider3 from "alchemy/Provider";
|
|
608
|
-
import * as
|
|
807
|
+
import * as Effect12 from "effect/Effect";
|
|
609
808
|
|
|
610
809
|
// src/openbao/cloudflare-group-scope.ts
|
|
611
810
|
var zoneResource = (zoneId) => `com.cloudflare.api.account.zone.${zoneId}`;
|
|
@@ -625,7 +824,7 @@ var groupKey = (name, scope) => `${scope} ${name}`;
|
|
|
625
824
|
|
|
626
825
|
// src/openbao/cloudflare-permission-groups.ts
|
|
627
826
|
import * as Context2 from "effect/Context";
|
|
628
|
-
import * as
|
|
827
|
+
import * as Effect11 from "effect/Effect";
|
|
629
828
|
|
|
630
829
|
// src/openbao/cloudflare-permission-groups-solve.ts
|
|
631
830
|
var observationsOf = (declared, live) => declared.flatMap((role) => {
|
|
@@ -795,7 +994,7 @@ var samePolicies2 = (want, have) => want.length === have.length && want.every((w
|
|
|
795
994
|
});
|
|
796
995
|
|
|
797
996
|
// src/openbao/cloudflare-role-wire.ts
|
|
798
|
-
import * as
|
|
997
|
+
import * as Effect10 from "effect/Effect";
|
|
799
998
|
|
|
800
999
|
// src/openbao/cloudflare-role-form.ts
|
|
801
1000
|
var rolePath = (mount, name) => `${mountPath(mount)}/roles/${name}`;
|
|
@@ -860,7 +1059,7 @@ var differences = (props, policies, live) => {
|
|
|
860
1059
|
};
|
|
861
1060
|
|
|
862
1061
|
// src/openbao/cloudflare-role-wire.ts
|
|
863
|
-
var readCloudflareRole = (mount, name) =>
|
|
1062
|
+
var readCloudflareRole = (mount, name) => Effect10.map(baoRead(rolePath(mount, name)), (data) => data === undefined ? undefined : liveRoleOf(data));
|
|
864
1063
|
var writeCloudflareRole = (mount, name, body) => baoWrite("PUT", rolePath(mount, name), body);
|
|
865
1064
|
var deleteCloudflareRole = (mount, name) => baoDelete(rolePath(mount, name));
|
|
866
1065
|
|
|
@@ -882,9 +1081,9 @@ var pick = (mount, groups, refs, hint) => {
|
|
|
882
1081
|
const unknown = refs.filter((ref) => !groups.has(groupKey(ref.name, ref.scope)));
|
|
883
1082
|
if (unknown.length > 0) {
|
|
884
1083
|
const problems = unknown.map((ref) => `unknown group ${JSON.stringify(ref.name)} at scope ${ref.scope} — ${hint}`);
|
|
885
|
-
return
|
|
1084
|
+
return Effect11.fail(new CloudflareGroupsError(mount, problems));
|
|
886
1085
|
}
|
|
887
|
-
return
|
|
1086
|
+
return Effect11.succeed(new Map(refs.flatMap((ref) => {
|
|
888
1087
|
const key = groupKey(ref.name, ref.scope);
|
|
889
1088
|
const id = groups.get(key);
|
|
890
1089
|
return id === undefined ? [] : [[key, id]];
|
|
@@ -892,15 +1091,15 @@ var pick = (mount, groups, refs, hint) => {
|
|
|
892
1091
|
};
|
|
893
1092
|
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
894
1093
|
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";
|
|
895
|
-
var deniedNamed = (mount, read) =>
|
|
896
|
-
var engineGroups = (mount) =>
|
|
1094
|
+
var deniedNamed = (mount, read) => Effect11.catchIf(read, (error) => error.status === 403, () => Effect11.fail(new CloudflareGroupsError(mount, [deniedHint(mount)])));
|
|
1095
|
+
var engineGroups = (mount) => Effect11.flatMap(deniedNamed(mount, baoRead(`${mountPath(mount)}/permission-groups`)), (data) => {
|
|
897
1096
|
const list = data?.["permission_groups"];
|
|
898
1097
|
if (data === undefined) {
|
|
899
1098
|
const why = "GET <mount>/permission-groups answered 404 — the endpoint is not deployed here";
|
|
900
|
-
return
|
|
1099
|
+
return Effect11.fail(new CloudflareGroupsError(mount, [why]));
|
|
901
1100
|
}
|
|
902
1101
|
if (!Array.isArray(list)) {
|
|
903
|
-
return
|
|
1102
|
+
return Effect11.fail(new CloudflareGroupsError(mount, ["no permission_groups array in reply"]));
|
|
904
1103
|
}
|
|
905
1104
|
const scoped = new Map;
|
|
906
1105
|
const idsOf = new Map;
|
|
@@ -927,24 +1126,24 @@ var engineGroups = (mount) => Effect7.flatMap(deniedNamed(mount, baoRead(`${moun
|
|
|
927
1126
|
const only = [...ids];
|
|
928
1127
|
return ids.size === 1 && only[0] !== undefined ? [[name, only[0]]] : [];
|
|
929
1128
|
}));
|
|
930
|
-
return problems.length > 0 ?
|
|
1129
|
+
return problems.length > 0 ? Effect11.fail(new CloudflareGroupsError(mount, problems)) : Effect11.succeed(new Map([...atEveryScope(sole), ...scoped]));
|
|
931
1130
|
});
|
|
932
|
-
var permissionGroupsFromEngine = () =>
|
|
1131
|
+
var permissionGroupsFromEngine = () => Effect11.sync(() => {
|
|
933
1132
|
const cache = new Map;
|
|
934
|
-
const cachedFor = (mount) =>
|
|
1133
|
+
const cachedFor = (mount) => Effect11.suspend(() => {
|
|
935
1134
|
const hit = cache.get(mountPath(mount));
|
|
936
1135
|
if (hit !== undefined)
|
|
937
1136
|
return hit;
|
|
938
|
-
return
|
|
1137
|
+
return Effect11.flatMap(Effect11.cached(engineGroups(mount)), (lookup) => {
|
|
939
1138
|
cache.set(mountPath(mount), lookup);
|
|
940
1139
|
return lookup;
|
|
941
1140
|
});
|
|
942
1141
|
});
|
|
943
1142
|
return CloudflarePermissionGroups.of({
|
|
944
|
-
resolve: (mount, refs) =>
|
|
1143
|
+
resolve: (mount, refs) => Effect11.flatMap(cachedFor(mount), (groups) => pick(mount, groups, refs, "the engine does not list it for this account at that scope"))
|
|
945
1144
|
});
|
|
946
1145
|
});
|
|
947
|
-
var permissionGroupsFromLiveRoles = (declared) =>
|
|
1146
|
+
var permissionGroupsFromLiveRoles = (declared) => Effect11.gen(function* () {
|
|
948
1147
|
const accountOf = new Map;
|
|
949
1148
|
const byAccount = new Map;
|
|
950
1149
|
for (const [mount, roles] of declared) {
|
|
@@ -955,9 +1154,9 @@ var permissionGroupsFromLiveRoles = (declared) => Effect7.gen(function* () {
|
|
|
955
1154
|
}
|
|
956
1155
|
const solved = new Map;
|
|
957
1156
|
for (const [account, roles] of byAccount) {
|
|
958
|
-
const solve =
|
|
1157
|
+
const solve = Effect11.gen(function* () {
|
|
959
1158
|
const live = new Map;
|
|
960
|
-
yield*
|
|
1159
|
+
yield* Effect11.all(roles.map((role) => Effect11.map(readCloudflareRole(role.mount, role.name), (read) => {
|
|
961
1160
|
if (read?.policies !== undefined) {
|
|
962
1161
|
live.set(`${role.mount}/${role.name}`, read.policies);
|
|
963
1162
|
}
|
|
@@ -965,11 +1164,11 @@ var permissionGroupsFromLiveRoles = (declared) => Effect7.gen(function* () {
|
|
|
965
1164
|
const result = solveGroups(observationsOf(roles, live));
|
|
966
1165
|
const problems = describeProblems(result);
|
|
967
1166
|
if (problems.length > 0) {
|
|
968
|
-
return yield*
|
|
1167
|
+
return yield* Effect11.fail(new CloudflareGroupsError(`account ${account}`, problems));
|
|
969
1168
|
}
|
|
970
1169
|
return atEveryScope(result.groups);
|
|
971
1170
|
});
|
|
972
|
-
solved.set(account, yield*
|
|
1171
|
+
solved.set(account, yield* Effect11.cached(solve));
|
|
973
1172
|
}
|
|
974
1173
|
return CloudflarePermissionGroups.of({
|
|
975
1174
|
resolve: (mount, refs) => {
|
|
@@ -977,10 +1176,10 @@ var permissionGroupsFromLiveRoles = (declared) => Effect7.gen(function* () {
|
|
|
977
1176
|
const lookup = account === undefined ? undefined : solved.get(account);
|
|
978
1177
|
if (account === undefined || lookup === undefined) {
|
|
979
1178
|
const why = "no declared role on this mount, so no live role can vouch for its groups";
|
|
980
|
-
return
|
|
1179
|
+
return Effect11.fail(new CloudflareGroupsError(mount, [why]));
|
|
981
1180
|
}
|
|
982
1181
|
const hint = `no live role on account ${account} carries it; a NEW group needs the engine endpoint`;
|
|
983
|
-
return
|
|
1182
|
+
return Effect11.flatMap(lookup, (groups) => pick(mount, groups, refs, hint));
|
|
984
1183
|
}
|
|
985
1184
|
});
|
|
986
1185
|
});
|
|
@@ -989,9 +1188,9 @@ var permissionGroupsFromLiveRoles = (declared) => Effect7.gen(function* () {
|
|
|
989
1188
|
var BaoCloudflareRole = Resource3("Bao.CloudflareRole", {
|
|
990
1189
|
defaultRemovalPolicy: "retain"
|
|
991
1190
|
});
|
|
992
|
-
var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole,
|
|
1191
|
+
var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect12.gen(function* () {
|
|
993
1192
|
const groups = yield* CloudflarePermissionGroups;
|
|
994
|
-
const resolved = (props) =>
|
|
1193
|
+
const resolved = (props) => Effect12.gen(function* () {
|
|
995
1194
|
const refs = new Map;
|
|
996
1195
|
for (const policy of props.policies) {
|
|
997
1196
|
const scope = policy.scope ?? scopeOfResource(policy.resource);
|
|
@@ -1001,17 +1200,17 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
|
|
|
1001
1200
|
const ids = yield* groups.resolve(props.mount, [...refs.values()]);
|
|
1002
1201
|
const { missing, policies } = resolvePolicies(props.policies, ids);
|
|
1003
1202
|
if (missing.length > 0) {
|
|
1004
|
-
return yield*
|
|
1203
|
+
return yield* Effect12.die(new Error(`Bao.CloudflareRole ${rolePath(props.mount, props.name)}: groups ` + `${missing.join(", ")} came back unresolved from a lookup that reported success.`));
|
|
1005
1204
|
}
|
|
1006
1205
|
return policies;
|
|
1007
1206
|
});
|
|
1008
1207
|
return BaoCloudflareRole.Provider.of({
|
|
1009
|
-
list: () =>
|
|
1010
|
-
read:
|
|
1208
|
+
list: () => Effect12.succeed([]),
|
|
1209
|
+
read: Effect12.fn(function* ({ olds }) {
|
|
1011
1210
|
const live = yield* readCloudflareRole(olds.mount, olds.name);
|
|
1012
1211
|
return live === undefined ? undefined : attributesOf4(olds, live);
|
|
1013
1212
|
}),
|
|
1014
|
-
diff:
|
|
1213
|
+
diff: Effect12.fn(function* ({ news, output }) {
|
|
1015
1214
|
if (output === undefined || !isResolved3(news))
|
|
1016
1215
|
return;
|
|
1017
1216
|
if (refusalOf(news) !== undefined)
|
|
@@ -1022,11 +1221,11 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
|
|
|
1022
1221
|
return { action: "update" };
|
|
1023
1222
|
return differences(news, policies, live).length === 0 ? { action: "noop" } : { action: "update" };
|
|
1024
1223
|
}),
|
|
1025
|
-
reconcile:
|
|
1224
|
+
reconcile: Effect12.fn(function* ({ news }) {
|
|
1026
1225
|
const path = rolePath(news.mount, news.name);
|
|
1027
1226
|
const refusal2 = refusalOf(news);
|
|
1028
1227
|
if (refusal2 !== undefined) {
|
|
1029
|
-
return yield*
|
|
1228
|
+
return yield* Effect12.die(new Error(`Bao.CloudflareRole ${path}: ${refusal2}.`));
|
|
1030
1229
|
}
|
|
1031
1230
|
const policies = yield* resolved(news);
|
|
1032
1231
|
const live = yield* readCloudflareRole(news.mount, news.name);
|
|
@@ -1035,22 +1234,22 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
|
|
|
1035
1234
|
}
|
|
1036
1235
|
const after = yield* readCloudflareRole(news.mount, news.name);
|
|
1037
1236
|
if (after === undefined) {
|
|
1038
|
-
return yield*
|
|
1237
|
+
return yield* Effect12.die(new Error(`Bao.CloudflareRole ${path}: the write succeeded but the role is absent.`));
|
|
1039
1238
|
}
|
|
1040
1239
|
const left = differences(news, policies, after);
|
|
1041
1240
|
if (left.length > 0) {
|
|
1042
|
-
return yield*
|
|
1241
|
+
return yield* Effect12.die(new Error(`Bao.CloudflareRole ${path}: wrote cleanly but reads back different in ` + `${left.map((d) => d.field).join(", ")}. Inspect the role before retrying.`));
|
|
1043
1242
|
}
|
|
1044
1243
|
return attributesOf4(news, after);
|
|
1045
1244
|
}),
|
|
1046
|
-
delete:
|
|
1245
|
+
delete: Effect12.fn(function* ({ output }) {
|
|
1047
1246
|
yield* deleteCloudflareRole(output.mount, output.name);
|
|
1048
1247
|
return;
|
|
1049
1248
|
})
|
|
1050
1249
|
});
|
|
1051
1250
|
}));
|
|
1052
1251
|
// src/openbao/cloudflare-roles-expand.ts
|
|
1053
|
-
var slug = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-")
|
|
1252
|
+
var slug = (name) => trimRuns(name.toLowerCase().replace(/[^a-z0-9]+/g, "-"), "-");
|
|
1054
1253
|
var zoneSlug = (zone) => `${slug(zone.name)}${zone.internal ? "-internal" : ""}`;
|
|
1055
1254
|
var mountName = (account, surface) => `cloudflare-${account}-${surface}`;
|
|
1056
1255
|
var policiesFor = (tpl, accountId, zone, accountGroups) => {
|
|
@@ -1145,12 +1344,12 @@ class RolesConfigError extends Error {
|
|
|
1145
1344
|
}
|
|
1146
1345
|
}
|
|
1147
1346
|
var isRaw = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1148
|
-
var
|
|
1347
|
+
var record2 = (value, where) => {
|
|
1149
1348
|
if (!isRaw(value))
|
|
1150
1349
|
throw new RolesConfigError(where, "expected a mapping");
|
|
1151
1350
|
return value;
|
|
1152
1351
|
};
|
|
1153
|
-
var
|
|
1352
|
+
var text4 = (raw, key, where) => {
|
|
1154
1353
|
const value = raw[key];
|
|
1155
1354
|
if (typeof value === "string")
|
|
1156
1355
|
return value;
|
|
@@ -1174,35 +1373,35 @@ var entries = (raw, key, where) => {
|
|
|
1174
1373
|
return value;
|
|
1175
1374
|
};
|
|
1176
1375
|
var template = (value, where) => {
|
|
1177
|
-
const raw =
|
|
1376
|
+
const raw = record2(value, where);
|
|
1178
1377
|
const groups = texts(raw, "groups", where);
|
|
1179
1378
|
if (groups === undefined)
|
|
1180
1379
|
throw new RolesConfigError(`${where}.groups`, "is required");
|
|
1181
|
-
const bucket = raw["bucket"] === undefined ? undefined :
|
|
1380
|
+
const bucket = raw["bucket"] === undefined ? undefined : text4(raw, "bucket", where);
|
|
1182
1381
|
if (bucket !== undefined && bucket.length === 0) {
|
|
1183
1382
|
throw new RolesConfigError(`${where}.bucket`, "is empty; omit it to stay account-wide");
|
|
1184
1383
|
}
|
|
1185
|
-
const groupsScope = raw["groups_scope"] === undefined ? undefined : groupScopeNamed(
|
|
1384
|
+
const groupsScope = raw["groups_scope"] === undefined ? undefined : groupScopeNamed(text4(raw, "groups_scope", where));
|
|
1186
1385
|
if (raw["groups_scope"] !== undefined && groupsScope === undefined) {
|
|
1187
1386
|
throw new RolesConfigError(`${where}.groups_scope`, `expected one of ${Object.keys(GROUP_SCOPES).join(", ")}`);
|
|
1188
1387
|
}
|
|
1189
1388
|
return {
|
|
1190
1389
|
accountGroups: texts(raw, "account_groups", where) ?? [],
|
|
1191
1390
|
bucket,
|
|
1192
|
-
bucketJurisdiction: raw["bucket_jurisdiction"] === undefined ? "default" :
|
|
1193
|
-
desc: raw["desc"] === undefined ? "" :
|
|
1391
|
+
bucketJurisdiction: raw["bucket_jurisdiction"] === undefined ? "default" : text4(raw, "bucket_jurisdiction", where),
|
|
1392
|
+
desc: raw["desc"] === undefined ? "" : text4(raw, "desc", where),
|
|
1194
1393
|
groups,
|
|
1195
1394
|
groupsScope,
|
|
1196
|
-
maxTtl:
|
|
1197
|
-
name:
|
|
1198
|
-
ttl:
|
|
1395
|
+
maxTtl: text4(raw, "max_ttl", where),
|
|
1396
|
+
name: text4(raw, "name", where),
|
|
1397
|
+
ttl: text4(raw, "ttl", where),
|
|
1199
1398
|
zoneTypes: texts(raw, "zone_types", where) ?? [],
|
|
1200
1399
|
zones: texts(raw, "zones", where)
|
|
1201
1400
|
};
|
|
1202
1401
|
};
|
|
1203
1402
|
var surface = (name, value) => {
|
|
1204
1403
|
const where = `surfaces.${name}`;
|
|
1205
|
-
const raw =
|
|
1404
|
+
const raw = record2(value, where);
|
|
1206
1405
|
return {
|
|
1207
1406
|
accountRoles: entries(raw, "account_roles", where).map((item, i) => template(item, `${where}.account_roles[${String(i)}]`)),
|
|
1208
1407
|
zoneRoles: entries(raw, "zone_roles", where).map((item, i) => template(item, `${where}.zone_roles[${String(i)}]`))
|
|
@@ -1210,7 +1409,7 @@ var surface = (name, value) => {
|
|
|
1210
1409
|
};
|
|
1211
1410
|
var account = (key, value, surfaces) => {
|
|
1212
1411
|
const where = `accounts.${key}`;
|
|
1213
|
-
const raw =
|
|
1412
|
+
const raw = record2(value, where);
|
|
1214
1413
|
const names = texts(raw, "surfaces", where) ?? [];
|
|
1215
1414
|
for (const name of names) {
|
|
1216
1415
|
if (surfaces[name] === undefined) {
|
|
@@ -1218,56 +1417,941 @@ var account = (key, value, surfaces) => {
|
|
|
1218
1417
|
}
|
|
1219
1418
|
}
|
|
1220
1419
|
const zones = raw["zones"] === undefined || raw["zones"] === "all" ? "all" : texts(raw, "zones", where);
|
|
1221
|
-
return { id:
|
|
1420
|
+
return { id: text4(raw, "id", where), key, surfaces: names, zones: zones ?? "all" };
|
|
1222
1421
|
};
|
|
1223
1422
|
var parseRolesConfig = (yaml) => {
|
|
1224
|
-
const root =
|
|
1423
|
+
const root = record2(Bun.YAML.parse(yaml), "(document)");
|
|
1225
1424
|
const surfaces = {};
|
|
1226
|
-
for (const [name, spec] of Object.entries(
|
|
1425
|
+
for (const [name, spec] of Object.entries(record2(root["surfaces"], "surfaces"))) {
|
|
1227
1426
|
surfaces[name] = surface(name, spec);
|
|
1228
1427
|
}
|
|
1229
|
-
const accounts = Object.entries(
|
|
1428
|
+
const accounts = Object.entries(record2(root["accounts"], "accounts")).map(([key, value]) => account(key, value, surfaces));
|
|
1230
1429
|
return {
|
|
1231
1430
|
accounts,
|
|
1232
|
-
namespace:
|
|
1431
|
+
namespace: text4(record2(root["defaults"], "defaults"), "namespace", "defaults"),
|
|
1233
1432
|
surfaces
|
|
1234
1433
|
};
|
|
1235
1434
|
};
|
|
1236
|
-
// src/openbao/
|
|
1435
|
+
// src/openbao/host-approles.ts
|
|
1436
|
+
var NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
1437
|
+
var HOST_ROLE_SEPARATOR = "--";
|
|
1438
|
+
var hostRoleName = (className, host) => `${className}${HOST_ROLE_SEPARATOR}${host}`;
|
|
1439
|
+
var classProblems = (name, spec) => {
|
|
1440
|
+
const found = [];
|
|
1441
|
+
if (!NAME.test(name))
|
|
1442
|
+
found.push(`class \`${name}\` is not lowercase letters, digits, single hyphens`);
|
|
1443
|
+
if (spec.policies.length === 0)
|
|
1444
|
+
found.push(`class \`${name}\` has no policies`);
|
|
1445
|
+
if (spec.policies.includes("root"))
|
|
1446
|
+
found.push(`class \`${name}\` grants \`root\``);
|
|
1447
|
+
const secretId = parseDuration(spec.secretIdTtl);
|
|
1448
|
+
if (secretId === undefined || secretId <= 0) {
|
|
1449
|
+
found.push(`class \`${name}\` secretIdTtl \`${spec.secretIdTtl}\` must be a duration above zero ` + "(0 never expires)");
|
|
1450
|
+
}
|
|
1451
|
+
const ttl = parseDuration(spec.tokenTtl);
|
|
1452
|
+
const max = parseDuration(spec.tokenMaxTtl);
|
|
1453
|
+
if (ttl === undefined || max === undefined) {
|
|
1454
|
+
found.push(`class \`${name}\` token TTLs must be durations like 15m or 1h`);
|
|
1455
|
+
} else if (max > 0 && ttl > max) {
|
|
1456
|
+
found.push(`class \`${name}\` tokenTtl is longer than tokenMaxTtl`);
|
|
1457
|
+
}
|
|
1458
|
+
return found;
|
|
1459
|
+
};
|
|
1460
|
+
var hostAppRoles = (input) => {
|
|
1461
|
+
const problems = Object.entries(input.classes).flatMap(([name, spec]) => classProblems(name, spec));
|
|
1462
|
+
const seen = new Set;
|
|
1463
|
+
const roles = [];
|
|
1464
|
+
for (const host of input.hosts) {
|
|
1465
|
+
if (!NAME.test(host.name)) {
|
|
1466
|
+
problems.push(`host \`${host.name}\` is not lowercase letters, digits, single hyphens`);
|
|
1467
|
+
}
|
|
1468
|
+
if (seen.has(host.name))
|
|
1469
|
+
problems.push(`host \`${host.name}\` is listed twice`);
|
|
1470
|
+
seen.add(host.name);
|
|
1471
|
+
const spec = input.classes[host.class];
|
|
1472
|
+
if (spec === undefined) {
|
|
1473
|
+
problems.push(`host \`${host.name}\` names unknown class \`${host.class}\``);
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
roles.push({
|
|
1477
|
+
bindSecretId: true,
|
|
1478
|
+
name: hostRoleName(host.class, host.name),
|
|
1479
|
+
secretIdNumUses: spec.secretIdNumUses ?? 0,
|
|
1480
|
+
secretIdTtl: spec.secretIdTtl,
|
|
1481
|
+
tokenMaxTtl: spec.tokenMaxTtl,
|
|
1482
|
+
tokenPolicies: [...new Set(spec.policies)].sort(),
|
|
1483
|
+
tokenTtl: spec.tokenTtl
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
if (problems.length > 0) {
|
|
1487
|
+
throw new Error(`hostAppRoles refused the input: ${problems.join("; ")}.`);
|
|
1488
|
+
}
|
|
1489
|
+
return roles.sort((a, b) => a.name.localeCompare(b.name));
|
|
1490
|
+
};
|
|
1491
|
+
// src/openbao/jwt-config.ts
|
|
1237
1492
|
import { Resource as Resource4 } from "alchemy";
|
|
1238
1493
|
import { isResolved as isResolved4 } from "alchemy/Diff";
|
|
1239
1494
|
import * as Provider4 from "alchemy/Provider";
|
|
1240
|
-
import * as
|
|
1495
|
+
import * as Effect14 from "effect/Effect";
|
|
1496
|
+
|
|
1497
|
+
// src/openbao/jwt-config-form.ts
|
|
1498
|
+
var text5 = (value) => typeof value === "string" ? value.trim() : "";
|
|
1499
|
+
var strings2 = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
1500
|
+
var sortedSet = (values) => [...new Set(values.map((value) => value.trim()))].sort();
|
|
1501
|
+
var canonical2 = (input) => ({
|
|
1502
|
+
boundIssuer: input.boundIssuer.trim(),
|
|
1503
|
+
defaultRole: input.defaultRole.trim(),
|
|
1504
|
+
jwksCaPem: input.jwksCaPem.trim(),
|
|
1505
|
+
jwksUrl: input.jwksUrl.trim(),
|
|
1506
|
+
jwtSupportedAlgs: sortedSet(input.jwtSupportedAlgs),
|
|
1507
|
+
jwtValidationPubkeys: sortedSet(input.jwtValidationPubkeys),
|
|
1508
|
+
oidcDiscoveryCaPem: input.oidcDiscoveryCaPem.trim(),
|
|
1509
|
+
oidcDiscoveryUrl: input.oidcDiscoveryUrl.trim()
|
|
1510
|
+
});
|
|
1511
|
+
var canonicalFromProps = (props) => canonical2({
|
|
1512
|
+
boundIssuer: props.boundIssuer ?? "",
|
|
1513
|
+
defaultRole: props.defaultRole ?? "",
|
|
1514
|
+
jwksCaPem: props.jwksCaPem ?? "",
|
|
1515
|
+
jwksUrl: props.jwksUrl ?? "",
|
|
1516
|
+
jwtSupportedAlgs: props.jwtSupportedAlgs ?? [],
|
|
1517
|
+
jwtValidationPubkeys: props.jwtValidationPubkeys ?? [],
|
|
1518
|
+
oidcDiscoveryCaPem: props.oidcDiscoveryCaPem ?? "",
|
|
1519
|
+
oidcDiscoveryUrl: props.oidcDiscoveryUrl ?? ""
|
|
1520
|
+
});
|
|
1521
|
+
var canonicalFromLive = (live) => canonical2({
|
|
1522
|
+
boundIssuer: text5(live["bound_issuer"]),
|
|
1523
|
+
defaultRole: text5(live["default_role"]),
|
|
1524
|
+
jwksCaPem: text5(live["jwks_ca_pem"]),
|
|
1525
|
+
jwksUrl: text5(live["jwks_url"]),
|
|
1526
|
+
jwtSupportedAlgs: strings2(live["jwt_supported_algs"]),
|
|
1527
|
+
jwtValidationPubkeys: strings2(live["jwt_validation_pubkeys"]),
|
|
1528
|
+
oidcDiscoveryCaPem: text5(live["oidc_discovery_ca_pem"]),
|
|
1529
|
+
oidcDiscoveryUrl: text5(live["oidc_discovery_url"])
|
|
1530
|
+
});
|
|
1531
|
+
var mountOf = (props) => mountPath(props.mount ?? "jwt");
|
|
1532
|
+
var configPath = (props) => `auth/${mountOf(props)}/config`;
|
|
1533
|
+
var attributesOf5 = (props, live) => {
|
|
1534
|
+
const form = canonicalFromLive(live);
|
|
1535
|
+
return { ...form, digest: sha256(JSON.stringify(form)), mount: mountOf(props) };
|
|
1536
|
+
};
|
|
1537
|
+
var matches4 = (attributes, props) => attributes.digest === sha256(JSON.stringify(canonicalFromProps(props)));
|
|
1538
|
+
var writeBody3 = (props) => ({
|
|
1539
|
+
bound_issuer: props.boundIssuer ?? "",
|
|
1540
|
+
default_role: props.defaultRole ?? "",
|
|
1541
|
+
jwks_ca_pem: props.jwksCaPem ?? "",
|
|
1542
|
+
jwks_url: props.jwksUrl ?? "",
|
|
1543
|
+
jwt_supported_algs: [...props.jwtSupportedAlgs ?? []],
|
|
1544
|
+
jwt_validation_pubkeys: [...props.jwtValidationPubkeys ?? []],
|
|
1545
|
+
oidc_discovery_ca_pem: props.oidcDiscoveryCaPem ?? "",
|
|
1546
|
+
oidc_discovery_url: props.oidcDiscoveryUrl ?? ""
|
|
1547
|
+
});
|
|
1548
|
+
var wouldErase = (live) => {
|
|
1549
|
+
const lost = [];
|
|
1550
|
+
if (text5(live["oidc_client_id"]) !== "")
|
|
1551
|
+
lost.push("oidc_client_id (and its unreadable secret)");
|
|
1552
|
+
const provider = live["provider_config"];
|
|
1553
|
+
if (typeof provider === "object" && provider !== null && Object.keys(provider).length > 0) {
|
|
1554
|
+
lost.push("provider_config");
|
|
1555
|
+
}
|
|
1556
|
+
if (text5(live["oidc_response_mode"]) !== "")
|
|
1557
|
+
lost.push("oidc_response_mode");
|
|
1558
|
+
if (strings2(live["oidc_response_types"]).length > 0)
|
|
1559
|
+
lost.push("oidc_response_types");
|
|
1560
|
+
if (strings2(live["override_allowed_server_names"]).length > 0) {
|
|
1561
|
+
lost.push("override_allowed_server_names");
|
|
1562
|
+
}
|
|
1563
|
+
return lost;
|
|
1564
|
+
};
|
|
1565
|
+
var problems = (props) => {
|
|
1566
|
+
const found = [];
|
|
1567
|
+
const sources = [
|
|
1568
|
+
(props.oidcDiscoveryUrl ?? "") !== "",
|
|
1569
|
+
(props.jwksUrl ?? "") !== "",
|
|
1570
|
+
(props.jwtValidationPubkeys?.length ?? 0) > 0
|
|
1571
|
+
].filter(Boolean).length;
|
|
1572
|
+
if (sources !== 1) {
|
|
1573
|
+
found.push("exactly one of oidcDiscoveryUrl, jwksUrl or jwtValidationPubkeys must be set");
|
|
1574
|
+
}
|
|
1575
|
+
if ((props.oidcDiscoveryUrl ?? "").includes(".well-known")) {
|
|
1576
|
+
found.push("oidcDiscoveryUrl must be the issuer, without /.well-known/openid-configuration");
|
|
1577
|
+
}
|
|
1578
|
+
const role = props.defaultRole ?? "";
|
|
1579
|
+
if (role !== role.toLowerCase())
|
|
1580
|
+
found.push(`defaultRole \`${role}\` must be lower case`);
|
|
1581
|
+
return found;
|
|
1582
|
+
};
|
|
1583
|
+
|
|
1584
|
+
// src/openbao/role-reconcile.ts
|
|
1585
|
+
import * as Effect13 from "effect/Effect";
|
|
1586
|
+
var readRoleAt = (spec) => Effect13.map(baoRead(spec.path), (live) => live === undefined ? undefined : { attributes: spec.attributesOf(live), live });
|
|
1587
|
+
var planRole = (spec) => Effect13.gen(function* () {
|
|
1588
|
+
if (spec.problems.length > 0)
|
|
1589
|
+
return "update";
|
|
1590
|
+
const found = yield* readRoleAt(spec);
|
|
1591
|
+
return found !== undefined && spec.matches(found.attributes) ? "noop" : "update";
|
|
1592
|
+
});
|
|
1593
|
+
var refuse2 = (spec, message) => Effect13.die(new Error(`${spec.family} ${spec.path}: ${message}`));
|
|
1594
|
+
var reconcileRole = (spec) => Effect13.gen(function* () {
|
|
1595
|
+
if (spec.problems.length > 0)
|
|
1596
|
+
return yield* refuse2(spec, spec.problems.join("; "));
|
|
1597
|
+
const found = yield* readRoleAt(spec);
|
|
1598
|
+
if (found === undefined || !spec.matches(found.attributes)) {
|
|
1599
|
+
const lost = found === undefined ? [] : spec.wouldErase?.(found.live) ?? [];
|
|
1600
|
+
if (lost.length > 0) {
|
|
1601
|
+
return yield* refuse2(spec, `live carries ${lost.join(", ")}, which this resource does not declare and the write ` + "would reset. Fold it into the declaration, or clear it by hand first.");
|
|
1602
|
+
}
|
|
1603
|
+
yield* baoWrite("PUT", spec.path, spec.body);
|
|
1604
|
+
}
|
|
1605
|
+
const after = yield* readRoleAt(spec);
|
|
1606
|
+
if (after === undefined)
|
|
1607
|
+
return yield* refuse2(spec, "write reported success but it is absent.");
|
|
1608
|
+
if (!spec.matches(after.attributes)) {
|
|
1609
|
+
return yield* refuse2(spec, "wrote cleanly but read back different — OpenBao stored something other than what was " + "declared. Read it by hand before retrying.");
|
|
1610
|
+
}
|
|
1611
|
+
return after.attributes;
|
|
1612
|
+
});
|
|
1613
|
+
|
|
1614
|
+
// src/openbao/jwt-config.ts
|
|
1615
|
+
var BaoJwtAuthConfig = Resource4("Bao.JwtAuthConfig", {
|
|
1616
|
+
defaultRemovalPolicy: "retain"
|
|
1617
|
+
});
|
|
1618
|
+
var jwtConfigSpec = (props) => ({
|
|
1619
|
+
attributesOf: (live) => attributesOf5(props, live),
|
|
1620
|
+
body: writeBody3(props),
|
|
1621
|
+
family: "Bao.JwtAuthConfig",
|
|
1622
|
+
matches: (attributes) => matches4(attributes, props),
|
|
1623
|
+
path: configPath(props),
|
|
1624
|
+
problems: problems(props),
|
|
1625
|
+
wouldErase
|
|
1626
|
+
});
|
|
1627
|
+
var BaoJwtAuthConfigProvider = () => Provider4.effect(BaoJwtAuthConfig, Effect14.succeed(BaoJwtAuthConfig.Provider.of({
|
|
1628
|
+
list: () => Effect14.succeed([]),
|
|
1629
|
+
read: Effect14.fn(function* ({ olds }) {
|
|
1630
|
+
const found = yield* readRoleAt(jwtConfigSpec(olds));
|
|
1631
|
+
return found?.attributes;
|
|
1632
|
+
}),
|
|
1633
|
+
diff: Effect14.fn(function* ({ news, output }) {
|
|
1634
|
+
if (output === undefined || !isResolved4(news))
|
|
1635
|
+
return;
|
|
1636
|
+
if (mountOf(news) !== output.mount)
|
|
1637
|
+
return { action: "replace" };
|
|
1638
|
+
return { action: yield* planRole(jwtConfigSpec(news)) };
|
|
1639
|
+
}),
|
|
1640
|
+
reconcile: Effect14.fn(function* ({ news }) {
|
|
1641
|
+
return yield* reconcileRole(jwtConfigSpec(news));
|
|
1642
|
+
}),
|
|
1643
|
+
delete: () => Effect14.void
|
|
1644
|
+
})));
|
|
1645
|
+
// src/openbao/jwt-role.ts
|
|
1646
|
+
import { Resource as Resource5 } from "alchemy";
|
|
1647
|
+
import { isResolved as isResolved5 } from "alchemy/Diff";
|
|
1648
|
+
import * as Provider5 from "alchemy/Provider";
|
|
1649
|
+
import * as Effect15 from "effect/Effect";
|
|
1650
|
+
|
|
1651
|
+
// src/openbao/auth-token-form.ts
|
|
1652
|
+
var sortedSet2 = (values) => [...new Set(values)].sort();
|
|
1653
|
+
var cidr = (value) => value.trim().replace(/\/(32|128)$/, "");
|
|
1654
|
+
var strings3 = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
1655
|
+
var seconds2 = (text6) => parseDuration(text6 ?? "0") ?? -1;
|
|
1656
|
+
var tokenFields = (input) => ({
|
|
1657
|
+
tokenBoundCidrs: input.tokenBoundCidrs,
|
|
1658
|
+
tokenExplicitMaxTtlSeconds: input.tokenExplicitMaxTtlSeconds,
|
|
1659
|
+
tokenMaxTtlSeconds: input.tokenMaxTtlSeconds,
|
|
1660
|
+
tokenNoDefaultPolicy: input.tokenNoDefaultPolicy,
|
|
1661
|
+
tokenNumUses: input.tokenNumUses,
|
|
1662
|
+
tokenPeriodSeconds: input.tokenPeriodSeconds,
|
|
1663
|
+
tokenPolicies: input.tokenPolicies,
|
|
1664
|
+
tokenTtlSeconds: input.tokenTtlSeconds,
|
|
1665
|
+
tokenType: input.tokenType
|
|
1666
|
+
});
|
|
1667
|
+
var tokenForm = (input) => ({
|
|
1668
|
+
tokenBoundCidrs: sortedSet2(input.tokenBoundCidrs.map(cidr)),
|
|
1669
|
+
tokenExplicitMaxTtlSeconds: input.tokenExplicitMaxTtlSeconds,
|
|
1670
|
+
tokenMaxTtlSeconds: input.tokenMaxTtlSeconds,
|
|
1671
|
+
tokenNoDefaultPolicy: input.tokenNoDefaultPolicy,
|
|
1672
|
+
tokenNumUses: input.tokenNumUses,
|
|
1673
|
+
tokenPeriodSeconds: input.tokenPeriodSeconds,
|
|
1674
|
+
tokenPolicies: sortedSet2(input.tokenPolicies),
|
|
1675
|
+
tokenTtlSeconds: input.tokenTtlSeconds,
|
|
1676
|
+
tokenType: input.tokenType
|
|
1677
|
+
});
|
|
1678
|
+
var tokenFormOfProps = (props) => tokenForm({
|
|
1679
|
+
tokenBoundCidrs: props.tokenBoundCidrs ?? [],
|
|
1680
|
+
tokenExplicitMaxTtlSeconds: seconds2(props.tokenExplicitMaxTtl),
|
|
1681
|
+
tokenMaxTtlSeconds: seconds2(props.tokenMaxTtl),
|
|
1682
|
+
tokenNoDefaultPolicy: props.tokenNoDefaultPolicy ?? false,
|
|
1683
|
+
tokenNumUses: props.tokenNumUses ?? 0,
|
|
1684
|
+
tokenPeriodSeconds: seconds2(props.tokenPeriod),
|
|
1685
|
+
tokenPolicies: props.tokenPolicies,
|
|
1686
|
+
tokenTtlSeconds: seconds2(props.tokenTtl),
|
|
1687
|
+
tokenType: props.tokenType ?? "default"
|
|
1688
|
+
});
|
|
1689
|
+
var tokenFormOfLive = (live) => tokenForm({
|
|
1690
|
+
tokenBoundCidrs: strings3(live["token_bound_cidrs"]),
|
|
1691
|
+
tokenExplicitMaxTtlSeconds: ttlSeconds(live["token_explicit_max_ttl"]) ?? -1,
|
|
1692
|
+
tokenMaxTtlSeconds: ttlSeconds(live["token_max_ttl"]) ?? -1,
|
|
1693
|
+
tokenNoDefaultPolicy: live["token_no_default_policy"] === true,
|
|
1694
|
+
tokenNumUses: typeof live["token_num_uses"] === "number" ? live["token_num_uses"] : -1,
|
|
1695
|
+
tokenPeriodSeconds: ttlSeconds(live["token_period"]) ?? -1,
|
|
1696
|
+
tokenPolicies: strings3(live["token_policies"]),
|
|
1697
|
+
tokenTtlSeconds: ttlSeconds(live["token_ttl"]) ?? -1,
|
|
1698
|
+
tokenType: typeof live["token_type"] === "string" ? live["token_type"] : ""
|
|
1699
|
+
});
|
|
1700
|
+
var tokenBody = (props) => ({
|
|
1701
|
+
token_bound_cidrs: [...props.tokenBoundCidrs ?? []],
|
|
1702
|
+
token_explicit_max_ttl: props.tokenExplicitMaxTtl ?? "0",
|
|
1703
|
+
token_max_ttl: props.tokenMaxTtl ?? "0",
|
|
1704
|
+
token_no_default_policy: props.tokenNoDefaultPolicy ?? false,
|
|
1705
|
+
token_num_uses: props.tokenNumUses ?? 0,
|
|
1706
|
+
token_period: props.tokenPeriod ?? "0",
|
|
1707
|
+
token_policies: [...props.tokenPolicies],
|
|
1708
|
+
token_ttl: props.tokenTtl ?? "0",
|
|
1709
|
+
token_type: props.tokenType ?? "default"
|
|
1710
|
+
});
|
|
1711
|
+
var tokenProblems = (props) => {
|
|
1712
|
+
const found = [];
|
|
1713
|
+
const durations = {
|
|
1714
|
+
tokenExplicitMaxTtl: props.tokenExplicitMaxTtl,
|
|
1715
|
+
tokenMaxTtl: props.tokenMaxTtl,
|
|
1716
|
+
tokenPeriod: props.tokenPeriod,
|
|
1717
|
+
tokenTtl: props.tokenTtl
|
|
1718
|
+
};
|
|
1719
|
+
for (const [field, text6] of Object.entries(durations)) {
|
|
1720
|
+
if (seconds2(text6) < 0)
|
|
1721
|
+
found.push(`${field}=${text6 ?? ""} is not a duration (0 or <n>s|m|h|d)`);
|
|
1722
|
+
}
|
|
1723
|
+
const ttl = seconds2(props.tokenTtl);
|
|
1724
|
+
const max = seconds2(props.tokenMaxTtl);
|
|
1725
|
+
if (ttl > 0 && max > 0 && ttl > max)
|
|
1726
|
+
found.push("tokenTtl is longer than tokenMaxTtl");
|
|
1727
|
+
if (props.tokenType === "batch" && (seconds2(props.tokenPeriod) > 0 || (props.tokenNumUses ?? 0) > 0)) {
|
|
1728
|
+
found.push("a batch token cannot be periodic or use-limited (tokenutil.go:234-241)");
|
|
1729
|
+
}
|
|
1730
|
+
return found;
|
|
1731
|
+
};
|
|
1732
|
+
|
|
1733
|
+
// src/openbao/jwt-role-form.ts
|
|
1734
|
+
var sortedSet3 = (values) => [...new Set(values)].sort();
|
|
1735
|
+
var text6 = (value) => typeof value === "string" ? value : "";
|
|
1736
|
+
var strings4 = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
1737
|
+
var record3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
1738
|
+
var claimsOf = (value) => {
|
|
1739
|
+
const source = record3(value);
|
|
1740
|
+
const out = {};
|
|
1741
|
+
for (const key of Object.keys(source).sort()) {
|
|
1742
|
+
const raw = source[key];
|
|
1743
|
+
out[key] = sortedSet3(typeof raw === "string" ? [raw] : strings4(raw));
|
|
1744
|
+
}
|
|
1745
|
+
return out;
|
|
1746
|
+
};
|
|
1747
|
+
var mappingsOf = (value) => {
|
|
1748
|
+
const source = record3(value);
|
|
1749
|
+
const out = {};
|
|
1750
|
+
for (const key of Object.keys(source).sort())
|
|
1751
|
+
out[key] = text6(source[key]);
|
|
1752
|
+
return out;
|
|
1753
|
+
};
|
|
1754
|
+
var canonical3 = (input) => ({
|
|
1755
|
+
allowedRedirectUris: sortedSet3(input.allowedRedirectUris),
|
|
1756
|
+
boundAudiences: sortedSet3(input.boundAudiences),
|
|
1757
|
+
boundClaims: claimsOf(input.boundClaims),
|
|
1758
|
+
boundClaimsType: input.boundClaimsType,
|
|
1759
|
+
boundSubject: input.boundSubject,
|
|
1760
|
+
callbackMode: input.callbackMode,
|
|
1761
|
+
claimMappings: mappingsOf(input.claimMappings),
|
|
1762
|
+
groupsClaim: input.groupsClaim,
|
|
1763
|
+
oidcDisableConfirmation: input.oidcDisableConfirmation,
|
|
1764
|
+
oidcScopes: sortedSet3(input.oidcScopes),
|
|
1765
|
+
roleType: input.roleType,
|
|
1766
|
+
userClaim: input.userClaim,
|
|
1767
|
+
verboseOidcLogging: input.verboseOidcLogging,
|
|
1768
|
+
...tokenFields(input)
|
|
1769
|
+
});
|
|
1770
|
+
var canonicalFromProps2 = (props) => canonical3({
|
|
1771
|
+
...tokenFormOfProps(props),
|
|
1772
|
+
allowedRedirectUris: props.allowedRedirectUris ?? [],
|
|
1773
|
+
boundAudiences: props.boundAudiences ?? [],
|
|
1774
|
+
boundClaims: claimsOf(props.boundClaims ?? {}),
|
|
1775
|
+
boundClaimsType: props.boundClaimsType ?? "string",
|
|
1776
|
+
boundSubject: props.boundSubject ?? "",
|
|
1777
|
+
callbackMode: props.callbackMode ?? "client",
|
|
1778
|
+
claimMappings: props.claimMappings ?? {},
|
|
1779
|
+
groupsClaim: props.groupsClaim ?? "",
|
|
1780
|
+
oidcDisableConfirmation: false,
|
|
1781
|
+
oidcScopes: props.oidcScopes ?? [],
|
|
1782
|
+
roleType: props.roleType,
|
|
1783
|
+
userClaim: props.userClaim,
|
|
1784
|
+
verboseOidcLogging: false
|
|
1785
|
+
});
|
|
1786
|
+
var canonicalFromLive2 = (live) => canonical3({
|
|
1787
|
+
...tokenFormOfLive(live),
|
|
1788
|
+
allowedRedirectUris: strings4(live["allowed_redirect_uris"]),
|
|
1789
|
+
boundAudiences: strings4(live["bound_audiences"]),
|
|
1790
|
+
boundClaims: claimsOf(live["bound_claims"]),
|
|
1791
|
+
boundClaimsType: text6(live["bound_claims_type"]),
|
|
1792
|
+
boundSubject: text6(live["bound_subject"]),
|
|
1793
|
+
callbackMode: text6(live["callback_mode"]),
|
|
1794
|
+
claimMappings: mappingsOf(live["claim_mappings"]),
|
|
1795
|
+
groupsClaim: text6(live["groups_claim"]),
|
|
1796
|
+
oidcDisableConfirmation: live["oidc_disable_confirmation"] === true,
|
|
1797
|
+
oidcScopes: strings4(live["oidc_scopes"]),
|
|
1798
|
+
roleType: text6(live["role_type"]),
|
|
1799
|
+
userClaim: text6(live["user_claim"]),
|
|
1800
|
+
verboseOidcLogging: live["verbose_oidc_logging"] === true
|
|
1801
|
+
});
|
|
1802
|
+
var mountOf2 = (props) => mountPath(props.mount ?? "jwt");
|
|
1803
|
+
var rolePath2 = (props) => `auth/${mountOf2(props)}/role/${props.name}`;
|
|
1804
|
+
var attributesOf6 = (props, live) => {
|
|
1805
|
+
const form = canonicalFromLive2(live);
|
|
1806
|
+
return { ...form, digest: sha256(JSON.stringify(form)), mount: mountOf2(props), name: props.name };
|
|
1807
|
+
};
|
|
1808
|
+
var matches5 = (attributes, props) => attributes.digest === sha256(JSON.stringify(canonicalFromProps2(props)));
|
|
1809
|
+
var writeBody4 = (props) => ({
|
|
1810
|
+
allowed_redirect_uris: [...props.allowedRedirectUris ?? []],
|
|
1811
|
+
bound_audiences: [...props.boundAudiences ?? []],
|
|
1812
|
+
bound_claims: { ...props.boundClaims },
|
|
1813
|
+
bound_claims_type: props.boundClaimsType ?? "string",
|
|
1814
|
+
bound_subject: props.boundSubject ?? "",
|
|
1815
|
+
callback_mode: props.callbackMode ?? "client",
|
|
1816
|
+
claim_mappings: { ...props.claimMappings },
|
|
1817
|
+
groups_claim: props.groupsClaim ?? "",
|
|
1818
|
+
oidc_disable_confirmation: false,
|
|
1819
|
+
oidc_scopes: [...props.oidcScopes ?? []],
|
|
1820
|
+
role_type: props.roleType,
|
|
1821
|
+
user_claim: props.userClaim,
|
|
1822
|
+
verbose_oidc_logging: false,
|
|
1823
|
+
...tokenBody(props)
|
|
1824
|
+
});
|
|
1825
|
+
var NAME2 = /^[a-z0-9_](?:[a-z0-9_.-]*[a-z0-9_])?$/;
|
|
1826
|
+
var problems2 = (props) => {
|
|
1827
|
+
const found = [...tokenProblems(props)];
|
|
1828
|
+
if (!NAME2.test(props.name))
|
|
1829
|
+
found.push(`name \`${props.name}\` must be lower case (the server folds it)`);
|
|
1830
|
+
if (props.userClaim.trim() === "")
|
|
1831
|
+
found.push("userClaim is empty; the server requires one");
|
|
1832
|
+
const bound = (props.boundAudiences?.length ?? 0) > 0 || (props.boundSubject ?? "") !== "" || Object.keys(props.boundClaims ?? {}).length > 0 || (props.tokenBoundCidrs?.length ?? 0) > 0;
|
|
1833
|
+
if (props.roleType === "jwt" && !bound) {
|
|
1834
|
+
found.push("a jwt role needs a bound constraint: boundAudiences, boundSubject, boundClaims or tokenBoundCidrs");
|
|
1835
|
+
}
|
|
1836
|
+
if (props.roleType === "oidc" && (props.allowedRedirectUris?.length ?? 0) === 0) {
|
|
1837
|
+
found.push("an oidc role needs allowedRedirectUris");
|
|
1838
|
+
}
|
|
1839
|
+
const targets = Object.values(props.claimMappings ?? {});
|
|
1840
|
+
if (targets.includes("role"))
|
|
1841
|
+
found.push("claimMappings may not map to the reserved key `role`");
|
|
1842
|
+
if (new Set(targets).size !== targets.length)
|
|
1843
|
+
found.push("claimMappings maps two claims to one key");
|
|
1844
|
+
return found;
|
|
1845
|
+
};
|
|
1846
|
+
|
|
1847
|
+
// src/openbao/jwt-role.ts
|
|
1848
|
+
var BaoJwtRole = Resource5("Bao.JwtRole", {
|
|
1849
|
+
defaultRemovalPolicy: "retain"
|
|
1850
|
+
});
|
|
1851
|
+
var jwtRoleSpec = (props) => ({
|
|
1852
|
+
attributesOf: (live) => attributesOf6(props, live),
|
|
1853
|
+
body: writeBody4(props),
|
|
1854
|
+
family: "Bao.JwtRole",
|
|
1855
|
+
matches: (attributes) => matches5(attributes, props),
|
|
1856
|
+
path: rolePath2(props),
|
|
1857
|
+
problems: problems2(props)
|
|
1858
|
+
});
|
|
1859
|
+
var BaoJwtRoleProvider = () => Provider5.effect(BaoJwtRole, Effect15.succeed(BaoJwtRole.Provider.of({
|
|
1860
|
+
list: () => Effect15.succeed([]),
|
|
1861
|
+
read: Effect15.fn(function* ({ olds }) {
|
|
1862
|
+
const found = yield* readRoleAt(jwtRoleSpec(olds));
|
|
1863
|
+
return found?.attributes;
|
|
1864
|
+
}),
|
|
1865
|
+
diff: Effect15.fn(function* ({ news, output }) {
|
|
1866
|
+
if (output === undefined || !isResolved5(news))
|
|
1867
|
+
return;
|
|
1868
|
+
if (mountOf2(news) !== output.mount || news.name !== output.name) {
|
|
1869
|
+
return { action: "replace" };
|
|
1870
|
+
}
|
|
1871
|
+
return { action: yield* planRole(jwtRoleSpec(news)) };
|
|
1872
|
+
}),
|
|
1873
|
+
reconcile: Effect15.fn(function* ({ news }) {
|
|
1874
|
+
return yield* reconcileRole(jwtRoleSpec(news));
|
|
1875
|
+
}),
|
|
1876
|
+
delete: Effect15.fn(function* ({ output }) {
|
|
1877
|
+
yield* baoDelete(rolePath2(output));
|
|
1878
|
+
return;
|
|
1879
|
+
})
|
|
1880
|
+
})));
|
|
1881
|
+
// src/openbao/kubernetes-role.ts
|
|
1882
|
+
import { Resource as Resource6 } from "alchemy";
|
|
1883
|
+
import { isResolved as isResolved6 } from "alchemy/Diff";
|
|
1884
|
+
import * as Provider6 from "alchemy/Provider";
|
|
1885
|
+
import * as Effect16 from "effect/Effect";
|
|
1886
|
+
|
|
1887
|
+
// src/openbao/kubernetes-role-form.ts
|
|
1888
|
+
var sortedSet4 = (values) => [...new Set(values)].sort();
|
|
1889
|
+
var text7 = (value) => typeof value === "string" ? value : "";
|
|
1890
|
+
var strings5 = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
1891
|
+
var canonical4 = (input) => ({
|
|
1892
|
+
aliasNameSource: input.aliasNameSource,
|
|
1893
|
+
audience: input.audience,
|
|
1894
|
+
boundServiceAccountNames: sortedSet4(input.boundServiceAccountNames),
|
|
1895
|
+
boundServiceAccountNamespaceSelector: input.boundServiceAccountNamespaceSelector.trim(),
|
|
1896
|
+
boundServiceAccountNamespaces: sortedSet4(input.boundServiceAccountNamespaces),
|
|
1897
|
+
...tokenFields(input)
|
|
1898
|
+
});
|
|
1899
|
+
var canonicalFromProps3 = (props) => canonical4({
|
|
1900
|
+
...tokenFormOfProps(props),
|
|
1901
|
+
aliasNameSource: props.aliasNameSource,
|
|
1902
|
+
audience: props.audience ?? "",
|
|
1903
|
+
boundServiceAccountNames: props.boundServiceAccountNames,
|
|
1904
|
+
boundServiceAccountNamespaceSelector: props.boundServiceAccountNamespaceSelector ?? "",
|
|
1905
|
+
boundServiceAccountNamespaces: props.boundServiceAccountNamespaces ?? []
|
|
1906
|
+
});
|
|
1907
|
+
var canonicalFromLive3 = (live) => canonical4({
|
|
1908
|
+
...tokenFormOfLive(live),
|
|
1909
|
+
aliasNameSource: text7(live["alias_name_source"]),
|
|
1910
|
+
audience: text7(live["audience"]),
|
|
1911
|
+
boundServiceAccountNames: strings5(live["bound_service_account_names"]),
|
|
1912
|
+
boundServiceAccountNamespaceSelector: text7(live["bound_service_account_namespace_selector"]),
|
|
1913
|
+
boundServiceAccountNamespaces: strings5(live["bound_service_account_namespaces"])
|
|
1914
|
+
});
|
|
1915
|
+
var mountOf3 = (props) => mountPath(props.mount ?? "kubernetes");
|
|
1916
|
+
var rolePath3 = (props) => `auth/${mountOf3(props)}/role/${props.name}`;
|
|
1917
|
+
var attributesOf7 = (props, live) => {
|
|
1918
|
+
const form = canonicalFromLive3(live);
|
|
1919
|
+
return { ...form, digest: sha256(JSON.stringify(form)), mount: mountOf3(props), name: props.name };
|
|
1920
|
+
};
|
|
1921
|
+
var matches6 = (attributes, props) => attributes.digest === sha256(JSON.stringify(canonicalFromProps3(props)));
|
|
1922
|
+
var writeBody5 = (props) => ({
|
|
1923
|
+
alias_name_source: props.aliasNameSource,
|
|
1924
|
+
audience: props.audience ?? "",
|
|
1925
|
+
bound_service_account_names: [...props.boundServiceAccountNames],
|
|
1926
|
+
bound_service_account_namespace_selector: props.boundServiceAccountNamespaceSelector ?? "",
|
|
1927
|
+
bound_service_account_namespaces: [...props.boundServiceAccountNamespaces ?? []],
|
|
1928
|
+
...tokenBody(props)
|
|
1929
|
+
});
|
|
1930
|
+
var NAME3 = /^[a-z0-9_](?:[a-z0-9_.-]*[a-z0-9_])?$/;
|
|
1931
|
+
var starMixed = (values) => values.length > 1 && values.includes("*");
|
|
1932
|
+
var problems3 = (props) => {
|
|
1933
|
+
const found = [...tokenProblems(props)];
|
|
1934
|
+
const namespaces = props.boundServiceAccountNamespaces ?? [];
|
|
1935
|
+
const selector = (props.boundServiceAccountNamespaceSelector ?? "").trim();
|
|
1936
|
+
if (!NAME3.test(props.name))
|
|
1937
|
+
found.push(`name \`${props.name}\` must be lower case (stored folded)`);
|
|
1938
|
+
if (props.boundServiceAccountNames.length === 0)
|
|
1939
|
+
found.push("boundServiceAccountNames is empty");
|
|
1940
|
+
if (starMixed(props.boundServiceAccountNames))
|
|
1941
|
+
found.push("`*` mixed with other account names");
|
|
1942
|
+
if (namespaces.length === 0 && selector === "") {
|
|
1943
|
+
found.push("boundServiceAccountNamespaces is empty and no namespace selector is set");
|
|
1944
|
+
}
|
|
1945
|
+
if (starMixed(namespaces))
|
|
1946
|
+
found.push("`*` mixed with other namespaces");
|
|
1947
|
+
return found;
|
|
1948
|
+
};
|
|
1949
|
+
|
|
1950
|
+
// src/openbao/kubernetes-role.ts
|
|
1951
|
+
var BaoKubernetesRole = Resource6("Bao.KubernetesRole", {
|
|
1952
|
+
defaultRemovalPolicy: "retain"
|
|
1953
|
+
});
|
|
1954
|
+
var kubernetesRoleSpec = (props) => ({
|
|
1955
|
+
attributesOf: (live) => attributesOf7(props, live),
|
|
1956
|
+
body: writeBody5(props),
|
|
1957
|
+
family: "Bao.KubernetesRole",
|
|
1958
|
+
matches: (attributes) => matches6(attributes, props),
|
|
1959
|
+
path: rolePath3(props),
|
|
1960
|
+
problems: problems3(props)
|
|
1961
|
+
});
|
|
1962
|
+
var BaoKubernetesRoleProvider = () => Provider6.effect(BaoKubernetesRole, Effect16.succeed(BaoKubernetesRole.Provider.of({
|
|
1963
|
+
list: () => Effect16.succeed([]),
|
|
1964
|
+
read: Effect16.fn(function* ({ olds }) {
|
|
1965
|
+
const found = yield* readRoleAt(kubernetesRoleSpec(olds));
|
|
1966
|
+
return found?.attributes;
|
|
1967
|
+
}),
|
|
1968
|
+
diff: Effect16.fn(function* ({ news, output }) {
|
|
1969
|
+
if (output === undefined || !isResolved6(news))
|
|
1970
|
+
return;
|
|
1971
|
+
if (mountOf3(news) !== output.mount || news.name !== output.name) {
|
|
1972
|
+
return { action: "replace" };
|
|
1973
|
+
}
|
|
1974
|
+
return { action: yield* planRole(kubernetesRoleSpec(news)) };
|
|
1975
|
+
}),
|
|
1976
|
+
reconcile: Effect16.fn(function* ({ news }) {
|
|
1977
|
+
return yield* reconcileRole(kubernetesRoleSpec(news));
|
|
1978
|
+
}),
|
|
1979
|
+
delete: Effect16.fn(function* ({ output }) {
|
|
1980
|
+
yield* baoDelete(rolePath3(output));
|
|
1981
|
+
return;
|
|
1982
|
+
})
|
|
1983
|
+
})));
|
|
1984
|
+
// src/openbao/mfa-enforcement.ts
|
|
1985
|
+
import { Resource as Resource7 } from "alchemy";
|
|
1986
|
+
import { isResolved as isResolved7 } from "alchemy/Diff";
|
|
1987
|
+
import * as Provider7 from "alchemy/Provider";
|
|
1988
|
+
import * as Effect18 from "effect/Effect";
|
|
1989
|
+
|
|
1990
|
+
// src/openbao/mfa-enforcement-form.ts
|
|
1991
|
+
var sortedSet5 = (values) => [...new Set(values.map((value) => value.trim()))].filter((value) => value !== "").sort();
|
|
1992
|
+
var strings6 = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
1993
|
+
var text8 = (value) => typeof value === "string" ? value : "";
|
|
1994
|
+
var canonical5 = (input) => ({
|
|
1995
|
+
authMethodAccessors: sortedSet5(input.authMethodAccessors),
|
|
1996
|
+
authMethodTypes: sortedSet5(input.authMethodTypes),
|
|
1997
|
+
identityEntityIds: sortedSet5(input.identityEntityIds),
|
|
1998
|
+
identityGroupIds: sortedSet5(input.identityGroupIds),
|
|
1999
|
+
mfaMethodIds: sortedSet5(input.mfaMethodIds)
|
|
2000
|
+
});
|
|
2001
|
+
var canonicalFromProps4 = (props, resolved) => canonical5({
|
|
2002
|
+
authMethodAccessors: [...props.authMethodAccessors ?? [], ...resolved],
|
|
2003
|
+
authMethodTypes: props.authMethodTypes ?? [],
|
|
2004
|
+
identityEntityIds: props.identityEntityIds ?? [],
|
|
2005
|
+
identityGroupIds: props.identityGroupIds ?? [],
|
|
2006
|
+
mfaMethodIds: props.mfaMethodIds
|
|
2007
|
+
});
|
|
2008
|
+
var canonicalFromLive4 = (live) => canonical5({
|
|
2009
|
+
authMethodAccessors: strings6(live["auth_method_accessors"]),
|
|
2010
|
+
authMethodTypes: strings6(live["auth_method_types"]),
|
|
2011
|
+
identityEntityIds: strings6(live["identity_entity_ids"]),
|
|
2012
|
+
identityGroupIds: strings6(live["identity_group_ids"]),
|
|
2013
|
+
mfaMethodIds: strings6(live["mfa_method_ids"])
|
|
2014
|
+
});
|
|
2015
|
+
var attributesOf8 = (name, live) => {
|
|
2016
|
+
const form = canonicalFromLive4(live);
|
|
2017
|
+
return {
|
|
2018
|
+
...form,
|
|
2019
|
+
digest: sha256(JSON.stringify(form)),
|
|
2020
|
+
enforcementId: text8(live["id"]),
|
|
2021
|
+
name,
|
|
2022
|
+
namespacePath: text8(live["namespace_path"])
|
|
2023
|
+
};
|
|
2024
|
+
};
|
|
2025
|
+
var matches7 = (attributes, props, resolved) => attributes.digest === sha256(JSON.stringify(canonicalFromProps4(props, resolved)));
|
|
2026
|
+
var writeBody6 = (form) => ({
|
|
2027
|
+
auth_method_accessors: [...form.authMethodAccessors],
|
|
2028
|
+
auth_method_types: [...form.authMethodTypes],
|
|
2029
|
+
identity_entity_ids: [...form.identityEntityIds],
|
|
2030
|
+
identity_group_ids: [...form.identityGroupIds],
|
|
2031
|
+
mfa_method_ids: [...form.mfaMethodIds]
|
|
2032
|
+
});
|
|
2033
|
+
var NAME4 = /^\w(?:[\w.-]*\w)?$/;
|
|
2034
|
+
var problems4 = (props, missingPaths) => {
|
|
2035
|
+
const found = [];
|
|
2036
|
+
if (!NAME4.test(props.name))
|
|
2037
|
+
found.push(`name \`${props.name}\` is not a valid enforcement name`);
|
|
2038
|
+
if (sortedSet5(props.mfaMethodIds).length === 0)
|
|
2039
|
+
found.push("mfaMethodIds is empty");
|
|
2040
|
+
const targets = [
|
|
2041
|
+
props.authMethodPaths,
|
|
2042
|
+
props.authMethodAccessors,
|
|
2043
|
+
props.authMethodTypes,
|
|
2044
|
+
props.identityGroupIds,
|
|
2045
|
+
props.identityEntityIds
|
|
2046
|
+
].some((list) => sortedSet5(list ?? []).length > 0);
|
|
2047
|
+
if (!targets)
|
|
2048
|
+
found.push("no target: an enforcement with none is accepted and enforces NOTHING");
|
|
2049
|
+
if (missingPaths.length > 0) {
|
|
2050
|
+
found.push(`no auth mount at ${missingPaths.map((path) => mountPath(path)).join(", ")}`);
|
|
2051
|
+
}
|
|
2052
|
+
return found;
|
|
2053
|
+
};
|
|
2054
|
+
|
|
2055
|
+
// src/openbao/mfa-wire.ts
|
|
2056
|
+
import * as Effect17 from "effect/Effect";
|
|
2057
|
+
var TOTP_PATH = "identity/mfa/method/totp";
|
|
2058
|
+
var METHODS_PATH = "identity/mfa/method";
|
|
2059
|
+
var enforcementPath = (name) => `identity/mfa/login-enforcement/${name}`;
|
|
2060
|
+
var record4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
2061
|
+
var ownNamespacePath = Effect17.gen(function* () {
|
|
2062
|
+
const env = yield* BaoEnv;
|
|
2063
|
+
return namespacePath(envNamespace(env));
|
|
2064
|
+
});
|
|
2065
|
+
var findTotp = (name) => Effect17.gen(function* () {
|
|
2066
|
+
const listing = yield* baoRead(`${METHODS_PATH}?list=true`);
|
|
2067
|
+
const info = record4(listing?.["key_info"]) ?? {};
|
|
2068
|
+
const own = yield* ownNamespacePath;
|
|
2069
|
+
for (const [id, raw] of Object.entries(info)) {
|
|
2070
|
+
const live = record4(raw);
|
|
2071
|
+
if (live === undefined || live["name"] !== name || live["namespace_path"] !== own)
|
|
2072
|
+
continue;
|
|
2073
|
+
if (live["type"] !== "totp") {
|
|
2074
|
+
return yield* Effect17.die(new Error(`Bao.MfaTotpMethod ${name}: that name belongs to a ${String(live["type"])} method ` + `(${id}) in this namespace, and a TOTP write would convert it. Pick another name.`));
|
|
2075
|
+
}
|
|
2076
|
+
return { id, live };
|
|
2077
|
+
}
|
|
2078
|
+
return;
|
|
2079
|
+
});
|
|
2080
|
+
var writeTotp = (body) => Effect17.asVoid(baoCall("write", "POST", TOTP_PATH, body));
|
|
2081
|
+
var deleteTotp = (id) => baoDelete(`${TOTP_PATH}/${id}`);
|
|
2082
|
+
var readEnforcement = (name) => baoRead(enforcementPath(name));
|
|
2083
|
+
var writeEnforcement = (name, body) => Effect17.asVoid(baoCall("write", "POST", enforcementPath(name), body));
|
|
2084
|
+
var authAccessors = (paths) => Effect17.gen(function* () {
|
|
2085
|
+
if (paths.length === 0)
|
|
2086
|
+
return { accessors: [], missing: [] };
|
|
2087
|
+
const table = (yield* baoRead("sys/auth")) ?? {};
|
|
2088
|
+
const accessors = [];
|
|
2089
|
+
const missing = [];
|
|
2090
|
+
for (const path of paths) {
|
|
2091
|
+
const accessor = record4(table[`${mountPath(path)}/`])?.["accessor"];
|
|
2092
|
+
if (typeof accessor === "string" && accessor !== "")
|
|
2093
|
+
accessors.push(accessor);
|
|
2094
|
+
else
|
|
2095
|
+
missing.push(mountPath(path));
|
|
2096
|
+
}
|
|
2097
|
+
return { accessors, missing };
|
|
2098
|
+
});
|
|
2099
|
+
|
|
2100
|
+
// src/openbao/mfa-enforcement.ts
|
|
2101
|
+
var BaoMfaLoginEnforcement = Resource7("Bao.MfaLoginEnforcement", {
|
|
2102
|
+
defaultRemovalPolicy: "retain"
|
|
2103
|
+
});
|
|
2104
|
+
var refuse3 = (name, message) => Effect18.die(new Error(`Bao.MfaLoginEnforcement ${name}: ${message}`));
|
|
2105
|
+
var resolveTargets = (props) => Effect18.map(authAccessors(props.authMethodPaths ?? []), ({ accessors, missing }) => ({
|
|
2106
|
+
bad: problems4(props, missing),
|
|
2107
|
+
resolved: accessors
|
|
2108
|
+
}));
|
|
2109
|
+
var readLive = (name) => Effect18.map(readEnforcement(name), (live) => live === undefined ? undefined : attributesOf8(name, live));
|
|
2110
|
+
var planEnforcement = (props) => Effect18.gen(function* () {
|
|
2111
|
+
const { bad, resolved } = yield* resolveTargets(props);
|
|
2112
|
+
if (bad.length > 0)
|
|
2113
|
+
return "update";
|
|
2114
|
+
const live = yield* readLive(props.name);
|
|
2115
|
+
return live !== undefined && matches7(live, props, resolved) ? "noop" : "update";
|
|
2116
|
+
});
|
|
2117
|
+
var reconcileEnforcement = (props) => Effect18.gen(function* () {
|
|
2118
|
+
const { bad, resolved } = yield* resolveTargets(props);
|
|
2119
|
+
if (bad.length > 0)
|
|
2120
|
+
return yield* refuse3(props.name, bad.join("; "));
|
|
2121
|
+
const live = yield* readLive(props.name);
|
|
2122
|
+
if (live === undefined || !matches7(live, props, resolved)) {
|
|
2123
|
+
yield* writeEnforcement(props.name, writeBody6(canonicalFromProps4(props, resolved)));
|
|
2124
|
+
}
|
|
2125
|
+
const after = yield* readLive(props.name);
|
|
2126
|
+
if (after === undefined)
|
|
2127
|
+
return yield* refuse3(props.name, "the write succeeded but it is absent.");
|
|
2128
|
+
if (!matches7(after, props, resolved)) {
|
|
2129
|
+
return yield* refuse3(props.name, "the enforcement read back different.");
|
|
2130
|
+
}
|
|
2131
|
+
return after;
|
|
2132
|
+
});
|
|
2133
|
+
var BaoMfaLoginEnforcementProvider = () => Provider7.effect(BaoMfaLoginEnforcement, Effect18.succeed(BaoMfaLoginEnforcement.Provider.of({
|
|
2134
|
+
list: () => Effect18.succeed([]),
|
|
2135
|
+
read: Effect18.fn(function* ({ olds }) {
|
|
2136
|
+
return yield* readLive(olds.name);
|
|
2137
|
+
}),
|
|
2138
|
+
diff: Effect18.fn(function* ({ news, output }) {
|
|
2139
|
+
if (output === undefined || !isResolved7(news))
|
|
2140
|
+
return;
|
|
2141
|
+
if (news.name !== output.name)
|
|
2142
|
+
return { action: "replace" };
|
|
2143
|
+
return { action: yield* planEnforcement(news) };
|
|
2144
|
+
}),
|
|
2145
|
+
reconcile: Effect18.fn(function* ({ news }) {
|
|
2146
|
+
return yield* reconcileEnforcement(news);
|
|
2147
|
+
}),
|
|
2148
|
+
delete: Effect18.fn(function* ({ output }) {
|
|
2149
|
+
return yield* refuse3(output.name, "REFUSING to delete: OpenBao 2.6.2 drops a login enforcement from memory only, and it " + "returns on the next unseal (openbao/openbao#4030). Set this resource back to " + "retain to drop it from state; remove it for good only once the server is fixed.");
|
|
2150
|
+
})
|
|
2151
|
+
})));
|
|
2152
|
+
// src/openbao/mfa-totp.ts
|
|
2153
|
+
import { Resource as Resource8 } from "alchemy";
|
|
2154
|
+
import { isResolved as isResolved8 } from "alchemy/Diff";
|
|
2155
|
+
import * as Provider8 from "alchemy/Provider";
|
|
2156
|
+
import * as Effect19 from "effect/Effect";
|
|
2157
|
+
|
|
2158
|
+
// src/openbao/mfa-totp-form.ts
|
|
2159
|
+
var canonical6 = (input) => ({
|
|
2160
|
+
algorithm: input.algorithm,
|
|
2161
|
+
digits: input.digits,
|
|
2162
|
+
issuer: input.issuer,
|
|
2163
|
+
keySize: input.keySize,
|
|
2164
|
+
maxValidationAttempts: input.maxValidationAttempts,
|
|
2165
|
+
periodSeconds: input.periodSeconds,
|
|
2166
|
+
qrSize: input.qrSize,
|
|
2167
|
+
skew: input.skew
|
|
2168
|
+
});
|
|
2169
|
+
var canonicalFromProps5 = (props) => canonical6({
|
|
2170
|
+
algorithm: props.algorithm ?? "SHA1",
|
|
2171
|
+
digits: props.digits ?? 6,
|
|
2172
|
+
issuer: props.issuer,
|
|
2173
|
+
keySize: props.keySize ?? 20,
|
|
2174
|
+
maxValidationAttempts: props.maxValidationAttempts ?? 5,
|
|
2175
|
+
periodSeconds: parseDuration(props.period ?? "30s") ?? -1,
|
|
2176
|
+
qrSize: props.qrSize ?? 200,
|
|
2177
|
+
skew: props.skew ?? 1
|
|
2178
|
+
});
|
|
2179
|
+
var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : -1;
|
|
2180
|
+
var text9 = (value) => typeof value === "string" ? value : "";
|
|
2181
|
+
var canonicalFromLive5 = (live) => canonical6({
|
|
2182
|
+
algorithm: text9(live["algorithm"]),
|
|
2183
|
+
digits: num(live["digits"]),
|
|
2184
|
+
issuer: text9(live["issuer"]),
|
|
2185
|
+
keySize: num(live["key_size"]),
|
|
2186
|
+
maxValidationAttempts: num(live["max_validation_attempts"]),
|
|
2187
|
+
periodSeconds: ttlSeconds(live["period"]) ?? -1,
|
|
2188
|
+
qrSize: num(live["qr_size"]),
|
|
2189
|
+
skew: num(live["skew"])
|
|
2190
|
+
});
|
|
2191
|
+
var attributesOf9 = (methodId, live) => {
|
|
2192
|
+
const form = canonicalFromLive5(live);
|
|
2193
|
+
return {
|
|
2194
|
+
...form,
|
|
2195
|
+
digest: sha256(JSON.stringify(form)),
|
|
2196
|
+
methodId,
|
|
2197
|
+
name: text9(live["name"]),
|
|
2198
|
+
namespacePath: text9(live["namespace_path"])
|
|
2199
|
+
};
|
|
2200
|
+
};
|
|
2201
|
+
var matches8 = (attributes, props) => attributes.digest === sha256(JSON.stringify(canonicalFromProps5(props)));
|
|
2202
|
+
var writeBody7 = (props) => {
|
|
2203
|
+
const form = canonicalFromProps5(props);
|
|
2204
|
+
return {
|
|
2205
|
+
algorithm: form.algorithm,
|
|
2206
|
+
digits: form.digits,
|
|
2207
|
+
issuer: form.issuer,
|
|
2208
|
+
key_size: form.keySize,
|
|
2209
|
+
max_validation_attempts: form.maxValidationAttempts,
|
|
2210
|
+
method_name: props.name,
|
|
2211
|
+
period: props.period ?? "30s",
|
|
2212
|
+
qr_size: form.qrSize,
|
|
2213
|
+
skew: form.skew
|
|
2214
|
+
};
|
|
2215
|
+
};
|
|
2216
|
+
var renameProblem = (from, to) => from === to ? undefined : `renaming from \`${from}\` would strand every enrolled secret. Declare the new method as a ` + "second resource, list both ids on the enforcement, enrol, then remove the old one " + "(mfa-totp.ts).";
|
|
2217
|
+
var problems5 = (props) => {
|
|
2218
|
+
const found = [];
|
|
2219
|
+
const form = canonicalFromProps5(props);
|
|
2220
|
+
if (props.name.trim() === "")
|
|
2221
|
+
found.push("name is empty; it is the only key a declaration has");
|
|
2222
|
+
if (props.issuer.trim() === "")
|
|
2223
|
+
found.push("issuer is empty; the server requires one");
|
|
2224
|
+
if (form.periodSeconds <= 0)
|
|
2225
|
+
found.push(`period \`${props.period ?? ""}\` must be a duration above zero`);
|
|
2226
|
+
if (form.keySize <= 0)
|
|
2227
|
+
found.push("keySize must be above zero");
|
|
2228
|
+
if (form.maxValidationAttempts < 1)
|
|
2229
|
+
found.push("maxValidationAttempts must be at least 1 (0 reads back as 5)");
|
|
2230
|
+
return found;
|
|
2231
|
+
};
|
|
2232
|
+
|
|
2233
|
+
// src/openbao/mfa-totp.ts
|
|
2234
|
+
var BaoMfaTotpMethod = Resource8("Bao.MfaTotpMethod", {
|
|
2235
|
+
defaultRemovalPolicy: "retain"
|
|
2236
|
+
});
|
|
2237
|
+
var refuse4 = (props, message) => Effect19.die(new Error(`Bao.MfaTotpMethod ${props.name}: ${message}`));
|
|
2238
|
+
var readTotp = (props) => Effect19.map(findTotp(props.name), (found) => found === undefined ? undefined : attributesOf9(found.id, found.live));
|
|
2239
|
+
var planTotp = (props) => Effect19.gen(function* () {
|
|
2240
|
+
if (problems5(props).length > 0)
|
|
2241
|
+
return "update";
|
|
2242
|
+
const live = yield* readTotp(props);
|
|
2243
|
+
return live !== undefined && matches8(live, props) ? "noop" : "update";
|
|
2244
|
+
});
|
|
2245
|
+
var reconcileTotp = (props) => Effect19.gen(function* () {
|
|
2246
|
+
const bad = problems5(props);
|
|
2247
|
+
if (bad.length > 0)
|
|
2248
|
+
return yield* refuse4(props, bad.join("; "));
|
|
2249
|
+
const live = yield* readTotp(props);
|
|
2250
|
+
if (live === undefined || !matches8(live, props))
|
|
2251
|
+
yield* writeTotp(writeBody7(props));
|
|
2252
|
+
const after = yield* readTotp(props);
|
|
2253
|
+
if (after === undefined)
|
|
2254
|
+
return yield* refuse4(props, "the write succeeded but no method has that name.");
|
|
2255
|
+
if (!matches8(after, props))
|
|
2256
|
+
return yield* refuse4(props, "the method read back different.");
|
|
2257
|
+
return after;
|
|
2258
|
+
});
|
|
2259
|
+
var BaoMfaTotpMethodProvider = () => Provider8.effect(BaoMfaTotpMethod, Effect19.succeed(BaoMfaTotpMethod.Provider.of({
|
|
2260
|
+
list: () => Effect19.succeed([]),
|
|
2261
|
+
read: Effect19.fn(function* ({ olds }) {
|
|
2262
|
+
return yield* readTotp(olds);
|
|
2263
|
+
}),
|
|
2264
|
+
diff: Effect19.fn(function* ({ news, output }) {
|
|
2265
|
+
if (output === undefined || !isResolved8(news))
|
|
2266
|
+
return;
|
|
2267
|
+
const rename = renameProblem(output.name, news.name);
|
|
2268
|
+
if (rename !== undefined)
|
|
2269
|
+
return yield* refuse4(news, rename);
|
|
2270
|
+
return { action: yield* planTotp(news) };
|
|
2271
|
+
}),
|
|
2272
|
+
reconcile: Effect19.fn(function* ({ news }) {
|
|
2273
|
+
return yield* reconcileTotp(news);
|
|
2274
|
+
}),
|
|
2275
|
+
delete: Effect19.fn(function* ({ output }) {
|
|
2276
|
+
yield* deleteTotp(output.methodId);
|
|
2277
|
+
return;
|
|
2278
|
+
})
|
|
2279
|
+
})));
|
|
2280
|
+
// src/openbao/mount.ts
|
|
2281
|
+
import { Resource as Resource9 } from "alchemy";
|
|
2282
|
+
import { isResolved as isResolved9 } from "alchemy/Diff";
|
|
2283
|
+
import * as Provider9 from "alchemy/Provider";
|
|
2284
|
+
import * as Effect22 from "effect/Effect";
|
|
2285
|
+
|
|
2286
|
+
// src/openbao/mount-reconcile.ts
|
|
2287
|
+
import * as Effect21 from "effect/Effect";
|
|
1241
2288
|
|
|
1242
2289
|
// src/openbao/mount-wire.ts
|
|
1243
|
-
import * as
|
|
1244
|
-
var absentFromTable2 = (path, asked) => baoRead("sys/mounts").pipe(
|
|
2290
|
+
import * as Effect20 from "effect/Effect";
|
|
2291
|
+
var absentFromTable2 = (path, asked) => baoRead("sys/mounts").pipe(Effect20.mapError((listing) => new BaoError(asked.status, asked.method, asked.path, [
|
|
1245
2292
|
...asked.errors,
|
|
1246
2293
|
`sys/mounts could not confirm the mount is absent: ${listing.message}`
|
|
1247
|
-
])),
|
|
1248
|
-
var readMountData = (path) => baoRead(readPath(path)).pipe(
|
|
2294
|
+
])), Effect20.flatMap((table) => table !== undefined && !Object.hasOwn(table, `${mountPath(path)}/`) ? Effect20.succeed(undefined) : Effect20.fail(asked)));
|
|
2295
|
+
var readMountData = (path) => baoRead(readPath(path)).pipe(Effect20.catchIf((error) => error.status === 400, (error) => absentFromTable2(path, error)));
|
|
1249
2296
|
var enableMount = (props) => baoWrite("POST", readPath(props.path), enableBody(props));
|
|
1250
2297
|
var tuneMount = (props) => baoWrite("POST", tunePath(props.path), tuneBody(props));
|
|
1251
2298
|
var disableMount = (path) => baoDelete(readPath(path));
|
|
1252
2299
|
|
|
1253
|
-
// src/openbao/mount.ts
|
|
1254
|
-
var
|
|
1255
|
-
defaultRemovalPolicy: "retain"
|
|
1256
|
-
});
|
|
1257
|
-
var readMount = (props) => Effect10.gen(function* () {
|
|
2300
|
+
// src/openbao/mount-reconcile.ts
|
|
2301
|
+
var readMount = (props) => Effect21.gen(function* () {
|
|
1258
2302
|
const live = yield* readMountData(props.path);
|
|
1259
2303
|
if (live === undefined)
|
|
1260
2304
|
return;
|
|
1261
2305
|
return attributesOf(props, live);
|
|
1262
2306
|
});
|
|
1263
|
-
var
|
|
1264
|
-
|
|
1265
|
-
|
|
2307
|
+
var moveTarget2 = (props) => ({
|
|
2308
|
+
family: "Bao.Mount",
|
|
2309
|
+
path: props.path,
|
|
2310
|
+
read: (path) => readMount({ ...props, path }),
|
|
2311
|
+
type: props.type,
|
|
2312
|
+
wire: mountPath
|
|
2313
|
+
});
|
|
2314
|
+
var reconcileMount = (news, stated, options) => Effect21.gen(function* () {
|
|
2315
|
+
const move = planMove("Bao.Mount", stated, news.path, news.remountFrom);
|
|
2316
|
+
if (move.kind === "refuse")
|
|
2317
|
+
return yield* Effect21.die(new Error(move.message));
|
|
2318
|
+
if (move.kind === "move")
|
|
2319
|
+
yield* performMove(moveTarget2(news), move.from, options);
|
|
2320
|
+
let live = yield* readMount(news);
|
|
2321
|
+
if (live === undefined) {
|
|
2322
|
+
yield* enableMount(news);
|
|
2323
|
+
if (wantsTune(news))
|
|
2324
|
+
yield* tuneMount(news);
|
|
2325
|
+
} else if (!matches(live, news)) {
|
|
2326
|
+
if (live.type !== news.type) {
|
|
2327
|
+
return yield* Effect21.die(new Error(`Bao.Mount ${mountPath(news.path)}: live type ${live.type} != ${news.type}. ` + "Secrets engine type is immutable — replace manually."));
|
|
2328
|
+
}
|
|
2329
|
+
yield* tuneMount(news);
|
|
2330
|
+
}
|
|
2331
|
+
live = yield* readMount(news);
|
|
2332
|
+
if (live === undefined) {
|
|
2333
|
+
return yield* Effect21.die(new Error(`Bao.Mount ${mountPath(news.path)}: write returned no error but the mount is still absent.`));
|
|
2334
|
+
}
|
|
2335
|
+
return live;
|
|
2336
|
+
});
|
|
2337
|
+
|
|
2338
|
+
// src/openbao/mount.ts
|
|
2339
|
+
var BaoMount = Resource9("Bao.Mount", {
|
|
2340
|
+
defaultRemovalPolicy: "retain"
|
|
2341
|
+
});
|
|
2342
|
+
var BaoMountProvider = () => Provider9.effect(BaoMount, Effect22.succeed(BaoMount.Provider.of({
|
|
2343
|
+
list: () => Effect22.succeed([]),
|
|
2344
|
+
read: Effect22.fn(function* ({ olds }) {
|
|
1266
2345
|
return yield* readMount(olds);
|
|
1267
2346
|
}),
|
|
1268
|
-
diff:
|
|
1269
|
-
if (output === undefined || !
|
|
2347
|
+
diff: Effect22.fn(function* ({ news, output }) {
|
|
2348
|
+
if (output === undefined || !isResolved9(news))
|
|
1270
2349
|
return;
|
|
2350
|
+
const move = planMove("Bao.Mount", output.path, news.path, news.remountFrom);
|
|
2351
|
+
if (move.kind === "refuse")
|
|
2352
|
+
return yield* Effect22.die(new Error(move.message));
|
|
2353
|
+
if (move.kind === "move")
|
|
2354
|
+
return { action: "update" };
|
|
1271
2355
|
const live = yield* readMount(news);
|
|
1272
2356
|
if (live === undefined)
|
|
1273
2357
|
return { action: "update" };
|
|
@@ -1277,39 +2361,29 @@ var BaoMountProvider = () => Provider4.effect(BaoMount, Effect10.succeed(BaoMoun
|
|
|
1277
2361
|
return { action: "noop" };
|
|
1278
2362
|
return { action: "update" };
|
|
1279
2363
|
}),
|
|
1280
|
-
reconcile:
|
|
1281
|
-
|
|
1282
|
-
if (live === undefined) {
|
|
1283
|
-
yield* enableMount(news);
|
|
1284
|
-
if (wantsTune(news))
|
|
1285
|
-
yield* tuneMount(news);
|
|
1286
|
-
} else if (!matches(live, news)) {
|
|
1287
|
-
if (live.type !== news.type) {
|
|
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."));
|
|
1289
|
-
}
|
|
1290
|
-
yield* tuneMount(news);
|
|
1291
|
-
}
|
|
1292
|
-
live = yield* readMount(news);
|
|
1293
|
-
if (live === undefined) {
|
|
1294
|
-
return yield* Effect10.die(new Error(`Bao.Mount ${mountPath(news.path)}: write returned no error but the mount is still absent.`));
|
|
1295
|
-
}
|
|
1296
|
-
return live;
|
|
2364
|
+
reconcile: Effect22.fn(function* ({ news, output }) {
|
|
2365
|
+
return yield* reconcileMount(news, output?.path);
|
|
1297
2366
|
}),
|
|
1298
|
-
delete:
|
|
2367
|
+
delete: Effect22.fn(function* ({ output }) {
|
|
1299
2368
|
yield* disableMount(output.path);
|
|
1300
2369
|
return;
|
|
1301
2370
|
})
|
|
1302
2371
|
})));
|
|
1303
2372
|
// src/openbao/pki-role.ts
|
|
1304
|
-
import { Resource as
|
|
1305
|
-
import { isResolved as
|
|
1306
|
-
import * as
|
|
1307
|
-
import * as
|
|
2373
|
+
import { Resource as Resource10 } from "alchemy";
|
|
2374
|
+
import { isResolved as isResolved10 } from "alchemy/Diff";
|
|
2375
|
+
import * as Provider10 from "alchemy/Provider";
|
|
2376
|
+
import * as Effect23 from "effect/Effect";
|
|
1308
2377
|
|
|
1309
|
-
// src/openbao/pki-role-
|
|
2378
|
+
// src/openbao/pki-role-canonical.ts
|
|
2379
|
+
var DEFAULT_KEY_USAGE = [
|
|
2380
|
+
"DigitalSignature",
|
|
2381
|
+
"KeyAgreement",
|
|
2382
|
+
"KeyEncipherment"
|
|
2383
|
+
];
|
|
1310
2384
|
var sorted = (values) => [...values].sort();
|
|
1311
2385
|
var foldedUsages = (values) => sorted(values.map((v) => v.toLowerCase()));
|
|
1312
|
-
var
|
|
2386
|
+
var canonical7 = (input) => ({
|
|
1313
2387
|
allowBareDomains: input.allowBareDomains,
|
|
1314
2388
|
allowGlobDomains: input.allowGlobDomains,
|
|
1315
2389
|
allowIpSans: input.allowIpSans,
|
|
@@ -1317,18 +2391,24 @@ var canonical2 = (input) => ({
|
|
|
1317
2391
|
allowSubdomains: input.allowSubdomains,
|
|
1318
2392
|
allowWildcardCertificates: input.allowWildcardCertificates,
|
|
1319
2393
|
allowedDomains: sorted(input.allowedDomains),
|
|
2394
|
+
allowedDomainsTemplate: input.allowedDomainsTemplate,
|
|
1320
2395
|
clientFlag: input.clientFlag,
|
|
1321
2396
|
cnValidations: sorted(input.cnValidations),
|
|
2397
|
+
enforceHostnames: input.enforceHostnames,
|
|
1322
2398
|
extKeyUsage: foldedUsages(input.extKeyUsage),
|
|
2399
|
+
generateLease: input.generateLease,
|
|
1323
2400
|
keyBits: input.keyBits,
|
|
1324
2401
|
keyType: input.keyType,
|
|
2402
|
+
keyUsage: foldedUsages(input.keyUsage),
|
|
1325
2403
|
maxTtlSeconds: input.maxTtlSeconds,
|
|
2404
|
+
noStore: input.noStore,
|
|
2405
|
+
requireCn: input.requireCn,
|
|
1326
2406
|
serverFlag: input.serverFlag,
|
|
1327
2407
|
signatureBits: input.signatureBits,
|
|
1328
2408
|
ttlSeconds: input.ttlSeconds
|
|
1329
2409
|
});
|
|
1330
|
-
var
|
|
1331
|
-
var
|
|
2410
|
+
var seconds3 = (text10) => parseDuration(text10) ?? -1;
|
|
2411
|
+
var canonicalFromProps6 = (props) => canonical7({
|
|
1332
2412
|
allowBareDomains: props.allowBareDomains ?? false,
|
|
1333
2413
|
allowGlobDomains: props.allowGlobDomains ?? false,
|
|
1334
2414
|
allowIpSans: props.allowIpSans ?? false,
|
|
@@ -1336,21 +2416,27 @@ var canonicalFromProps = (props) => canonical2({
|
|
|
1336
2416
|
allowSubdomains: props.allowSubdomains ?? false,
|
|
1337
2417
|
allowWildcardCertificates: props.allowWildcardCertificates ?? false,
|
|
1338
2418
|
allowedDomains: props.allowedDomains,
|
|
2419
|
+
allowedDomainsTemplate: props.allowedDomainsTemplate ?? false,
|
|
1339
2420
|
clientFlag: props.clientFlag ?? true,
|
|
1340
2421
|
cnValidations: props.cnValidations ?? ["hostname"],
|
|
2422
|
+
enforceHostnames: props.enforceHostnames ?? true,
|
|
1341
2423
|
extKeyUsage: props.extKeyUsage ?? [],
|
|
2424
|
+
generateLease: props.generateLease ?? false,
|
|
1342
2425
|
keyBits: props.keyBits ?? 0,
|
|
1343
2426
|
keyType: props.keyType ?? "rsa",
|
|
1344
|
-
|
|
2427
|
+
keyUsage: props.keyUsage ?? DEFAULT_KEY_USAGE,
|
|
2428
|
+
maxTtlSeconds: seconds3(props.maxTtl),
|
|
2429
|
+
noStore: props.noStore ?? false,
|
|
2430
|
+
requireCn: props.requireCn ?? true,
|
|
1345
2431
|
serverFlag: props.serverFlag ?? true,
|
|
1346
2432
|
signatureBits: props.signatureBits ?? 0,
|
|
1347
|
-
ttlSeconds:
|
|
2433
|
+
ttlSeconds: seconds3(props.ttl)
|
|
1348
2434
|
});
|
|
1349
2435
|
var bool2 = (value, fallback) => typeof value === "boolean" ? value : fallback;
|
|
1350
2436
|
var int2 = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
1351
2437
|
var list = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
1352
2438
|
var str = (value, fallback) => typeof value === "string" ? value : fallback;
|
|
1353
|
-
var
|
|
2439
|
+
var canonicalFromLive6 = (live) => canonical7({
|
|
1354
2440
|
allowBareDomains: bool2(live["allow_bare_domains"], false),
|
|
1355
2441
|
allowGlobDomains: bool2(live["allow_glob_domains"], false),
|
|
1356
2442
|
allowIpSans: bool2(live["allow_ip_sans"], false),
|
|
@@ -1358,16 +2444,25 @@ var canonicalFromLive = (live) => canonical2({
|
|
|
1358
2444
|
allowSubdomains: bool2(live["allow_subdomains"], false),
|
|
1359
2445
|
allowWildcardCertificates: bool2(live["allow_wildcard_certificates"], false),
|
|
1360
2446
|
allowedDomains: list(live["allowed_domains"]),
|
|
2447
|
+
allowedDomainsTemplate: bool2(live["allowed_domains_template"], false),
|
|
1361
2448
|
clientFlag: bool2(live["client_flag"], true),
|
|
1362
2449
|
cnValidations: list(live["cn_validations"]),
|
|
2450
|
+
enforceHostnames: bool2(live["enforce_hostnames"], false),
|
|
1363
2451
|
extKeyUsage: list(live["ext_key_usage"]),
|
|
2452
|
+
generateLease: bool2(live["generate_lease"], false),
|
|
1364
2453
|
keyBits: int2(live["key_bits"], 0),
|
|
1365
2454
|
keyType: str(live["key_type"], "rsa"),
|
|
2455
|
+
keyUsage: list(live["key_usage"]),
|
|
1366
2456
|
maxTtlSeconds: ttlSeconds(live["max_ttl"]) ?? -1,
|
|
2457
|
+
noStore: bool2(live["no_store"], false),
|
|
2458
|
+
requireCn: bool2(live["require_cn"], false),
|
|
1367
2459
|
serverFlag: bool2(live["server_flag"], true),
|
|
1368
2460
|
signatureBits: int2(live["signature_bits"], 0),
|
|
1369
2461
|
ttlSeconds: ttlSeconds(live["ttl"]) ?? -1
|
|
1370
2462
|
});
|
|
2463
|
+
var digestOf = (form) => sha256(JSON.stringify(form));
|
|
2464
|
+
|
|
2465
|
+
// src/openbao/pki-role-form.ts
|
|
1371
2466
|
var ttlText2 = (value) => {
|
|
1372
2467
|
if (value < 0)
|
|
1373
2468
|
return "";
|
|
@@ -1379,10 +2474,9 @@ var ttlText2 = (value) => {
|
|
|
1379
2474
|
return `${String(value / 60)}m`;
|
|
1380
2475
|
return `${String(value)}s`;
|
|
1381
2476
|
};
|
|
1382
|
-
var
|
|
1383
|
-
var
|
|
1384
|
-
|
|
1385
|
-
const form = canonicalFromLive(live);
|
|
2477
|
+
var rolePath4 = (props) => `${mountPath(props.mount ?? "pki")}/roles/${props.name}`;
|
|
2478
|
+
var attributesOf10 = (props, live) => {
|
|
2479
|
+
const form = canonicalFromLive6(live);
|
|
1386
2480
|
return {
|
|
1387
2481
|
...form,
|
|
1388
2482
|
name: props.name,
|
|
@@ -1392,8 +2486,8 @@ var attributesOf5 = (props, live) => {
|
|
|
1392
2486
|
digest: digestOf(form)
|
|
1393
2487
|
};
|
|
1394
2488
|
};
|
|
1395
|
-
var
|
|
1396
|
-
const form =
|
|
2489
|
+
var writeBody8 = (props) => {
|
|
2490
|
+
const form = canonicalFromProps6(props);
|
|
1397
2491
|
const body = {
|
|
1398
2492
|
allowed_domains: form.allowedDomains.join(","),
|
|
1399
2493
|
allow_bare_domains: String(form.allowBareDomains),
|
|
@@ -1409,76 +2503,94 @@ var writeBody3 = (props) => {
|
|
|
1409
2503
|
key_bits: String(form.keyBits),
|
|
1410
2504
|
signature_bits: String(form.signatureBits),
|
|
1411
2505
|
ttl: props.ttl,
|
|
1412
|
-
max_ttl: props.maxTtl
|
|
2506
|
+
max_ttl: props.maxTtl,
|
|
2507
|
+
require_cn: String(form.requireCn),
|
|
2508
|
+
enforce_hostnames: String(form.enforceHostnames),
|
|
2509
|
+
key_usage: (props.keyUsage ?? DEFAULT_KEY_USAGE).join(","),
|
|
2510
|
+
allowed_domains_template: String(form.allowedDomainsTemplate),
|
|
2511
|
+
no_store: String(form.noStore),
|
|
2512
|
+
generate_lease: String(form.generateLease)
|
|
1413
2513
|
};
|
|
1414
2514
|
const usages = props.extKeyUsage ?? [];
|
|
1415
2515
|
if (usages.length > 0)
|
|
1416
2516
|
body["ext_key_usage"] = usages.join(",");
|
|
1417
2517
|
return body;
|
|
1418
2518
|
};
|
|
1419
|
-
var
|
|
2519
|
+
var matches9 = (attributes, props) => attributes.digest === digestOf(canonicalFromProps6(props));
|
|
2520
|
+
var problems6 = (props) => {
|
|
2521
|
+
const found = [];
|
|
2522
|
+
if (props.allowedDomains.length === 0) {
|
|
2523
|
+
found.push("allowedDomains is empty. With allow_any_name false that role can issue no certificate at all.");
|
|
2524
|
+
}
|
|
2525
|
+
const bad = [
|
|
2526
|
+
["ttl", props.ttl],
|
|
2527
|
+
["maxTtl", props.maxTtl]
|
|
2528
|
+
].filter(([, text10]) => parseDuration(text10 ?? "") === undefined);
|
|
2529
|
+
if (bad.length > 0) {
|
|
2530
|
+
found.push(`${bad.map(([k, v]) => `${k ?? ""}=${v ?? ""}`).join(", ")} is not a duration OpenBao ` + "parses (expect 30m, 720h, 8760h or 0).");
|
|
2531
|
+
}
|
|
2532
|
+
if (props.noStore === true && props.generateLease === true) {
|
|
2533
|
+
found.push("noStore and generateLease are both true; OpenBao keeps no_store and drops the lease.");
|
|
2534
|
+
}
|
|
2535
|
+
return found;
|
|
2536
|
+
};
|
|
1420
2537
|
|
|
1421
2538
|
// src/openbao/pki-role.ts
|
|
1422
|
-
var BaoPkiRole =
|
|
2539
|
+
var BaoPkiRole = Resource10("Bao.PkiRole", {
|
|
1423
2540
|
defaultRemovalPolicy: "retain"
|
|
1424
2541
|
});
|
|
1425
|
-
var readRole2 = (props) =>
|
|
1426
|
-
const live = yield* baoRead(
|
|
2542
|
+
var readRole2 = (props) => Effect23.gen(function* () {
|
|
2543
|
+
const live = yield* baoRead(rolePath4(props));
|
|
1427
2544
|
if (live === undefined)
|
|
1428
2545
|
return;
|
|
1429
|
-
return
|
|
2546
|
+
return attributesOf10(props, live);
|
|
1430
2547
|
});
|
|
1431
|
-
var
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
read: Effect11.fn(function* ({ olds }) {
|
|
2548
|
+
var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect23.succeed(BaoPkiRole.Provider.of({
|
|
2549
|
+
list: () => Effect23.succeed([]),
|
|
2550
|
+
read: Effect23.fn(function* ({ olds }) {
|
|
1435
2551
|
return yield* readRole2(olds);
|
|
1436
2552
|
}),
|
|
1437
|
-
diff:
|
|
1438
|
-
if (output === undefined || !
|
|
2553
|
+
diff: Effect23.fn(function* ({ news, output }) {
|
|
2554
|
+
if (output === undefined || !isResolved10(news))
|
|
1439
2555
|
return;
|
|
1440
|
-
if (mountPath(news.mount ?? "pki") !== output.mount)
|
|
2556
|
+
if (mountPath(news.mount ?? "pki") !== output.mount || news.name !== output.name)
|
|
1441
2557
|
return { action: "replace" };
|
|
1442
|
-
if (
|
|
2558
|
+
if (problems6(news).length > 0)
|
|
1443
2559
|
return { action: "update" };
|
|
1444
|
-
}
|
|
1445
2560
|
const live = yield* readRole2(news);
|
|
1446
2561
|
if (live === undefined)
|
|
1447
2562
|
return { action: "update" };
|
|
1448
|
-
return
|
|
2563
|
+
return matches9(live, news) ? { action: "noop" } : { action: "update" };
|
|
1449
2564
|
}),
|
|
1450
|
-
reconcile:
|
|
1451
|
-
const path =
|
|
1452
|
-
|
|
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."));
|
|
1454
|
-
}
|
|
1455
|
-
const bad = badDurations(news);
|
|
2565
|
+
reconcile: Effect23.fn(function* ({ news }) {
|
|
2566
|
+
const path = rolePath4(news);
|
|
2567
|
+
const bad = problems6(news);
|
|
1456
2568
|
if (bad.length > 0) {
|
|
1457
|
-
return yield*
|
|
2569
|
+
return yield* Effect23.die(new Error(`Bao.PkiRole ${path}: ${bad.join(" ")}`));
|
|
1458
2570
|
}
|
|
1459
2571
|
const live = yield* readRole2(news);
|
|
1460
|
-
if (live === undefined || !
|
|
1461
|
-
yield* baoWrite("PUT", path,
|
|
2572
|
+
if (live === undefined || !matches9(live, news)) {
|
|
2573
|
+
yield* baoWrite("PUT", path, writeBody8(news));
|
|
1462
2574
|
}
|
|
1463
2575
|
const after = yield* readRole2(news);
|
|
1464
2576
|
if (after === undefined) {
|
|
1465
|
-
return yield*
|
|
2577
|
+
return yield* Effect23.die(new Error(`Bao.PkiRole ${path}: write reported success but the role is absent.`));
|
|
1466
2578
|
}
|
|
1467
|
-
if (!
|
|
1468
|
-
return yield*
|
|
2579
|
+
if (!matches9(after, news)) {
|
|
2580
|
+
return yield* Effect23.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.`));
|
|
1469
2581
|
}
|
|
1470
2582
|
return after;
|
|
1471
2583
|
}),
|
|
1472
|
-
delete:
|
|
1473
|
-
yield* baoDelete(
|
|
2584
|
+
delete: Effect23.fn(function* ({ output }) {
|
|
2585
|
+
yield* baoDelete(rolePath4(output));
|
|
1474
2586
|
return;
|
|
1475
2587
|
})
|
|
1476
2588
|
})));
|
|
1477
2589
|
// src/openbao/plugin.ts
|
|
1478
|
-
import { Resource as
|
|
1479
|
-
import { isResolved as
|
|
1480
|
-
import * as
|
|
1481
|
-
import * as
|
|
2590
|
+
import { Resource as Resource11 } from "alchemy";
|
|
2591
|
+
import { isResolved as isResolved11 } from "alchemy/Diff";
|
|
2592
|
+
import * as Provider11 from "alchemy/Provider";
|
|
2593
|
+
import * as Effect25 from "effect/Effect";
|
|
1482
2594
|
|
|
1483
2595
|
// src/openbao/plugin-form.ts
|
|
1484
2596
|
var resolve = (props) => ({
|
|
@@ -1490,7 +2602,7 @@ var resolve = (props) => ({
|
|
|
1490
2602
|
version: props.version ?? ""
|
|
1491
2603
|
});
|
|
1492
2604
|
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
|
|
2605
|
+
var problems7 = (form) => {
|
|
1494
2606
|
const found = [];
|
|
1495
2607
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(form.name) || form.name.includes("..")) {
|
|
1496
2608
|
found.push(`name ${JSON.stringify(form.name)} is not a plain catalog name`);
|
|
@@ -1516,25 +2628,25 @@ var registerBody = (form) => ({
|
|
|
1516
2628
|
...form.args.length > 0 ? { args: form.args } : {},
|
|
1517
2629
|
...form.version === "" ? {} : { version: form.version }
|
|
1518
2630
|
});
|
|
1519
|
-
var
|
|
2631
|
+
var text10 = (value) => typeof value === "string" ? value : "";
|
|
1520
2632
|
var list2 = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
1521
|
-
var
|
|
2633
|
+
var attributesOf11 = (form, live) => {
|
|
1522
2634
|
const attrs = {
|
|
1523
2635
|
name: form.name,
|
|
1524
2636
|
type: form.type,
|
|
1525
|
-
version:
|
|
1526
|
-
sha256:
|
|
1527
|
-
command:
|
|
2637
|
+
version: text10(live["version"]),
|
|
2638
|
+
sha256: text10(live["sha256"]).toLowerCase(),
|
|
2639
|
+
command: text10(live["command"]),
|
|
1528
2640
|
args: list2(live["args"]),
|
|
1529
2641
|
builtin: live["builtin"] === true
|
|
1530
2642
|
};
|
|
1531
2643
|
return { ...attrs, digest: sha256(JSON.stringify(attrs)) };
|
|
1532
2644
|
};
|
|
1533
2645
|
var sameArgs = (want, have) => want.length === have.length && want.every((entry, index) => entry === have[index]);
|
|
1534
|
-
var
|
|
2646
|
+
var matches10 = (attributes, form) => !attributes.builtin && attributes.version === form.version && attributes.sha256 === form.sha256 && attributes.command === form.command && sameArgs(form.args, attributes.args);
|
|
1535
2647
|
|
|
1536
2648
|
// src/openbao/plugin-reconcile.ts
|
|
1537
|
-
import * as
|
|
2649
|
+
import * as Effect24 from "effect/Effect";
|
|
1538
2650
|
|
|
1539
2651
|
// src/openbao/plugin-wire.ts
|
|
1540
2652
|
var readPluginData = (form) => baoRead(versionedPath(form.type, form.name, form.version));
|
|
@@ -1542,44 +2654,44 @@ var registerPlugin = (form) => baoWrite("PUT", catalogPath(form.type, form.name)
|
|
|
1542
2654
|
var deregisterPlugin = (type, name, version) => baoDelete(versionedPath(type, name, version));
|
|
1543
2655
|
|
|
1544
2656
|
// src/openbao/plugin-reconcile.ts
|
|
1545
|
-
var readEntry = (form) =>
|
|
1546
|
-
var
|
|
2657
|
+
var readEntry = (form) => Effect24.map(readPluginData(form), (live) => live === undefined ? undefined : { attributes: attributesOf11(form, live), live });
|
|
2658
|
+
var refuse5 = (form, message) => Effect24.die(new Error(`Bao.Plugin ${versionedPath(form.type, form.name, form.version)}: ${message}`));
|
|
1547
2659
|
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) =>
|
|
2660
|
+
var reconcilePlugin = (props) => Effect24.gen(function* () {
|
|
1549
2661
|
const form = resolve(props);
|
|
1550
|
-
const bad =
|
|
2662
|
+
const bad = problems7(form);
|
|
1551
2663
|
if (bad.length > 0)
|
|
1552
|
-
return yield*
|
|
2664
|
+
return yield* refuse5(form, bad.join("; "));
|
|
1553
2665
|
const found = yield* readEntry(form);
|
|
1554
|
-
if (found !== undefined && !
|
|
2666
|
+
if (found !== undefined && !matches10(found.attributes, form)) {
|
|
1555
2667
|
if (found.live["declarative"] === true || found.live["oci"] === true) {
|
|
1556
|
-
return yield*
|
|
2668
|
+
return yield* refuse5(form, "the live entry is declarative/OCI; manage it in server config.");
|
|
1557
2669
|
}
|
|
1558
2670
|
if (found.attributes.builtin) {
|
|
1559
|
-
return yield*
|
|
2671
|
+
return yield* refuse5(form, "this name is a builtin; an unversioned write would shadow it.");
|
|
1560
2672
|
}
|
|
1561
2673
|
}
|
|
1562
|
-
if (found === undefined || !
|
|
2674
|
+
if (found === undefined || !matches10(found.attributes, form))
|
|
1563
2675
|
yield* registerPlugin(form);
|
|
1564
2676
|
const after = yield* readEntry(form);
|
|
1565
|
-
if (after === undefined || !
|
|
1566
|
-
return yield*
|
|
2677
|
+
if (after === undefined || !matches10(after.attributes, form)) {
|
|
2678
|
+
return yield* refuse5(form, SELF_REPORTED);
|
|
1567
2679
|
}
|
|
1568
2680
|
return after.attributes;
|
|
1569
2681
|
});
|
|
1570
2682
|
|
|
1571
2683
|
// src/openbao/plugin.ts
|
|
1572
|
-
var BaoPlugin =
|
|
2684
|
+
var BaoPlugin = Resource11("Bao.Plugin", {
|
|
1573
2685
|
defaultRemovalPolicy: "retain"
|
|
1574
2686
|
});
|
|
1575
|
-
var BaoPluginProvider = () =>
|
|
1576
|
-
list: () =>
|
|
1577
|
-
read:
|
|
2687
|
+
var BaoPluginProvider = () => Provider11.effect(BaoPlugin, Effect25.succeed(BaoPlugin.Provider.of({
|
|
2688
|
+
list: () => Effect25.succeed([]),
|
|
2689
|
+
read: Effect25.fn(function* ({ olds }) {
|
|
1578
2690
|
const found = yield* readEntry(resolve(olds));
|
|
1579
2691
|
return found?.attributes;
|
|
1580
2692
|
}),
|
|
1581
|
-
diff:
|
|
1582
|
-
if (output === undefined || !
|
|
2693
|
+
diff: Effect25.fn(function* ({ news, output }) {
|
|
2694
|
+
if (output === undefined || !isResolved11(news))
|
|
1583
2695
|
return;
|
|
1584
2696
|
const form = resolve(news);
|
|
1585
2697
|
if (output.name !== form.name || output.type !== form.type || output.version !== form.version) {
|
|
@@ -1588,21 +2700,21 @@ var BaoPluginProvider = () => Provider6.effect(BaoPlugin, Effect13.succeed(BaoPl
|
|
|
1588
2700
|
const found = yield* readEntry(form);
|
|
1589
2701
|
if (found === undefined)
|
|
1590
2702
|
return { action: "update" };
|
|
1591
|
-
return
|
|
2703
|
+
return matches10(found.attributes, form) ? { action: "noop" } : { action: "update" };
|
|
1592
2704
|
}),
|
|
1593
|
-
reconcile:
|
|
2705
|
+
reconcile: Effect25.fn(function* ({ news }) {
|
|
1594
2706
|
return yield* reconcilePlugin(news);
|
|
1595
2707
|
}),
|
|
1596
|
-
delete:
|
|
2708
|
+
delete: Effect25.fn(function* ({ output }) {
|
|
1597
2709
|
yield* deregisterPlugin(output.type, output.name, output.version);
|
|
1598
2710
|
return;
|
|
1599
2711
|
})
|
|
1600
2712
|
})));
|
|
1601
2713
|
// src/openbao/policy.ts
|
|
1602
|
-
import { Resource as
|
|
1603
|
-
import { isResolved as
|
|
1604
|
-
import * as
|
|
1605
|
-
import * as
|
|
2714
|
+
import { Resource as Resource12 } from "alchemy";
|
|
2715
|
+
import { isResolved as isResolved12 } from "alchemy/Diff";
|
|
2716
|
+
import * as Provider12 from "alchemy/Provider";
|
|
2717
|
+
import * as Effect27 from "effect/Effect";
|
|
1606
2718
|
import * as FileSystem from "effect/FileSystem";
|
|
1607
2719
|
import * as Path from "effect/Path";
|
|
1608
2720
|
|
|
@@ -1610,32 +2722,32 @@ import * as Path from "effect/Path";
|
|
|
1610
2722
|
var isEmptyAssembly = (parts, joined) => parts === 0 || !/^\s*path\s+"/m.test(joined);
|
|
1611
2723
|
|
|
1612
2724
|
// src/openbao/policy-wire.ts
|
|
1613
|
-
import * as
|
|
2725
|
+
import * as Effect26 from "effect/Effect";
|
|
1614
2726
|
var policyPath = (name) => `sys/policies/acl/${name}`;
|
|
1615
|
-
var readPolicy = (name) =>
|
|
2727
|
+
var readPolicy = (name) => Effect26.flatMap(baoRead(policyPath(name)), (data) => {
|
|
1616
2728
|
if (data === undefined)
|
|
1617
|
-
return
|
|
1618
|
-
const
|
|
1619
|
-
return typeof
|
|
2729
|
+
return Effect26.succeed("");
|
|
2730
|
+
const text11 = data["policy"];
|
|
2731
|
+
return typeof text11 === "string" ? Effect26.succeed(text11) : Effect26.fail(new BaoError(200, "GET", policyPath(name), ["no data.policy in the response"]));
|
|
1620
2732
|
});
|
|
1621
2733
|
var writePolicy = (name, hcl) => baoWrite("PUT", policyPath(name), { policy: hcl });
|
|
1622
2734
|
var deletePolicy = (name) => baoDelete(policyPath(name));
|
|
1623
2735
|
|
|
1624
2736
|
// src/openbao/policy.ts
|
|
1625
|
-
var BaoPolicy =
|
|
2737
|
+
var BaoPolicy = Resource12("Bao.Policy", {
|
|
1626
2738
|
defaultRemovalPolicy: "retain"
|
|
1627
2739
|
});
|
|
1628
2740
|
var grantsOf = (hcl) => (hcl.match(/^path /gm) ?? []).length;
|
|
1629
|
-
var
|
|
2741
|
+
var attributesOf12 = (name, hcl, parts) => ({
|
|
1630
2742
|
name,
|
|
1631
2743
|
digest: sha256(hcl),
|
|
1632
2744
|
grants: grantsOf(hcl),
|
|
1633
2745
|
parts
|
|
1634
2746
|
});
|
|
1635
|
-
var BaoPolicyProvider = () =>
|
|
2747
|
+
var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect27.gen(function* () {
|
|
1636
2748
|
const fs = yield* FileSystem.FileSystem;
|
|
1637
2749
|
const path = yield* Path.Path;
|
|
1638
|
-
const assemble = (dir) =>
|
|
2750
|
+
const assemble = (dir) => Effect27.gen(function* () {
|
|
1639
2751
|
const entries2 = (yield* fs.readDirectory(dir)).filter((f) => f.endsWith(".hcl")).sort();
|
|
1640
2752
|
let joined = "";
|
|
1641
2753
|
for (const f of entries2)
|
|
@@ -1644,67 +2756,67 @@ var BaoPolicyProvider = () => Provider7.effect(BaoPolicy, Effect15.gen(function*
|
|
|
1644
2756
|
return { joined, parts: entries2.length };
|
|
1645
2757
|
});
|
|
1646
2758
|
return BaoPolicy.Provider.of({
|
|
1647
|
-
list: () =>
|
|
1648
|
-
read:
|
|
2759
|
+
list: () => Effect27.succeed([]),
|
|
2760
|
+
read: Effect27.fn(function* ({ olds }) {
|
|
1649
2761
|
const live = yield* readPolicy(olds.name);
|
|
1650
|
-
return live.length === 0 ? undefined :
|
|
2762
|
+
return live.length === 0 ? undefined : attributesOf12(olds.name, live, 0);
|
|
1651
2763
|
}),
|
|
1652
|
-
diff:
|
|
1653
|
-
if (output === undefined || !
|
|
2764
|
+
diff: Effect27.fn(function* ({ news, output }) {
|
|
2765
|
+
if (output === undefined || !isResolved12(news))
|
|
1654
2766
|
return;
|
|
1655
2767
|
const { joined } = yield* assemble(news.fragments);
|
|
1656
2768
|
const live = yield* readPolicy(news.name);
|
|
1657
2769
|
return live.length > 0 && sha256(live) === sha256(joined) ? { action: "noop" } : { action: "update" };
|
|
1658
2770
|
}),
|
|
1659
|
-
reconcile:
|
|
2771
|
+
reconcile: Effect27.fn(function* ({ news }) {
|
|
1660
2772
|
const name = news.name;
|
|
1661
2773
|
const { joined, parts } = yield* assemble(news.fragments);
|
|
1662
2774
|
if (isEmptyAssembly(parts, joined))
|
|
1663
|
-
return yield*
|
|
2775
|
+
return yield* Effect27.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.`));
|
|
1664
2776
|
const current = yield* readPolicy(name);
|
|
1665
2777
|
if (current.length > 0 && sha256(current) === sha256(joined)) {
|
|
1666
|
-
return
|
|
2778
|
+
return attributesOf12(name, joined, parts);
|
|
1667
2779
|
}
|
|
1668
2780
|
yield* writePolicy(name, joined);
|
|
1669
|
-
return
|
|
2781
|
+
return attributesOf12(name, joined, parts);
|
|
1670
2782
|
}),
|
|
1671
|
-
delete:
|
|
2783
|
+
delete: Effect27.fn(function* ({ output }) {
|
|
1672
2784
|
yield* deletePolicy(output.name);
|
|
1673
2785
|
return;
|
|
1674
2786
|
})
|
|
1675
2787
|
});
|
|
1676
2788
|
}));
|
|
1677
2789
|
// src/openbao/proxmox-role.ts
|
|
1678
|
-
import { Resource as
|
|
1679
|
-
import { isResolved as
|
|
1680
|
-
import * as
|
|
1681
|
-
import * as
|
|
2790
|
+
import { Resource as Resource13 } from "alchemy";
|
|
2791
|
+
import { isResolved as isResolved13 } from "alchemy/Diff";
|
|
2792
|
+
import * as Provider13 from "alchemy/Provider";
|
|
2793
|
+
import * as Effect28 from "effect/Effect";
|
|
1682
2794
|
|
|
1683
2795
|
// src/openbao/proxmox-role-form.ts
|
|
1684
|
-
var
|
|
1685
|
-
var ttlText3 = (
|
|
1686
|
-
if (
|
|
2796
|
+
var text11 = (value) => typeof value === "string" ? value : "";
|
|
2797
|
+
var ttlText3 = (seconds4, fallback = "0") => {
|
|
2798
|
+
if (seconds4 === undefined)
|
|
1687
2799
|
return fallback;
|
|
1688
|
-
if (
|
|
1689
|
-
return `${String(
|
|
1690
|
-
if (
|
|
1691
|
-
return `${String(
|
|
1692
|
-
if (
|
|
1693
|
-
return `${String(
|
|
1694
|
-
return `${String(
|
|
1695
|
-
};
|
|
1696
|
-
var
|
|
1697
|
-
var
|
|
2800
|
+
if (seconds4 % 86400 === 0)
|
|
2801
|
+
return `${String(seconds4 / 86400)}d`;
|
|
2802
|
+
if (seconds4 % 3600 === 0)
|
|
2803
|
+
return `${String(seconds4 / 3600)}h`;
|
|
2804
|
+
if (seconds4 % 60 === 0)
|
|
2805
|
+
return `${String(seconds4 / 60)}m`;
|
|
2806
|
+
return `${String(seconds4)}s`;
|
|
2807
|
+
};
|
|
2808
|
+
var rolePath5 = (mount, name) => `${mountPath(mount)}/roles/${name}`;
|
|
2809
|
+
var attributesOf13 = (props, live) => {
|
|
1698
2810
|
const attrs = {
|
|
1699
2811
|
mount: mountPath(props.mount),
|
|
1700
|
-
name:
|
|
1701
|
-
mintUser:
|
|
2812
|
+
name: text11(live["name"]) || props.name,
|
|
2813
|
+
mintUser: text11(live["mint_user"]),
|
|
1702
2814
|
ttl: ttlText3(ttlSeconds(live["ttl"])),
|
|
1703
2815
|
maxTtl: ttlText3(ttlSeconds(live["max_ttl"]))
|
|
1704
2816
|
};
|
|
1705
2817
|
return { ...attrs, digest: sha256(JSON.stringify(attrs)) };
|
|
1706
2818
|
};
|
|
1707
|
-
var
|
|
2819
|
+
var writeBody9 = (props) => {
|
|
1708
2820
|
const ttl = parseDuration(props.ttl);
|
|
1709
2821
|
const maxTtl = parseDuration(props.maxTtl);
|
|
1710
2822
|
const bad = [];
|
|
@@ -1724,68 +2836,68 @@ var sameTtl5 = (want, have) => {
|
|
|
1724
2836
|
const haveSeconds = ttlSeconds(have);
|
|
1725
2837
|
return wantSeconds !== undefined && wantSeconds === haveSeconds;
|
|
1726
2838
|
};
|
|
1727
|
-
var
|
|
2839
|
+
var matches11 = (attributes, props) => attributes.mintUser === props.mintUser && sameTtl5(props.ttl, attributes.ttl) && sameTtl5(props.maxTtl, attributes.maxTtl);
|
|
1728
2840
|
|
|
1729
2841
|
// src/openbao/proxmox-role.ts
|
|
1730
|
-
var BaoProxmoxRole =
|
|
2842
|
+
var BaoProxmoxRole = Resource13("Bao.ProxmoxRole", {
|
|
1731
2843
|
defaultRemovalPolicy: "retain"
|
|
1732
2844
|
});
|
|
1733
|
-
var readRole3 = (props) =>
|
|
1734
|
-
const live = yield* baoRead(
|
|
2845
|
+
var readRole3 = (props) => Effect28.gen(function* () {
|
|
2846
|
+
const live = yield* baoRead(rolePath5(props.mount, props.name));
|
|
1735
2847
|
if (live === undefined)
|
|
1736
2848
|
return;
|
|
1737
|
-
return
|
|
2849
|
+
return attributesOf13(props, live);
|
|
1738
2850
|
});
|
|
1739
|
-
var BaoProxmoxRoleProvider = () =>
|
|
1740
|
-
list: () =>
|
|
1741
|
-
read:
|
|
2851
|
+
var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect28.succeed(BaoProxmoxRole.Provider.of({
|
|
2852
|
+
list: () => Effect28.succeed([]),
|
|
2853
|
+
read: Effect28.fn(function* ({ olds }) {
|
|
1742
2854
|
return yield* readRole3(olds);
|
|
1743
2855
|
}),
|
|
1744
|
-
diff:
|
|
1745
|
-
if (output === undefined || !
|
|
2856
|
+
diff: Effect28.fn(function* ({ news, output }) {
|
|
2857
|
+
if (output === undefined || !isResolved13(news))
|
|
1746
2858
|
return;
|
|
1747
2859
|
const live = yield* readRole3(news);
|
|
1748
2860
|
if (live === undefined)
|
|
1749
2861
|
return { action: "update" };
|
|
1750
|
-
if (
|
|
2862
|
+
if (matches11(live, news))
|
|
1751
2863
|
return { action: "noop" };
|
|
1752
2864
|
return { action: "update" };
|
|
1753
2865
|
}),
|
|
1754
|
-
reconcile:
|
|
2866
|
+
reconcile: Effect28.fn(function* ({ news }) {
|
|
1755
2867
|
const live = yield* readRole3(news);
|
|
1756
2868
|
if (live !== undefined && live.mintUser !== news.mintUser && news.allowMintUserChange !== live.mintUser) {
|
|
1757
|
-
return yield*
|
|
2869
|
+
return yield* Effect28.die(new Error(`Bao.ProxmoxRole ${rolePath5(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}'.`));
|
|
1758
2870
|
}
|
|
1759
|
-
if (live === undefined || !
|
|
1760
|
-
const write =
|
|
2871
|
+
if (live === undefined || !matches11(live, news)) {
|
|
2872
|
+
const write = writeBody9(news);
|
|
1761
2873
|
if (!write.ok) {
|
|
1762
|
-
return yield*
|
|
2874
|
+
return yield* Effect28.die(new Error(`Bao.ProxmoxRole ${rolePath5(news.mount, news.name)}: unparseable duration ` + `${write.bad.join(", ")}. Use 30s / 5m / 1h / 1d, or 0.`));
|
|
1763
2875
|
}
|
|
1764
|
-
yield* baoWrite("PUT",
|
|
2876
|
+
yield* baoWrite("PUT", rolePath5(news.mount, news.name), write.body);
|
|
1765
2877
|
}
|
|
1766
2878
|
const after = yield* readRole3(news);
|
|
1767
2879
|
if (after === undefined) {
|
|
1768
|
-
return yield*
|
|
2880
|
+
return yield* Effect28.die(new Error(`Bao.ProxmoxRole ${rolePath5(news.mount, news.name)}: write returned no error ` + "but the role is still absent."));
|
|
1769
2881
|
}
|
|
1770
2882
|
return after;
|
|
1771
2883
|
}),
|
|
1772
|
-
delete:
|
|
1773
|
-
yield* baoDelete(
|
|
2884
|
+
delete: Effect28.fn(function* ({ output }) {
|
|
2885
|
+
yield* baoDelete(rolePath5(output.mount, output.name));
|
|
1774
2886
|
return;
|
|
1775
2887
|
})
|
|
1776
2888
|
})));
|
|
1777
2889
|
// src/openbao/ssh-role.ts
|
|
1778
|
-
import { Resource as
|
|
1779
|
-
import { isResolved as
|
|
1780
|
-
import * as
|
|
1781
|
-
import * as
|
|
2890
|
+
import { Resource as Resource14 } from "alchemy";
|
|
2891
|
+
import { isResolved as isResolved14 } from "alchemy/Diff";
|
|
2892
|
+
import * as Provider14 from "alchemy/Provider";
|
|
2893
|
+
import * as Effect29 from "effect/Effect";
|
|
1782
2894
|
|
|
1783
2895
|
// src/openbao/ssh-role-form.ts
|
|
1784
2896
|
var readPath4 = (mount, name) => `${sshMountOf(mount)}/roles/${name}`;
|
|
1785
|
-
var
|
|
2897
|
+
var text12 = (value) => typeof value === "string" ? value : "";
|
|
1786
2898
|
var bool3 = (value, fallback) => typeof value === "boolean" ? value : fallback;
|
|
1787
2899
|
var csvList = (value) => {
|
|
1788
|
-
const parts = Array.isArray(value) ? value.filter((entry) => typeof entry === "string") :
|
|
2900
|
+
const parts = Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : text12(value).split(",");
|
|
1789
2901
|
return [
|
|
1790
2902
|
...new Set(parts.map((entry) => entry.trim()).filter((entry) => entry.length > 0))
|
|
1791
2903
|
].sort();
|
|
@@ -1796,7 +2908,7 @@ var extMap = (value) => {
|
|
|
1796
2908
|
return out;
|
|
1797
2909
|
const source = value;
|
|
1798
2910
|
for (const key of Object.keys(source).sort())
|
|
1799
|
-
out[key] =
|
|
2911
|
+
out[key] = text12(source[key]);
|
|
1800
2912
|
return out;
|
|
1801
2913
|
};
|
|
1802
2914
|
var resolve2 = (props) => ({
|
|
@@ -1817,12 +2929,12 @@ var resolve2 = (props) => ({
|
|
|
1817
2929
|
ttl: props.ttl,
|
|
1818
2930
|
maxTtl: props.maxTtl
|
|
1819
2931
|
});
|
|
1820
|
-
var
|
|
2932
|
+
var attributesOf14 = (form, live) => {
|
|
1821
2933
|
const attrs = {
|
|
1822
2934
|
name: form.name,
|
|
1823
2935
|
mount: form.mount,
|
|
1824
2936
|
allowedUsers: csvList(live["allowed_users"]),
|
|
1825
|
-
defaultUser:
|
|
2937
|
+
defaultUser: text12(live["default_user"]),
|
|
1826
2938
|
allowUserCertificates: bool3(live["allow_user_certificates"], false),
|
|
1827
2939
|
allowHostCertificates: bool3(live["allow_host_certificates"], false),
|
|
1828
2940
|
allowedDomains: csvList(live["allowed_domains"]),
|
|
@@ -1833,14 +2945,14 @@ var attributesOf9 = (form, live) => {
|
|
|
1833
2945
|
allowedCriticalOptions: csvList(live["allowed_critical_options"]),
|
|
1834
2946
|
defaultCriticalOptions: extMap(live["default_critical_options"]),
|
|
1835
2947
|
allowEmptyPrincipals: bool3(live["allow_empty_principals"], false),
|
|
1836
|
-
keyType:
|
|
2948
|
+
keyType: text12(live["key_type"]) || "ca",
|
|
1837
2949
|
ttlSeconds: ttlSeconds(live["ttl"]) ?? 0,
|
|
1838
2950
|
maxTtlSeconds: ttlSeconds(live["max_ttl"]) ?? 0
|
|
1839
2951
|
};
|
|
1840
2952
|
return { ...attrs, digest: sha256(JSON.stringify(attrs)) };
|
|
1841
2953
|
};
|
|
1842
|
-
var mapValue = (
|
|
1843
|
-
var
|
|
2954
|
+
var mapValue = (map10) => extMap(map10);
|
|
2955
|
+
var writeBody10 = (form) => ({
|
|
1844
2956
|
key_type: "ca",
|
|
1845
2957
|
allowed_users: form.allowedUsers.join(","),
|
|
1846
2958
|
default_user: form.defaultUser,
|
|
@@ -1863,18 +2975,18 @@ var sameMap = (want, have) => {
|
|
|
1863
2975
|
return keys.length === Object.keys(have).length && keys.every((key) => have[key] === want[key]);
|
|
1864
2976
|
};
|
|
1865
2977
|
var sameTtl6 = (want, have) => parseDuration(want) === have;
|
|
1866
|
-
var
|
|
1867
|
-
var
|
|
2978
|
+
var badDurations = (form) => ["ttl", "maxTtl"].filter((field) => parseDuration(form[field]) === undefined);
|
|
2979
|
+
var matches12 = (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);
|
|
1868
2980
|
|
|
1869
2981
|
// src/openbao/ssh-role.ts
|
|
1870
|
-
var BaoSshRole =
|
|
2982
|
+
var BaoSshRole = Resource14("Bao.SshRole", {
|
|
1871
2983
|
defaultRemovalPolicy: "retain"
|
|
1872
2984
|
});
|
|
1873
|
-
var readRole4 = (form) =>
|
|
2985
|
+
var readRole4 = (form) => Effect29.gen(function* () {
|
|
1874
2986
|
const live = yield* baoRead(readPath4(form.mount, form.name));
|
|
1875
2987
|
if (live === undefined)
|
|
1876
2988
|
return;
|
|
1877
|
-
return { attributes:
|
|
2989
|
+
return { attributes: attributesOf14(form, live), live };
|
|
1878
2990
|
});
|
|
1879
2991
|
var UNMANAGED_DEFAULTS = {
|
|
1880
2992
|
algorithm_signer: "default",
|
|
@@ -1889,22 +3001,22 @@ var UNMANAGED_DEFAULTS = {
|
|
|
1889
3001
|
not_before_duration: 30
|
|
1890
3002
|
};
|
|
1891
3003
|
var isDefault = (live, field) => live[field] === undefined || live[field] === "" || live[field] === UNMANAGED_DEFAULTS[field];
|
|
1892
|
-
var
|
|
3004
|
+
var wouldErase2 = (live) => {
|
|
1893
3005
|
const lost = Object.keys(UNMANAGED_DEFAULTS).filter((field) => !isDefault(live, field));
|
|
1894
3006
|
if (Object.keys(extMap(live["allowed_user_key_lengths"])).length > 0) {
|
|
1895
3007
|
lost.push("allowed_user_key_lengths");
|
|
1896
3008
|
}
|
|
1897
3009
|
return lost.sort();
|
|
1898
3010
|
};
|
|
1899
|
-
var
|
|
1900
|
-
var BaoSshRoleProvider = () =>
|
|
1901
|
-
list: () =>
|
|
1902
|
-
read:
|
|
3011
|
+
var refuse6 = (message) => Effect29.die(new Error(message));
|
|
3012
|
+
var BaoSshRoleProvider = () => Provider14.effect(BaoSshRole, Effect29.succeed(BaoSshRole.Provider.of({
|
|
3013
|
+
list: () => Effect29.succeed([]),
|
|
3014
|
+
read: Effect29.fn(function* ({ olds }) {
|
|
1903
3015
|
const found = yield* readRole4(resolve2(olds));
|
|
1904
3016
|
return found?.attributes;
|
|
1905
3017
|
}),
|
|
1906
|
-
diff:
|
|
1907
|
-
if (output === undefined || !
|
|
3018
|
+
diff: Effect29.fn(function* ({ news, output }) {
|
|
3019
|
+
if (output === undefined || !isResolved14(news))
|
|
1908
3020
|
return;
|
|
1909
3021
|
const form = resolve2(news);
|
|
1910
3022
|
if (output.mount !== form.mount || output.name !== form.name) {
|
|
@@ -1913,35 +3025,35 @@ var BaoSshRoleProvider = () => Provider9.effect(BaoSshRole, Effect17.succeed(Bao
|
|
|
1913
3025
|
const found = yield* readRole4(form);
|
|
1914
3026
|
if (found === undefined)
|
|
1915
3027
|
return { action: "update" };
|
|
1916
|
-
return
|
|
3028
|
+
return matches12(found.attributes, form) ? { action: "noop" } : { action: "update" };
|
|
1917
3029
|
}),
|
|
1918
|
-
reconcile:
|
|
3030
|
+
reconcile: Effect29.fn(function* ({ news }) {
|
|
1919
3031
|
const form = resolve2(news);
|
|
1920
3032
|
const path = readPath4(form.mount, form.name);
|
|
1921
|
-
const bad =
|
|
3033
|
+
const bad = badDurations(form);
|
|
1922
3034
|
if (bad.length > 0) {
|
|
1923
|
-
return yield*
|
|
3035
|
+
return yield* refuse6(`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.");
|
|
1924
3036
|
}
|
|
1925
3037
|
if (form.allowUserCertificates && form.allowedUsers.length === 0 && !form.allowEmptyPrincipals) {
|
|
1926
|
-
return yield*
|
|
3038
|
+
return yield* refuse6(`Bao.SshRole ${path}: allowUserCertificates with an empty allowedUsers and ` + "allowEmptyPrincipals false. Every sign request against it would be rejected.");
|
|
1927
3039
|
}
|
|
1928
3040
|
const found = yield* readRole4(form);
|
|
1929
|
-
if (found === undefined || !
|
|
3041
|
+
if (found === undefined || !matches12(found.attributes, form)) {
|
|
1930
3042
|
if (found !== undefined) {
|
|
1931
|
-
const lost =
|
|
3043
|
+
const lost = wouldErase2(found.live);
|
|
1932
3044
|
if (lost.length > 0) {
|
|
1933
|
-
return yield*
|
|
3045
|
+
return yield* refuse6(`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.");
|
|
1934
3046
|
}
|
|
1935
3047
|
}
|
|
1936
|
-
yield* baoWrite("PUT", path,
|
|
3048
|
+
yield* baoWrite("PUT", path, writeBody10(form));
|
|
1937
3049
|
}
|
|
1938
3050
|
const after = yield* readRole4(form);
|
|
1939
3051
|
if (after === undefined) {
|
|
1940
|
-
return yield*
|
|
3052
|
+
return yield* refuse6(`Bao.SshRole ${path}: write returned no error but the role is still absent.`);
|
|
1941
3053
|
}
|
|
1942
3054
|
return after.attributes;
|
|
1943
3055
|
}),
|
|
1944
|
-
delete:
|
|
3056
|
+
delete: Effect29.fn(function* ({ output }) {
|
|
1945
3057
|
yield* baoDelete(readPath4(output.mount, output.name));
|
|
1946
3058
|
return;
|
|
1947
3059
|
})
|
|
@@ -1953,7 +3065,18 @@ export {
|
|
|
1953
3065
|
BaoAuthRoleProvider,
|
|
1954
3066
|
BaoCloudflareRole,
|
|
1955
3067
|
BaoCloudflareRoleProvider,
|
|
3068
|
+
BaoIdentityError,
|
|
3069
|
+
BaoJwtAuthConfig,
|
|
3070
|
+
BaoJwtAuthConfigProvider,
|
|
3071
|
+
BaoJwtRole,
|
|
3072
|
+
BaoJwtRoleProvider,
|
|
3073
|
+
BaoKubernetesRole,
|
|
3074
|
+
BaoKubernetesRoleProvider,
|
|
1956
3075
|
BaoLoginError,
|
|
3076
|
+
BaoMfaLoginEnforcement,
|
|
3077
|
+
BaoMfaLoginEnforcementProvider,
|
|
3078
|
+
BaoMfaTotpMethod,
|
|
3079
|
+
BaoMfaTotpMethodProvider,
|
|
1957
3080
|
BaoMount,
|
|
1958
3081
|
BaoMountProvider,
|
|
1959
3082
|
BaoPkiRole,
|
|
@@ -1967,10 +3090,15 @@ export {
|
|
|
1967
3090
|
BaoSshRole,
|
|
1968
3091
|
BaoSshRoleProvider,
|
|
1969
3092
|
CloudflarePermissionGroups,
|
|
3093
|
+
HOST_ROLE_SEPARATOR,
|
|
1970
3094
|
appRoleLogin,
|
|
1971
3095
|
appRoleLoginEffect,
|
|
3096
|
+
assertBaoIdentity,
|
|
3097
|
+
assertBaoIdentityEffect,
|
|
1972
3098
|
expandAccount,
|
|
1973
3099
|
expandAll,
|
|
3100
|
+
hostAppRoles,
|
|
3101
|
+
hostRoleName,
|
|
1974
3102
|
mountName,
|
|
1975
3103
|
parseRolesConfig,
|
|
1976
3104
|
permissionGroupsFromEngine,
|
|
@@ -1979,5 +3107,5 @@ export {
|
|
|
1979
3107
|
revokeSelfEffect
|
|
1980
3108
|
};
|
|
1981
3109
|
|
|
1982
|
-
//# debugId=
|
|
3110
|
+
//# debugId=7E1DFE85CC398C5464756E2164756E21
|
|
1983
3111
|
//# sourceMappingURL=index.js.map
|