@lamalibre/create-lamaste 4.4.1 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/scripts/lamaste-priv +234 -18
- package/src/index.js +1 -0
- package/src/lib/backend-cookie.js +65 -0
- package/src/lib/ip-panel-vhost.js +208 -0
- package/src/lib/service-config.js +5 -3
- package/src/tasks/nginx.js +18 -135
- package/src/tasks/redeploy.js +39 -1
- package/vendor/docs/00-introduction/how-it-works.md +2 -2
- package/vendor/docs/00-introduction/quickstart.md +5 -5
- package/vendor/docs/00-introduction/what-is-lamaste.md +5 -5
- package/vendor/docs/01-concepts/authentication.md +1 -1
- package/vendor/docs/01-concepts/authorization.md +66 -48
- package/vendor/docs/01-concepts/certificates.md +3 -3
- package/vendor/docs/01-concepts/mtls.md +2 -2
- package/vendor/docs/01-concepts/security-model.md +70 -33
- package/vendor/docs/01-concepts/tickets.md +2 -2
- package/vendor/docs/01-concepts/tunneling.md +4 -4
- package/vendor/docs/02-guides/agent-setup.md +5 -5
- package/vendor/docs/02-guides/certificate-management.md +2 -2
- package/vendor/docs/02-guides/cloud-provisioning.md +3 -3
- package/vendor/docs/02-guides/desktop-app-setup.md +10 -10
- package/vendor/docs/02-guides/installation.md +3 -3
- package/vendor/docs/02-guides/managing-users.md +113 -25
- package/vendor/docs/02-guides/onboarding.md +2 -2
- package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +2 -2
- package/vendor/docs/02-guides/static-sites.md +4 -0
- package/vendor/docs/03-architecture/installer-flow.md +2 -2
- package/vendor/docs/03-architecture/installer.md +4 -4
- package/vendor/docs/03-architecture/management-flow.md +3 -1
- package/vendor/docs/03-architecture/nginx-configuration.md +229 -24
- package/vendor/docs/03-architecture/overview.md +13 -17
- package/vendor/docs/03-architecture/panel-client.md +6 -6
- package/vendor/docs/03-architecture/panel-server.md +3 -2
- package/vendor/docs/03-architecture/state-management.md +4 -8
- package/vendor/docs/03-architecture/system-overview.md +1 -1
- package/vendor/docs/04-api-reference/agent-hosted-plugins.md +378 -34
- package/vendor/docs/04-api-reference/app-host.md +3 -3
- package/vendor/docs/04-api-reference/gatekeeper.md +78 -5
- package/vendor/docs/04-api-reference/lifecycle-hooks.md +14 -12
- package/vendor/docs/04-api-reference/onboarding.md +1 -1
- package/vendor/docs/04-api-reference/registry-and-oidc.md +3 -3
- package/vendor/docs/04-api-reference/sites.md +4 -0
- package/vendor/docs/04-api-reference/tunnels.md +87 -47
- package/vendor/docs/04-api-reference/users.md +241 -45
- package/vendor/docs/05-operations/backup-and-restore.md +35 -26
- package/vendor/docs/05-operations/monitoring.md +39 -0
- package/vendor/docs/05-operations/uninstalling.md +2 -1
- package/vendor/docs/05-operations/upgrades.md +24 -6
- package/vendor/docs/06-reference/config-files.md +100 -202
- package/vendor/docs/06-reference/glossary.md +3 -3
- package/vendor/docs/06-reference/installer-flags.md +13 -13
- package/vendor/docs/06-reference/ports-and-services.md +25 -26
- package/vendor/docs/06-reference/troubleshooting.md +8 -8
- package/vendor/docs/gatekeeper-design.md +10 -8
- package/vendor/docs/identity-directory-design.md +48 -14
- package/vendor/docs/package.json +1 -1
- package/vendor/docs/todo/lib-cli-desktop-refactor.md +27 -27
- package/vendor/gatekeeper/dist/cli/index.js +0 -0
- package/vendor/gatekeeper/dist/index.d.ts +4 -3
- package/vendor/gatekeeper/dist/index.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/index.js +4 -2
- package/vendor/gatekeeper/dist/index.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/authz.d.ts +9 -0
- package/vendor/gatekeeper/dist/lib/authz.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/lib/authz.js +31 -0
- package/vendor/gatekeeper/dist/lib/authz.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/constants.d.ts +5 -2
- package/vendor/gatekeeper/dist/lib/constants.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/lib/constants.js +5 -2
- package/vendor/gatekeeper/dist/lib/constants.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/grants.d.ts +6 -0
- package/vendor/gatekeeper/dist/lib/grants.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/lib/grants.js +6 -1
- package/vendor/gatekeeper/dist/lib/grants.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/groups.d.ts +5 -0
- package/vendor/gatekeeper/dist/lib/groups.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/lib/groups.js +5 -1
- package/vendor/gatekeeper/dist/lib/groups.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/principals.d.ts +29 -0
- package/vendor/gatekeeper/dist/lib/principals.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/principals.js +67 -0
- package/vendor/gatekeeper/dist/lib/principals.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/tunnels.d.ts +10 -0
- package/vendor/gatekeeper/dist/lib/tunnels.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/tunnels.js +56 -0
- package/vendor/gatekeeper/dist/lib/tunnels.js.map +1 -0
- package/vendor/gatekeeper/dist/server/index.d.ts +2 -1
- package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/index.js +49 -56
- package/vendor/gatekeeper/dist/server/index.js.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts +15 -1
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/authz.js +332 -104
- package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js +61 -4
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/principals.d.ts +3 -0
- package/vendor/gatekeeper/dist/server/routes/principals.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/principals.js +27 -0
- package/vendor/gatekeeper/dist/server/routes/principals.js.map +1 -0
- package/vendor/gatekeeper/package.json +2 -2
- package/vendor/server/bin/lamaste-server.js +15 -1
- package/vendor/server/package.json +2 -2
- package/vendor/server/src/commands/plugin-identity.js +335 -0
- package/vendor/server/src/commands/plugin-web-surface.js +269 -0
- package/vendor/server/src/commands/plugins.js +156 -11
- package/vendor/server/src/commands/registry-credential.js +2 -1
- package/vendor/server/src/commands/sites.js +15 -1
- package/vendor/server/src/commands/tunnels.js +102 -2
- package/vendor/server/src/commands/uninstall.js +3 -1
- package/vendor/server/src/commands/users.js +358 -0
- package/vendor/server/src/plugin-approval.js +186 -5
- package/vendor/server/src/plugin-identity.js +382 -0
- package/vendor/server/src/plugin-web-surface.js +307 -0
- package/vendor/server-ui/dist/assets/index-BZwQ_aQc.css +1 -0
- package/vendor/server-ui/dist/assets/index-BvEfCZYS.js +84 -0
- package/vendor/server-ui/dist/index.html +2 -2
- package/vendor/serverd/package.json +2 -2
- package/vendor/serverd/src/index.js +34 -5
- package/vendor/serverd/src/lib/audience.js +576 -0
- package/vendor/serverd/src/lib/authelia.js +78 -7
- package/vendor/serverd/src/lib/chisel-runtime.js +18 -6
- package/vendor/serverd/src/lib/chisel.js +10 -2
- package/vendor/serverd/src/lib/config.js +68 -43
- package/vendor/serverd/src/lib/directory.js +1113 -0
- package/vendor/serverd/src/lib/entitlements.js +517 -0
- package/vendor/serverd/src/lib/gatekeeper-client.js +226 -10
- package/vendor/serverd/src/lib/hook-pipelines.js +21 -3
- package/vendor/serverd/src/lib/identity-sync.js +905 -0
- package/vendor/serverd/src/lib/migrations/state/0013_identity_directory.sql +158 -0
- package/vendor/serverd/src/lib/migrations/state/0014_identity_ledger.sql +20 -0
- package/vendor/serverd/src/lib/migrations/state/0015_plugin_web_surface.sql +14 -0
- package/vendor/serverd/src/lib/mtls.js +6 -2
- package/vendor/serverd/src/lib/nginx.js +546 -48
- package/vendor/serverd/src/lib/plugin-hooks.js +127 -45
- package/vendor/serverd/src/lib/plugin-hosting.js +33 -5
- package/vendor/serverd/src/lib/plugin-lifecycle.js +227 -13
- package/vendor/serverd/src/lib/plugin-packages.js +74 -2
- package/vendor/serverd/src/lib/plugins.js +95 -7
- package/vendor/serverd/src/lib/registry-credential.js +9 -6
- package/vendor/serverd/src/lib/request-logging.js +19 -0
- package/vendor/serverd/src/lib/retained-data.js +50 -4
- package/vendor/serverd/src/lib/state.js +36 -0
- package/vendor/serverd/src/lib/tunnel-hooks.js +4 -1
- package/vendor/serverd/src/lib/user-access-session.js +21 -11
- package/vendor/serverd/src/lib/user-caller.js +67 -0
- package/vendor/serverd/src/lib/user-surface.js +754 -0
- package/vendor/serverd/src/lib/vhost-reconcile.js +137 -33
- package/vendor/serverd/src/lib/web-surface.js +766 -0
- package/vendor/serverd/src/middleware/user-access-session.js +9 -0
- package/vendor/serverd/src/routes/agent-forwarder.js +4 -2
- package/vendor/serverd/src/routes/invite.js +53 -12
- package/vendor/serverd/src/routes/management/gatekeeper-proxy.js +107 -16
- package/vendor/serverd/src/routes/management/identity-directory.js +79 -0
- package/vendor/serverd/src/routes/management/invitations.js +78 -68
- package/vendor/serverd/src/routes/management/plugin-identity.js +221 -0
- package/vendor/serverd/src/routes/management/plugin-web-surface.js +151 -0
- package/vendor/serverd/src/routes/management/plugins.js +2 -0
- package/vendor/serverd/src/routes/management/registry-credential.js +1 -1
- package/vendor/serverd/src/routes/management/sites.js +143 -65
- package/vendor/serverd/src/routes/management/tunnels.js +109 -22
- package/vendor/serverd/src/routes/management/users.js +290 -208
- package/vendor/serverd/src/routes/management.js +6 -0
- package/vendor/serverd/src/routes/onboarding/provision.js +14 -1
- package/vendor/serverd/src/routes/user-access.js +17 -4
- package/vendor/serverd/src/routes/user-surface.js +69 -0
- package/vendor/server-ui/dist/assets/index-25wo_8av.css +0 -1
- package/vendor/server-ui/dist/assets/index-roDHz3dM.js +0 -84
|
@@ -5,14 +5,17 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Approval binds bytes (see `hook-set.ts` in the core library): one digest
|
|
7
7
|
* (`approvalDigest`) covers the relay hook set — the normalised relay
|
|
8
|
-
* declarations and the SHA-256 of the relay entry module —
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
8
|
+
* declarations and the SHA-256 of the relay entry module — the agent half
|
|
9
|
+
* Lamaste installs onto the hosting agent — its package, version, tarball
|
|
10
|
+
* integrity, service and system packages — and the plugin's identity block
|
|
11
|
+
* — the roles its users hold and the attributes it receives about them.
|
|
12
|
+
* Approval is needed when any exists. Lamaste runs relay hooks, hosts the
|
|
13
|
+
* plugin, installs its agent half and sends it its users only while the
|
|
14
|
+
* installed version's digest is the approved one. Settings are outside the
|
|
15
|
+
* digest, but an update that turns a secret setting into a plain one waits
|
|
16
|
+
* for an administrator's approval all the same (`plugin-lifecycle.js`), and
|
|
17
|
+
* so does one that adds or removes a role or widens the attributes: the
|
|
18
|
+
* same pending update, approved here.
|
|
16
19
|
*
|
|
17
20
|
* The runner is about the relay part alone: it holds a root-owned copy of
|
|
18
21
|
* the approved module that lamaste-priv installed after checking its
|
|
@@ -216,22 +219,44 @@ export function installedAgentHalf(plugin) {
|
|
|
216
219
|
}
|
|
217
220
|
|
|
218
221
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
+
* The web tree a plugin version's approval covers: `{ treeSha256 }` when it
|
|
223
|
+
* declares a web surface (`userSurface.web`), null otherwise. A declared
|
|
224
|
+
* surface without the tree's digest cannot be approved.
|
|
225
|
+
*/
|
|
226
|
+
function installedWebTree(plugin) {
|
|
227
|
+
if (!plugin.userSurface?.web) return null;
|
|
228
|
+
if (typeof plugin.webTreeSha256 !== 'string') {
|
|
229
|
+
throw new Error(`Plugin "${plugin.name}" declares a web surface but its web tree is unknown`);
|
|
230
|
+
}
|
|
231
|
+
return { treeSha256: plugin.webTreeSha256 };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* What approving a plugin version covers — its relay hook set, the agent
|
|
236
|
+
* half Lamaste installs, its identity block (normalized, as the registry
|
|
237
|
+
* stores it) and its web tree — and the digest the approval names.
|
|
238
|
+
* `digest` is null when there is nothing to approve. A plugin without an
|
|
239
|
+
* identity block or a web surface has the digest it had before they
|
|
240
|
+
* existed.
|
|
222
241
|
*/
|
|
223
242
|
export function installedApproval(plugin) {
|
|
224
243
|
const relay = installedRelayHooks(plugin);
|
|
225
244
|
const agentHalf = installedAgentHalf(plugin);
|
|
226
|
-
const
|
|
245
|
+
const identity = plugin.identity ?? null;
|
|
246
|
+
const web = installedWebTree(plugin);
|
|
247
|
+
const required = relay !== null || agentHalf !== null || identity !== null || web !== null;
|
|
227
248
|
return {
|
|
228
249
|
relay,
|
|
229
250
|
agentHalf,
|
|
251
|
+
identity,
|
|
252
|
+
web,
|
|
230
253
|
required,
|
|
231
254
|
digest: required
|
|
232
255
|
? approvalDigest({
|
|
233
256
|
relay: relay ? { set: relay.set, entrySha256: relay.entrySha256 } : null,
|
|
234
257
|
agentHalf,
|
|
258
|
+
identity,
|
|
259
|
+
web,
|
|
235
260
|
})
|
|
236
261
|
: null,
|
|
237
262
|
};
|
|
@@ -243,9 +268,10 @@ function isApproved(approval, state) {
|
|
|
243
268
|
}
|
|
244
269
|
|
|
245
270
|
/**
|
|
246
|
-
* Whether the installed version is approved — it brings
|
|
247
|
-
*
|
|
248
|
-
* Relay hooks run,
|
|
271
|
+
* Whether the installed version is approved — it brings no relay hooks, no
|
|
272
|
+
* agent half Lamaste installs and no identity block, or its digest is the
|
|
273
|
+
* approved one. Relay hooks run, the plugin is hosted and its users are
|
|
274
|
+
* sent to it only then.
|
|
249
275
|
*/
|
|
250
276
|
export async function installedVersionApproved(plugin) {
|
|
251
277
|
const approval = installedApproval(plugin);
|
|
@@ -253,17 +279,40 @@ export async function installedVersionApproved(plugin) {
|
|
|
253
279
|
return isApproved(approval, await readHookState(plugin.name));
|
|
254
280
|
}
|
|
255
281
|
|
|
256
|
-
/**
|
|
282
|
+
/**
|
|
283
|
+
* What the panel shows about a plugin's hooks, its agent half, its identity
|
|
284
|
+
* (`identity`: whether the installed version declares it, whether that
|
|
285
|
+
* version is approved, and the normalized block) and their approval.
|
|
286
|
+
*/
|
|
257
287
|
export async function hookStatus(plugin) {
|
|
258
288
|
const approval = installedApproval(plugin);
|
|
259
|
-
const { relay, agentHalf } = approval;
|
|
289
|
+
const { relay, agentHalf, identity, web } = approval;
|
|
260
290
|
const agent = agentHookSetOf({ hooks: plugin.hooks });
|
|
261
291
|
const state = await readHookState(plugin.name);
|
|
262
292
|
const approved = isApproved(approval, state);
|
|
293
|
+
let pendingUpdate = null;
|
|
294
|
+
if (state?.pendingUpdate) {
|
|
295
|
+
const { withIdentityGrantImpact } = await import('./entitlements.js');
|
|
296
|
+
pendingUpdate = await withIdentityGrantImpact(
|
|
297
|
+
pendingUpdateView(state.pendingUpdate),
|
|
298
|
+
plugin.route,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
263
301
|
return {
|
|
264
302
|
relay: relay ? relayHookSetPreview(relay.set, relay.entrySha256) : null,
|
|
265
303
|
agent,
|
|
266
304
|
agentHalf: agentHalf ? agentHalfPreview(derivePluginRoute(plugin.name), agentHalf) : null,
|
|
305
|
+
identity: {
|
|
306
|
+
declared: identity !== null,
|
|
307
|
+
approved: identity !== null && approved,
|
|
308
|
+
block: identity,
|
|
309
|
+
},
|
|
310
|
+
web: {
|
|
311
|
+
declared: web !== null,
|
|
312
|
+
approved: web !== null && approved,
|
|
313
|
+
treeSha256: web?.treeSha256 ?? null,
|
|
314
|
+
surface: plugin.userSurface?.web ?? null,
|
|
315
|
+
},
|
|
267
316
|
approval: {
|
|
268
317
|
required: approval.required,
|
|
269
318
|
state: !approval.required ? 'not-required' : approved ? 'approved' : 'pending',
|
|
@@ -279,7 +328,7 @@ export async function hookStatus(plugin) {
|
|
|
279
328
|
installedDigest: state?.installedDigest ?? null,
|
|
280
329
|
current: relay !== null && approved && state?.installedDigest === relay.digest,
|
|
281
330
|
},
|
|
282
|
-
pendingUpdate
|
|
331
|
+
pendingUpdate,
|
|
283
332
|
};
|
|
284
333
|
}
|
|
285
334
|
|
|
@@ -287,22 +336,30 @@ export async function hookStatus(plugin) {
|
|
|
287
336
|
export const DIGEST_UNAPPROVED = 'digest.unapproved';
|
|
288
337
|
/** Why an update waits: it turns secret settings into plain ones. */
|
|
289
338
|
export const SETTINGS_SECRET_TO_PLAIN = 'settings.secret-to-plain';
|
|
339
|
+
/** Why an update waits: it adds or removes a role, or widens the attributes it receives. */
|
|
340
|
+
export const IDENTITY_CHANGED = 'identity.changed';
|
|
290
341
|
|
|
291
342
|
/**
|
|
292
343
|
* The reason a staged version waits because what it brings — its relay
|
|
293
|
-
* hooks (`relay`), the agent half Lamaste installs (`agentHalf`)
|
|
294
|
-
*
|
|
344
|
+
* hooks (`relay`), the agent half Lamaste installs (`agentHalf`), its
|
|
345
|
+
* identity block (`identity`), its web tree (`web`); each truthy when it
|
|
346
|
+
* brings one — is not what was approved.
|
|
295
347
|
*
|
|
296
348
|
* @returns {{ code: string, reason: string }}
|
|
297
349
|
*/
|
|
298
|
-
export function digestUnapprovedReason({ relay, agentHalf }) {
|
|
299
|
-
const
|
|
300
|
-
relay &&
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
350
|
+
export function digestUnapprovedReason({ relay, agentHalf, identity = null, web = null }) {
|
|
351
|
+
const parts = [
|
|
352
|
+
...(relay || (!agentHalf && !identity && !web) ? ['relay hooks'] : []),
|
|
353
|
+
...(agentHalf ? ['agent half'] : []),
|
|
354
|
+
...(identity ? ['identity'] : []),
|
|
355
|
+
...(web ? ['web surface'] : []),
|
|
356
|
+
];
|
|
357
|
+
const named =
|
|
358
|
+
parts.length === 1
|
|
359
|
+
? parts[0]
|
|
360
|
+
: `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}`;
|
|
361
|
+
const verb = parts.length === 1 && parts[0] !== 'relay hooks' ? 'is' : 'are';
|
|
362
|
+
return { code: DIGEST_UNAPPROVED, reason: `its ${named} ${verb} not what was approved` };
|
|
306
363
|
}
|
|
307
364
|
|
|
308
365
|
/**
|
|
@@ -314,7 +371,12 @@ export function digestUnapprovedReason({ relay, agentHalf }) {
|
|
|
314
371
|
function pendingApprovalRequired(pending) {
|
|
315
372
|
return (
|
|
316
373
|
pending.approvalRequired ?? [
|
|
317
|
-
digestUnapprovedReason({
|
|
374
|
+
digestUnapprovedReason({
|
|
375
|
+
relay: pending.preview,
|
|
376
|
+
agentHalf: pending.agentHalf,
|
|
377
|
+
identity: pending.identity?.after ?? null,
|
|
378
|
+
web: pending.web?.after ?? null,
|
|
379
|
+
}),
|
|
318
380
|
]
|
|
319
381
|
);
|
|
320
382
|
}
|
|
@@ -323,14 +385,21 @@ function pendingApprovalRequired(pending) {
|
|
|
323
385
|
* A staged update waiting for approval as clients see it — in the hook
|
|
324
386
|
* status and in the update response alike: its version, the digest to
|
|
325
387
|
* approve it with (null when it brings neither relay hooks nor an agent
|
|
326
|
-
* half Lamaste installs: it is approved by its
|
|
327
|
-
* (its relay hook set and the agent half Lamaste
|
|
328
|
-
* it brings none), how it changes the settings
|
|
329
|
-
* `{ added: [{ key, type }], removed: [{ key, type }],
|
|
330
|
-
* from, to }], newRequired: [key], secretToPlain: [key] }`,
|
|
331
|
-
* neither version declares settings — not part of the digest),
|
|
332
|
-
*
|
|
333
|
-
*
|
|
388
|
+
* half Lamaste installs nor an identity block: it is approved by its
|
|
389
|
+
* version), what it brings (its relay hook set and the agent half Lamaste
|
|
390
|
+
* installs, each null when it brings none), how it changes the settings
|
|
391
|
+
* schema (`settings`: `{ added: [{ key, type }], removed: [{ key, type }],
|
|
392
|
+
* retyped: [{ key, from, to }], newRequired: [key], secretToPlain: [key] }`,
|
|
393
|
+
* null when neither version declares settings — not part of the digest),
|
|
394
|
+
* how it changes the identity block (`identity`: `{ before, after,
|
|
395
|
+
* rolesAdded, rolesRemoved, attributesAdded, attributesRemoved,
|
|
396
|
+
* labelsChanged }` with the normalized blocks, null when neither version
|
|
397
|
+
* declares one — part of the digest), its web tree (`web`: `{ before,
|
|
398
|
+
* after }`, the installed version's tree digest and the staged one's
|
|
399
|
+
* `{ sha256, files, totalBytes }`, null when neither declares a web
|
|
400
|
+
* surface — part of the digest), why it waits for an administrator
|
|
401
|
+
* (`approvalRequired`: `[{ code, reason }]`, `digest.unapproved`,
|
|
402
|
+
* `settings.secret-to-plain` and `identity.changed`; never empty), and —
|
|
334
403
|
* when the plugin has auto-approve on — why auto-approve did not cover it
|
|
335
404
|
* (`autoApproveWithheld`, sentences; null otherwise). The stored record
|
|
336
405
|
* names the relay hook set's preview `preview`.
|
|
@@ -343,6 +412,8 @@ export function pendingUpdateView(pending) {
|
|
|
343
412
|
relay: pending.preview ?? null,
|
|
344
413
|
agentHalf: pending.agentHalf ?? null,
|
|
345
414
|
settings: pending.settings ?? null,
|
|
415
|
+
identity: pending.identity ?? null,
|
|
416
|
+
web: pending.web ?? null,
|
|
346
417
|
approvalRequired: pendingApprovalRequired(pending),
|
|
347
418
|
autoApproveWithheld: pending.autoApproveWithheld ?? null,
|
|
348
419
|
};
|
|
@@ -462,12 +533,14 @@ export async function restartRelayHookRunner(plugin, logger) {
|
|
|
462
533
|
|
|
463
534
|
/**
|
|
464
535
|
* An administrator approves what a plugin version brings — its relay hook
|
|
465
|
-
* set
|
|
466
|
-
* shown: the installed version's, or a pending update's.
|
|
467
|
-
* is also approved by naming its `version`, with its
|
|
468
|
-
* one — the only way to approve one that brings neither
|
|
469
|
-
*
|
|
470
|
-
* settings change to the release
|
|
536
|
+
* set, the agent half Lamaste installs and its identity block — with the
|
|
537
|
+
* digest they were shown: the installed version's, or a pending update's.
|
|
538
|
+
* A pending update is also approved by naming its `version`, with its
|
|
539
|
+
* digest when it has one — the only way to approve one that brings neither
|
|
540
|
+
* relay hooks nor an agent half nor an identity block (its digest is
|
|
541
|
+
* null), and what binds the approval of a settings change to the release
|
|
542
|
+
* that was shown. Approving an installed version that declares identity
|
|
543
|
+
* computes its audience and sends it to its agent half.
|
|
471
544
|
*
|
|
472
545
|
* The installed version's approval is recorded here, in the hook state and
|
|
473
546
|
* the audit log. A pending update's is checked against the update that is
|
|
@@ -506,7 +579,7 @@ export async function approveHookSet(
|
|
|
506
579
|
const pendingDigest = pending.digest ?? null;
|
|
507
580
|
if (digest === null && pendingDigest !== null) {
|
|
508
581
|
throw httpError(
|
|
509
|
-
`Version ${version} brings relay hooks
|
|
582
|
+
`Version ${version} brings relay hooks, an agent half or an identity block — approve it with the approval digest it shows`,
|
|
510
583
|
400,
|
|
511
584
|
);
|
|
512
585
|
}
|
|
@@ -530,7 +603,7 @@ export async function approveHookSet(
|
|
|
530
603
|
if (pending && pending.digest === digest) return pendingUpdateApproval(pending);
|
|
531
604
|
if (!approval.required) {
|
|
532
605
|
throw httpError(
|
|
533
|
-
`Plugin "${plugin.name}" has no relay hooks
|
|
606
|
+
`Plugin "${plugin.name}" has no relay hooks, no agent half Lamaste installs and no identity block — nothing to approve`,
|
|
534
607
|
400,
|
|
535
608
|
);
|
|
536
609
|
}
|
|
@@ -553,6 +626,15 @@ export async function approveHookSet(
|
|
|
553
626
|
details: { digest, version: plugin.version, for: 'installed', certSerial },
|
|
554
627
|
});
|
|
555
628
|
await ensureRelayHooksInstalled(plugin, logger);
|
|
629
|
+
// An approved identity block lets the plugin's users through: its
|
|
630
|
+
// audience is computed and sent to its agent half (when it is hosted), and
|
|
631
|
+
// the hosts its user surface is bound to start serving it.
|
|
632
|
+
if (approval.identity) {
|
|
633
|
+
const { refreshPluginIdentity } = await import('./identity-sync.js');
|
|
634
|
+
await refreshPluginIdentity(plugin.name, logger);
|
|
635
|
+
const { refreshUserSurfaceVhosts } = await import('./user-surface.js');
|
|
636
|
+
await refreshUserSurfaceVhosts(plugin.route, logger);
|
|
637
|
+
}
|
|
556
638
|
return { target: 'installed', digest };
|
|
557
639
|
}
|
|
558
640
|
|
|
@@ -69,6 +69,8 @@ import { recordAudit } from './audit.js';
|
|
|
69
69
|
import { onHealthTransition } from './tunnel-hooks.js';
|
|
70
70
|
import { agentLastSeenAt, agentPresenceMap } from './agent-presence.js';
|
|
71
71
|
import { refreshPluginSettingsBestEffort } from './plugin-settings.js';
|
|
72
|
+
import { refreshPluginIdentity } from './identity-sync.js';
|
|
73
|
+
import { refreshUserSurfaceVhosts } from './user-surface.js';
|
|
72
74
|
|
|
73
75
|
const STATE_DIR = process.env.LAMALIBRE_LAMASTE_STATE_DIR || '/etc/lamalibre/lamaste';
|
|
74
76
|
|
|
@@ -311,7 +313,7 @@ export async function hostPlugin(name, agentLabel, { actor, logger, sourceIp })
|
|
|
311
313
|
}
|
|
312
314
|
if (!(await installedVersionApproved(plugin))) {
|
|
313
315
|
throw httpError(
|
|
314
|
-
`Plugin "${name}" brings relay hooks
|
|
316
|
+
`Plugin "${name}" brings relay hooks, an agent half or an identity block that are not approved — review and approve them first`,
|
|
315
317
|
409,
|
|
316
318
|
{ code: 'hooks_not_approved' },
|
|
317
319
|
);
|
|
@@ -339,9 +341,17 @@ export async function hostPlugin(name, agentLabel, { actor, logger, sourceIp })
|
|
|
339
341
|
logger,
|
|
340
342
|
run: async (op) => {
|
|
341
343
|
const result = await runInstallPipeline(op, ctx);
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
344
|
+
if (result.status === 'succeeded') {
|
|
345
|
+
// The agent half starts without settings — or with those it kept on
|
|
346
|
+
// this agent from an earlier hosting: the relay's cache follows it.
|
|
347
|
+
await refreshPluginSettingsBestEffort(plugin.name, logger);
|
|
348
|
+
// A plugin that declares identity learns its users now: its first
|
|
349
|
+
// sync replays the whole audience.
|
|
350
|
+
await refreshPluginIdentity(plugin.name, logger);
|
|
351
|
+
// Its user surface's locations take the body limit of the internal
|
|
352
|
+
// tunnel the hosting brought.
|
|
353
|
+
await refreshUserSurfaceVhosts(plugin.route, logger);
|
|
354
|
+
}
|
|
345
355
|
return result;
|
|
346
356
|
},
|
|
347
357
|
});
|
|
@@ -528,6 +538,9 @@ export async function purgeRetainedData(name, agentLabel, { actor, logger, sourc
|
|
|
528
538
|
plugin: name,
|
|
529
539
|
details: { agentLabel, route: retained.route, package: retained.package },
|
|
530
540
|
});
|
|
541
|
+
// The last data of an uninstalled plugin: its identity ledger goes too.
|
|
542
|
+
const { dropIdentityLedgerIfNoData } = await import('./audience.js');
|
|
543
|
+
await dropIdentityLedgerIfNoData(name, logger);
|
|
531
544
|
return { forgotten: retained };
|
|
532
545
|
}
|
|
533
546
|
if (!(await isActiveAgent(agentLabel))) {
|
|
@@ -613,7 +626,19 @@ export async function acknowledgeAgentPluginConfig(label, route, issuedAt) {
|
|
|
613
626
|
// ---------------------------------------------------------------------------
|
|
614
627
|
|
|
615
628
|
/**
|
|
616
|
-
* Resolve a route to a forward target
|
|
629
|
+
* Resolve a route to a forward target — of the admin forwarder
|
|
630
|
+
* (`agentForwarderPlugin`) and the user surface alike.
|
|
631
|
+
*
|
|
632
|
+
* While the hosting's health is `down` — the monitor's probes found nothing
|
|
633
|
+
* on the relay-loopback port that answers with the installation secret —
|
|
634
|
+
* this refuses with `down` and the health's reason (`tunnel` or `app`), so
|
|
635
|
+
* nothing of an administrator's, an agent's or a user's request reaches
|
|
636
|
+
* whatever holds that port: the admin forwarder answers as for a dead
|
|
637
|
+
* tunnel (the `tunnel:down` fallback, or `502` with the reason), the user
|
|
638
|
+
* surface `503`. Lamaste's own calls — the health probe, settings, hooks,
|
|
639
|
+
* the identity feed — do not come this way, so the monitor still finds the
|
|
640
|
+
* agent half when it is back and the hosting comes up again.
|
|
641
|
+
*
|
|
617
642
|
* @param {string} route
|
|
618
643
|
*/
|
|
619
644
|
export async function resolveForwardTarget(route) {
|
|
@@ -623,6 +648,9 @@ export async function resolveForwardTarget(route) {
|
|
|
623
648
|
if (plugin.status !== 'enabled') return { kind: 'disabled' };
|
|
624
649
|
const record = await ownHosting(plugin);
|
|
625
650
|
if (!record || pluginHostingPhase(record) !== 'active') return { kind: 'not_hosted' };
|
|
651
|
+
if (record.health?.state === 'down') {
|
|
652
|
+
return { kind: 'down', reason: record.health.reason === 'app' ? 'app' : 'tunnel' };
|
|
653
|
+
}
|
|
626
654
|
const tunnels = await readTunnels();
|
|
627
655
|
const tunnel = tunnels.find((t) => t.id === record.tunnelId);
|
|
628
656
|
const maxBodyMb = tunnel?.maxBodySizeMb ?? TUNNEL_MAX_BODY_MB.default;
|