@lamalibre/create-lamaste 4.4.0 → 4.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/package.json +2 -2
  2. package/vendor/docs/00-introduction/how-it-works.md +2 -2
  3. package/vendor/docs/00-introduction/quickstart.md +5 -5
  4. package/vendor/docs/00-introduction/what-is-lamaste.md +5 -5
  5. package/vendor/docs/01-concepts/certificates.md +3 -3
  6. package/vendor/docs/01-concepts/mtls.md +2 -2
  7. package/vendor/docs/01-concepts/security-model.md +2 -2
  8. package/vendor/docs/01-concepts/tickets.md +2 -2
  9. package/vendor/docs/01-concepts/tunneling.md +4 -4
  10. package/vendor/docs/02-guides/agent-setup.md +5 -5
  11. package/vendor/docs/02-guides/certificate-management.md +2 -2
  12. package/vendor/docs/02-guides/cloud-provisioning.md +3 -3
  13. package/vendor/docs/02-guides/desktop-app-setup.md +10 -10
  14. package/vendor/docs/02-guides/installation.md +3 -3
  15. package/vendor/docs/02-guides/onboarding.md +2 -2
  16. package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +2 -2
  17. package/vendor/docs/03-architecture/installer-flow.md +2 -2
  18. package/vendor/docs/03-architecture/installer.md +4 -4
  19. package/vendor/docs/03-architecture/management-flow.md +1 -1
  20. package/vendor/docs/03-architecture/overview.md +5 -5
  21. package/vendor/docs/03-architecture/panel-client.md +6 -6
  22. package/vendor/docs/03-architecture/panel-server.md +2 -2
  23. package/vendor/docs/03-architecture/state-management.md +2 -2
  24. package/vendor/docs/03-architecture/system-overview.md +1 -1
  25. package/vendor/docs/04-api-reference/agent-hosted-plugins.md +11 -11
  26. package/vendor/docs/04-api-reference/app-host.md +3 -3
  27. package/vendor/docs/04-api-reference/lifecycle-hooks.md +5 -5
  28. package/vendor/docs/04-api-reference/onboarding.md +1 -1
  29. package/vendor/docs/04-api-reference/registry-and-oidc.md +3 -1
  30. package/vendor/docs/04-api-reference/tunnels.md +1 -1
  31. package/vendor/docs/05-operations/backup-and-restore.md +1 -1
  32. package/vendor/docs/05-operations/uninstalling.md +1 -1
  33. package/vendor/docs/05-operations/upgrades.md +5 -5
  34. package/vendor/docs/06-reference/config-files.md +2 -2
  35. package/vendor/docs/06-reference/glossary.md +3 -3
  36. package/vendor/docs/06-reference/installer-flags.md +13 -13
  37. package/vendor/docs/06-reference/ports-and-services.md +1 -1
  38. package/vendor/docs/06-reference/troubleshooting.md +8 -8
  39. package/vendor/docs/gatekeeper-design.md +8 -8
  40. package/vendor/docs/identity-directory-design.md +2 -2
  41. package/vendor/docs/package.json +1 -1
  42. package/vendor/docs/todo/lib-cli-desktop-refactor.md +27 -27
  43. package/vendor/gatekeeper/package.json +2 -2
  44. package/vendor/server/package.json +2 -2
  45. package/vendor/server-ui/dist/assets/{index-CESyyK8V.js → index-roDHz3dM.js} +1 -1
  46. package/vendor/server-ui/dist/index.html +1 -1
  47. package/vendor/serverd/package.json +2 -2
  48. package/vendor/serverd/src/lib/plugin-packages.js +30 -22
  49. package/vendor/serverd/src/lib/plugins.js +35 -11
  50. package/vendor/serverd/src/lib/registry-credential.js +37 -0
@@ -29,6 +29,7 @@ import {
29
29
  } from '@lamalibre/lamaste';
30
30
  import { readTarballFiles } from '@lamalibre/lamaste/server';
31
31
  import { httpError } from './app-error.js';
32
+ import { withRegistryUserconfig } from './registry-credential.js';
32
33
 
33
34
  const STATE_DIR = process.env.LAMALIBRE_LAMASTE_STATE_DIR || '/etc/lamalibre/lamaste';
34
35
 
@@ -88,9 +89,11 @@ export async function installedHookEntrySha256(packageName, manifest) {
88
89
  }
89
90
 
90
91
  /**
91
- * Fetch `<package>@<version>` from the registry STATE_DIR is configured
92
- * for (`npm pack`, as the panel's user) and read its manifest and, when
93
- * the manifest names one, its relay hook module — in memory only.
92
+ * Fetch `<package>@<version>` from the registry the panel installs from
93
+ * (`npm pack` through {@link withRegistryUserconfig}: the configured
94
+ * registry credential, else the machine's npm configuration) and read its
95
+ * manifest and, when the manifest names one, its relay hook module — in
96
+ * memory only.
94
97
  *
95
98
  * @param {string} packageName bare `@lamalibre/<name>`
96
99
  * @param {string} version an exact version or a dist-tag (`latest`)
@@ -103,22 +106,24 @@ export async function readRegistryPackage(packageName, version) {
103
106
  try {
104
107
  let packed;
105
108
  try {
106
- const { stdout } = await execa(
107
- 'npm',
108
- [
109
- 'pack',
110
- `${packageName}@${version}`,
111
- '--json',
112
- '--ignore-scripts',
113
- '--pack-destination',
114
- dir,
115
- ],
116
- { cwd: STATE_DIR, timeout: 120000 },
109
+ const { stdout } = await withRegistryUserconfig((env) =>
110
+ execa(
111
+ 'npm',
112
+ [
113
+ 'pack',
114
+ `${packageName}@${version}`,
115
+ '--json',
116
+ '--ignore-scripts',
117
+ '--pack-destination',
118
+ dir,
119
+ ],
120
+ { cwd: STATE_DIR, timeout: 120000, env },
121
+ ),
117
122
  );
118
123
  packed = JSON.parse(stdout);
119
124
  } catch (err) {
120
125
  throw httpError(
121
- `Could not fetch ${packageName}@${version} from the registry: ${(err.stderr || err.message || '').split('\n')[0]}`,
126
+ `Could not fetch ${packageName}@${version} from the registry: ${npmFailureLine(err)}`,
122
127
  502,
123
128
  );
124
129
  }
@@ -171,9 +176,9 @@ function npmFailureLine(err) {
171
176
 
172
177
  /**
173
178
  * The npm Subresource Integrity of `<package>@<version>`'s tarball, as the
174
- * registry STATE_DIR is configured for publishes it — `npm view … dist.integrity`
175
- * as the panel's user, so the registry and credential are the ones plugin
176
- * installs use. Only a sha512 SRI is accepted: it is what the approval
179
+ * registry the panel installs from publishes it — `npm view … dist.integrity`
180
+ * through {@link withRegistryUserconfig}, so the registry and credential
181
+ * are the ones plugin installs use. Only a sha512 SRI is accepted: it is what the approval
177
182
  * binds and what the agent checks the tarball it fetches against.
178
183
  *
179
184
  * @param {string} packageName bare `@lamalibre/<name>`
@@ -186,10 +191,13 @@ export async function readRegistryIntegrity(packageName, version) {
186
191
  const spec = `${packageName}@${version}`;
187
192
  let stdout;
188
193
  try {
189
- ({ stdout } = await execa('npm', ['view', spec, 'dist.integrity', '--json'], {
190
- cwd: STATE_DIR,
191
- timeout: 60000,
192
- }));
194
+ ({ stdout } = await withRegistryUserconfig((env) =>
195
+ execa('npm', ['view', spec, 'dist.integrity', '--json'], {
196
+ cwd: STATE_DIR,
197
+ timeout: 60000,
198
+ env,
199
+ }),
200
+ ));
193
201
  } catch (err) {
194
202
  if (/\bE404\b/.test(`${err.stderr ?? ''}\n${err.stdout ?? ''}`)) {
195
203
  throw httpError(
@@ -25,6 +25,8 @@ import {
25
25
  import { listRegisteredSubScopeNames } from './tickets.js';
26
26
  import { getStateDb } from './state-db.js';
27
27
  import { revokePluginCapabilitiesFromAgents } from './mtls.js';
28
+ import { withRegistryUserconfig } from './registry-credential.js';
29
+ import { RegistryCredentialError } from '@lamalibre/lamaste/server';
28
30
  import {
29
31
  bareLamalibrePackageName,
30
32
  installedHookEntrySha256,
@@ -326,6 +328,17 @@ export async function manifestPolicyError(manifest) {
326
328
  return null;
327
329
  }
328
330
 
331
+ /**
332
+ * What a failed install of `spec` is reported as. A registry that refused
333
+ * the panel's credential says so (the registry host and the status, from
334
+ * {@link withRegistryUserconfig}); anything else is npm's failure, whose
335
+ * output is in the log.
336
+ */
337
+ function installFailure(spec, err) {
338
+ const detail = err instanceof RegistryCredentialError ? `: ${err.message}` : '';
339
+ return `Failed to install "${spec}"${detail}`;
340
+ }
341
+
329
342
  export function installPlugin(packageName, logger) {
330
343
  return withPluginLock(async () => {
331
344
  // Validate package scope
@@ -358,19 +371,25 @@ export function installPlugin(packageName, logger) {
358
371
  }
359
372
  }
360
373
 
361
- // Install the npm package
374
+ // Install the npm package — from the configured registry, as the
375
+ // panel's own identity, when a registry credential is set.
362
376
  logger.info({ packageName: installSpec }, 'Installing plugin package');
363
377
  try {
364
- await execa('npm', ['install', '--ignore-scripts', installSpec], {
365
- cwd: STATE_DIR,
366
- timeout: 120000,
367
- });
378
+ await withRegistryUserconfig(
379
+ (env) =>
380
+ execa('npm', ['install', '--ignore-scripts', installSpec], {
381
+ cwd: STATE_DIR,
382
+ timeout: 120000,
383
+ env,
384
+ }),
385
+ { log: logger },
386
+ );
368
387
  } catch (err) {
369
388
  logger.error(
370
389
  { packageName: installSpec, stderr: err.stderr, err: err.message },
371
390
  'npm install failed',
372
391
  );
373
- throw Object.assign(new Error(`Failed to install "${installSpec}"`), { statusCode: 500 });
392
+ throw Object.assign(new Error(installFailure(installSpec, err)), { statusCode: 500 });
374
393
  }
375
394
 
376
395
  // Read the plugin manifest from the installed package
@@ -688,13 +707,18 @@ export function replacePluginPackage(name, packageName, version, logger) {
688
707
  assertSafeLamalibrePackage(spec);
689
708
  logger?.info({ name, spec }, 'Installing plugin package version');
690
709
  try {
691
- await execa('npm', ['install', '--ignore-scripts', spec], {
692
- cwd: STATE_DIR,
693
- timeout: 120000,
694
- });
710
+ await withRegistryUserconfig(
711
+ (env) =>
712
+ execa('npm', ['install', '--ignore-scripts', spec], {
713
+ cwd: STATE_DIR,
714
+ timeout: 120000,
715
+ env,
716
+ }),
717
+ { log: logger },
718
+ );
695
719
  } catch (err) {
696
720
  logger?.error({ spec, stderr: err.stderr, err: err.message }, 'npm install failed');
697
- throw Object.assign(new Error(`Failed to install "${spec}"`), { statusCode: 502 });
721
+ throw Object.assign(new Error(installFailure(spec, err)), { statusCode: 502 });
698
722
  }
699
723
  const require = createRequire(`${STATE_DIR}/`);
700
724
  const manifestPath = require.resolve(`${bare}/lamaste-plugin.json`);
@@ -12,6 +12,13 @@
12
12
  * install within the hour and limited to ten new tokens per agent per
13
13
  * hour.
14
14
  *
15
+ * The panel's own npm calls — installing a plugin, staging an update with
16
+ * `npm pack`, pinning an agent half with `npm view` — go through
17
+ * {@link withRegistryUserconfig}: with a credential configured, npm is
18
+ * routed to its registry for its scope and authenticated with the service
19
+ * token through a throw-away userconfig; without one, npm runs as it is
20
+ * configured on the machine (the public registry by default).
21
+ *
15
22
  * Also the one piece of policy that belongs here: when a user loses a
16
23
  * `feria-*` group or is deleted, their registry tokens are revoked (best
17
24
  * effort, never blocks the user operation).
@@ -26,6 +33,7 @@ import {
26
33
  revokeRegistryTokensForOwner,
27
34
  lostFeriaGroups,
28
35
  readOidcClientSecret,
36
+ withRegistryUserconfig as withRegistryUserconfigCore,
29
37
  RegistryServiceTokenCache,
30
38
  AgentTokenBroker,
31
39
  RegistryCredentialError,
@@ -102,6 +110,35 @@ async function serviceToken(config) {
102
110
  return (await serviceTokens.resolve(config, await oidcDeps(config))).token;
103
111
  }
104
112
 
113
+ /**
114
+ * Run `fn(env)` — one npm call of the panel's own — against the configured
115
+ * registry: with a credential, `env` names an isolated userconfig (0600,
116
+ * removed afterwards) routing the credential's scope to its registry with
117
+ * the panel's service token; a `401` refreshes the token once and retries.
118
+ * Without a credential `fn({})` runs npm as the machine configures it.
119
+ * A credential file that cannot be read fails the call rather than
120
+ * silently installing from the public registry.
121
+ *
122
+ * @template T
123
+ * @param {(env: Record<string, string>) => Promise<T>} fn
124
+ * @param {{ log?: import('pino').Logger | undefined }} [options]
125
+ * @returns {Promise<T>}
126
+ */
127
+ export async function withRegistryUserconfig(fn, options = {}) {
128
+ const config = await readRegistryCredentialConfig();
129
+ if (!config) return fn({});
130
+ const deps = await oidcDeps(config);
131
+ return withRegistryUserconfigCore(
132
+ config,
133
+ async ({ refresh }) => {
134
+ if (refresh) serviceTokens.invalidate();
135
+ return (await serviceTokens.resolve(config, deps)).token;
136
+ },
137
+ fn,
138
+ { log: options.log },
139
+ );
140
+ }
141
+
105
142
  /**
106
143
  * The calling agent's registry token: the cached one when it is still good
107
144
  * for an install, otherwise a freshly brokered one-hour token — unless the