@lamalibre/create-lamaste 4.2.0 → 4.3.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 +205 -2
- package/src/index.js +4 -4
- package/src/lib/node-version.js +114 -0
- package/src/lib/relay-services.js +15 -1
- package/src/lib/service-config.js +113 -0
- package/src/tasks/gatekeeper.js +8 -3
- package/src/tasks/node.js +34 -75
- package/src/tasks/redeploy.js +36 -0
- 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 +6 -3
- package/vendor/docs/01-concepts/authorization.md +29 -17
- package/vendor/docs/01-concepts/certificates.md +3 -3
- package/vendor/docs/01-concepts/mtls.md +2 -2
- package/vendor/docs/01-concepts/nginx-reverse-proxy.md +8 -3
- package/vendor/docs/01-concepts/security-model.md +98 -34
- 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 +34 -16
- 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/onboarding.md +2 -2
- package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +2 -2
- 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 +1 -1
- package/vendor/docs/03-architecture/nginx-configuration.md +91 -26
- package/vendor/docs/03-architecture/overview.md +8 -8
- package/vendor/docs/03-architecture/panel-client.md +6 -6
- package/vendor/docs/03-architecture/panel-server.md +2 -2
- package/vendor/docs/03-architecture/state-management.md +2 -2
- package/vendor/docs/03-architecture/system-overview.md +1 -1
- package/vendor/docs/04-api-reference/agent-hosted-plugins.md +407 -0
- package/vendor/docs/04-api-reference/app-host.md +360 -0
- package/vendor/docs/04-api-reference/audit-and-notifications.md +57 -0
- package/vendor/docs/04-api-reference/certificates.md +95 -38
- package/vendor/docs/04-api-reference/gatekeeper.md +5 -3
- package/vendor/docs/04-api-reference/lifecycle-hooks.md +333 -0
- package/vendor/docs/04-api-reference/onboarding.md +1 -1
- package/vendor/docs/04-api-reference/overview.md +137 -111
- package/vendor/docs/04-api-reference/registry-and-oidc.md +7 -3
- package/vendor/docs/04-api-reference/tunnels.md +15 -13
- package/vendor/docs/05-operations/backup-and-restore.md +1 -1
- package/vendor/docs/05-operations/uninstalling.md +1 -1
- package/vendor/docs/05-operations/upgrades.md +25 -20
- package/vendor/docs/06-reference/config-files.md +190 -97
- 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 +1 -1
- package/vendor/docs/06-reference/troubleshooting.md +33 -8
- package/vendor/docs/_index.json +20 -0
- package/vendor/docs/gatekeeper-design.md +22 -19
- package/vendor/docs/identity-directory-design.md +57 -56
- package/vendor/docs/package.json +1 -1
- package/vendor/docs/todo/lib-cli-desktop-refactor.md +27 -27
- package/vendor/gatekeeper/dist/lib/types.d.ts +12 -0
- package/vendor/gatekeeper/dist/lib/types.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/index.d.ts +2 -0
- package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/index.js +47 -8
- package/vendor/gatekeeper/dist/server/index.js.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/authz.js +233 -141
- package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -1
- package/vendor/gatekeeper/package.json +4 -3
- package/vendor/server/bin/lamaste-server.js +18 -1
- package/vendor/server/package.json +2 -2
- package/vendor/server/src/commands/audit.js +60 -0
- package/vendor/server/src/commands/plugins.js +963 -12
- package/vendor/server/src/commands/push-install.js +317 -0
- package/vendor/server/src/commands/registry-credential.js +2 -1
- package/vendor/server/src/commands/uninstall.js +28 -1
- package/vendor/server/src/ndjson.js +4 -2
- package/vendor/server/src/panel-api.js +32 -4
- package/vendor/server-ui/dist/assets/index-Bc5NCMSv.css +1 -0
- package/vendor/server-ui/dist/assets/index-DBp3cmaU.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 +56 -4
- package/vendor/serverd/src/lib/actor.js +24 -0
- package/vendor/serverd/src/lib/agent-jobs.js +330 -0
- package/vendor/serverd/src/lib/app-error.js +10 -0
- package/vendor/serverd/src/lib/audit.js +145 -0
- package/vendor/serverd/src/lib/hook-effects.js +123 -0
- package/vendor/serverd/src/lib/hook-pipelines.js +902 -0
- package/vendor/serverd/src/lib/migrations/state/0006_plugin_agent.sql +4 -0
- package/vendor/serverd/src/lib/migrations/state/0007_audit_and_notifications.sql +29 -0
- package/vendor/serverd/src/lib/migrations/state/0008_plugin_hooks.sql +70 -0
- package/vendor/serverd/src/lib/migrations/state/0009_plugin_agent_half.sql +53 -0
- package/vendor/serverd/src/lib/migrations/state/0010_plugin_routes_and_retained_data.sql +47 -0
- package/vendor/serverd/src/lib/migrations/state/0011_agent_job_signature.sql +14 -0
- package/vendor/serverd/src/lib/mtls.js +89 -8
- package/vendor/serverd/src/lib/nginx.js +298 -44
- package/vendor/serverd/src/lib/notifications.js +107 -0
- package/vendor/serverd/src/lib/plugin-hooks.js +560 -0
- package/vendor/serverd/src/lib/plugin-hosting.js +688 -0
- package/vendor/serverd/src/lib/plugin-lifecycle.js +603 -0
- package/vendor/serverd/src/lib/plugin-operations.js +295 -0
- package/vendor/serverd/src/lib/plugin-packages.js +249 -0
- package/vendor/serverd/src/lib/plugins.js +298 -68
- package/vendor/serverd/src/lib/push-install.js +88 -2
- package/vendor/serverd/src/lib/relay-hooks.js +100 -0
- package/vendor/serverd/src/lib/relay-signing.js +43 -0
- package/vendor/serverd/src/lib/request-hooks.js +79 -0
- package/vendor/serverd/src/lib/retained-data.js +99 -0
- package/vendor/serverd/src/lib/trust-proxy.js +34 -0
- package/vendor/serverd/src/lib/tunnel-hooks.js +88 -0
- package/vendor/serverd/src/lib/vhost-reconcile.js +260 -0
- package/vendor/serverd/src/middleware/twofa-session.js +11 -1
- package/vendor/serverd/src/routes/agent-forwarder.js +37 -0
- package/vendor/serverd/src/routes/management/agent-panel-authz.js +97 -0
- package/vendor/serverd/src/routes/management/agents.js +262 -1
- package/vendor/serverd/src/routes/management/audit.js +81 -0
- package/vendor/serverd/src/routes/management/certs.js +1 -15
- package/vendor/serverd/src/routes/management/plugins.js +569 -54
- package/vendor/serverd/src/routes/management/tunnels.js +26 -0
- package/vendor/serverd/src/routes/management.js +4 -0
- package/vendor/server-ui/dist/assets/index-B5Wp8lu-.js +0 -257
- package/vendor/server-ui/dist/assets/index-BqWWFlTU.css +0 -1
|
@@ -312,10 +312,40 @@ All three also declare the same request headers inside their `location /` (`TUNN
|
|
|
312
312
|
- **Per location, not per server.** nginx inherits `proxy_set_header` from the enclosing level only when a level declares none of its own. Every tunnel location declares some (`Upgrade`, `Remote-*`), so headers placed at server level never reached the app — it saw `Host: 127.0.0.1:<port>` and no client address. Declared in the location, `Host`, `X-Real-IP`, `X-Forwarded-For` and `X-Forwarded-Proto` now reach the app.
|
|
313
313
|
- **Client-certificate headers are always cleared.** The panel treats `X-SSL-Client-*` from nginx as proof of an mTLS client certificate. A tunnel vhost never passes client-supplied values through, whatever listens on its port.
|
|
314
314
|
- **Identity headers.** Public vhosts also clear `Remote-User`, `Remote-Groups`, `Remote-Name` and `Remote-Email` — a public tunnel has no authenticated user, and an app might trust those headers from an authenticated vhost. Authenticated and restricted vhosts set them from the Gatekeeper's answer, which replaces anything the client sent.
|
|
315
|
+
- **Relay assertions on plugin tunnels.** A plugin tunnel (a vhost with a `pathPrefix`) ends at the agent's lamaste-agentd, which takes the user only from a relay assertion — its port can be reached without nginx, so a `Remote-User` header proves nothing to it. Authenticated and restricted plugin vhosts forward the one Gatekeeper signs for the request (`auth_request_set $relay_assertion $upstream_http_x_lamalibre_relay_assertion;` and `proxy_set_header X-Lamalibre-Relay-Assertion $relay_assertion;`); public plugin vhosts clear the header. App tunnel vhosts are unchanged. See [Security Model](../01-concepts/security-model.md#5c-the-agent-daemon-lamaste-agentd-and-relay-assertions).
|
|
315
316
|
|
|
316
317
|
A tunnel created before `maxBodySizeMb` existed has no value and its vhost has no directive, so nginx applies its built-in 1 MiB limit until a body limit is set on it (`PATCH /api/tunnels/:id` with `maxBodySizeMb`, or **Edit** in the panel). Changing the access mode or body limit re-renders the vhost in place through the write-with-rollback pattern below; a disabled tunnel's vhost stays disabled.
|
|
317
318
|
|
|
318
|
-
Agent panel tunnels (`type: 'panel'`) use a separate mTLS vhost (`writeAgentPanelVhost()`) with no body limit setting.
|
|
319
|
+
Agent panel tunnels (`type: 'panel'`) use a separate mTLS vhost (`writeAgentPanelVhost()`) with no body limit setting. Every request on it is authorized by the panel, which signs the relay assertion the agent's lamaste-agentd requires:
|
|
320
|
+
|
|
321
|
+
```nginx
|
|
322
|
+
# Authorization by the panel: refuses revoked and foreign certificates,
|
|
323
|
+
# otherwise answers with the relay assertion for this one request
|
|
324
|
+
location = /internal/lamaste/agent-panel-authz {
|
|
325
|
+
internal;
|
|
326
|
+
proxy_pass http://127.0.0.1:3100/api/agent-panel/authz/<label>;
|
|
327
|
+
proxy_pass_request_body off;
|
|
328
|
+
proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
|
|
329
|
+
proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
|
|
330
|
+
proxy_set_header X-SSL-Client-Serial $ssl_client_serial;
|
|
331
|
+
proxy_set_header X-Original-Method $request_method;
|
|
332
|
+
proxy_set_header X-Original-URI $request_uri;
|
|
333
|
+
proxy_set_header X-Forwarded-For $remote_addr;
|
|
334
|
+
# ... Cookie, Authorization and Remote-* cleared
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
location / {
|
|
338
|
+
if ($ssl_client_verify != SUCCESS) { return 496; }
|
|
339
|
+
auth_request /internal/lamaste/agent-panel-authz;
|
|
340
|
+
auth_request_set $relay_assertion $upstream_http_x_lamalibre_relay_assertion;
|
|
341
|
+
proxy_pass http://127.0.0.1:<port>;
|
|
342
|
+
proxy_set_header X-Lamalibre-Relay-Assertion $relay_assertion;
|
|
343
|
+
# X-SSL-Client-* are still sent for agents older than 4.3; lamaste-agentd 4.3 removes them unread
|
|
344
|
+
# ...
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
`location /` has no rewrite: the daemon must receive exactly the `$request_uri` the assertion names. The panel admits the admin certificate and the agent's own (`403` otherwise, also when the agent has no enabled panel tunnel); the panel's 2FA does not apply to this subrequest.
|
|
319
349
|
|
|
320
350
|
### 6. Static Site Vhost (`lamalibre-lamaste-site-<id>`)
|
|
321
351
|
|
|
@@ -336,7 +366,8 @@ server {
|
|
|
336
366
|
add_header X-Frame-Options SAMEORIGIN always;
|
|
337
367
|
add_header X-Content-Type-Options nosniff always;
|
|
338
368
|
|
|
339
|
-
# Optional Authelia forward auth (auth_request to 127.0.0.1:9091/api/authz/auth-request
|
|
369
|
+
# Optional Authelia forward auth (auth_request to 127.0.0.1:9091/api/authz/auth-request,
|
|
370
|
+
# X-Original-URL https://blog.example.com$request_uri — the site's own hostname)
|
|
340
371
|
|
|
341
372
|
location / {
|
|
342
373
|
try_files $uri $uri/ =404; # Standard mode
|
|
@@ -362,7 +393,7 @@ Two serving modes based on the `spaMode` flag:
|
|
|
362
393
|
- **Standard**: `try_files $uri $uri/ =404` — serves files as-is, 404 for missing paths
|
|
363
394
|
- **SPA**: `try_files $uri $uri/ /index.html` — falls back to `index.html` for client-side routing
|
|
364
395
|
|
|
365
|
-
Optional Authelia protection is controlled by the `autheliaProtected` flag on the site object.
|
|
396
|
+
Optional Authelia protection is controlled by the `autheliaProtected` flag on the site object. Authelia's rules for a site restricted to some users (`allowedUsers`) are keyed by the site's hostname, so the subrequest names that hostname, never the client's `Host` header (see [below](#authorization-subrequests-name-their-own-hostname)).
|
|
366
397
|
|
|
367
398
|
A custom-domain site may list `aliases` (e.g. `www.myblog.net`). They are served by a second server block that answers with a 301 to the primary host. One certificate lineage, named after the site's domain (`certbot certonly --nginx --cert-name <fqdn> -d <fqdn> -d <alias> ...`), covers the primary name and every alias, so both blocks point at the same `live/<fqdn>/` directory.
|
|
368
399
|
|
|
@@ -417,19 +448,21 @@ server {
|
|
|
417
448
|
# ... standard SSL settings
|
|
418
449
|
client_max_body_size 10m;
|
|
419
450
|
|
|
420
|
-
# Gatekeeper authorization subrequest (handles Authelia validation + grant check)
|
|
451
|
+
# Gatekeeper authorization subrequest (handles Authelia validation + grant check).
|
|
452
|
+
# The tunnel is named in the path — never by the client's Host header.
|
|
421
453
|
location /internal/lamaste/authz {
|
|
422
454
|
internal;
|
|
423
455
|
|
|
424
|
-
proxy_pass http://127.0.0.1:9294/authz/
|
|
456
|
+
proxy_pass http://127.0.0.1:9294/authz/tunnel/app.example.com;
|
|
425
457
|
proxy_pass_request_body off;
|
|
426
458
|
|
|
427
459
|
proxy_set_header Content-Length "";
|
|
428
460
|
proxy_set_header Connection "";
|
|
429
461
|
proxy_set_header X-Original-Method $request_method;
|
|
430
|
-
proxy_set_header X-Original-URL $
|
|
462
|
+
proxy_set_header X-Original-URL https://app.example.com$request_uri;
|
|
431
463
|
proxy_set_header X-Forwarded-For $remote_addr;
|
|
432
|
-
|
|
464
|
+
# ... X-Forwarded-Host, -Proto, -Uri and -Method cleared ("")
|
|
465
|
+
proxy_set_header Host app.example.com;
|
|
433
466
|
proxy_set_header Cookie $http_cookie;
|
|
434
467
|
|
|
435
468
|
proxy_http_version 1.1;
|
|
@@ -459,15 +492,16 @@ server {
|
|
|
459
492
|
proxy_read_timeout 86400s;
|
|
460
493
|
proxy_send_timeout 86400s;
|
|
461
494
|
|
|
462
|
-
# Authentication failure -> redirect to Authelia login
|
|
463
|
-
error_page 401 =302 https://auth.<domain>/?rd
|
|
495
|
+
# Authentication failure -> redirect to Authelia login, back to this vhost
|
|
496
|
+
error_page 401 =302 https://auth.<domain>/?rd=https://app.example.com$request_uri;
|
|
464
497
|
}
|
|
465
498
|
}
|
|
466
499
|
```
|
|
467
500
|
|
|
468
501
|
Key characteristics:
|
|
469
502
|
|
|
470
|
-
- `auth_request` targets the Gatekeeper at `127.0.0.1:9294/authz/
|
|
503
|
+
- `auth_request` targets the Gatekeeper at `127.0.0.1:9294/authz/tunnel/<fqdn>` instead of Authelia directly
|
|
504
|
+
- The subrequest names the tunnel by the vhost's own hostname, written into the configuration — in its path, `X-Original-URL` and `Host` — never by the client's `Host` header (see [below](#authorization-subrequests-name-their-own-hostname))
|
|
471
505
|
- The Gatekeeper validates the Authelia session cookie and returns 200 (authenticated) or 401 (unauthenticated)
|
|
472
506
|
- The upstream receives `Remote-*` headers only as set from the Gatekeeper's response; client-supplied values under those names are replaced
|
|
473
507
|
- 401 redirects to the Authelia login portal
|
|
@@ -489,19 +523,21 @@ server {
|
|
|
489
523
|
# ... standard SSL settings
|
|
490
524
|
client_max_body_size 10m;
|
|
491
525
|
|
|
492
|
-
# Gatekeeper authorization subrequest (handles Authelia validation + grant check)
|
|
526
|
+
# Gatekeeper authorization subrequest (handles Authelia validation + grant check).
|
|
527
|
+
# The tunnel is named in the path — never by the client's Host header.
|
|
493
528
|
location /internal/lamaste/authz {
|
|
494
529
|
internal;
|
|
495
530
|
|
|
496
|
-
proxy_pass http://127.0.0.1:9294/authz/
|
|
531
|
+
proxy_pass http://127.0.0.1:9294/authz/tunnel/restricted-app.example.com;
|
|
497
532
|
proxy_pass_request_body off;
|
|
498
533
|
|
|
499
534
|
proxy_set_header Content-Length "";
|
|
500
535
|
proxy_set_header Connection "";
|
|
501
536
|
proxy_set_header X-Original-Method $request_method;
|
|
502
|
-
proxy_set_header X-Original-URL $
|
|
537
|
+
proxy_set_header X-Original-URL https://restricted-app.example.com$request_uri;
|
|
503
538
|
proxy_set_header X-Forwarded-For $remote_addr;
|
|
504
|
-
|
|
539
|
+
# ... X-Forwarded-Host, -Proto, -Uri and -Method cleared ("")
|
|
540
|
+
proxy_set_header Host restricted-app.example.com;
|
|
505
541
|
proxy_set_header Cookie $http_cookie;
|
|
506
542
|
|
|
507
543
|
proxy_http_version 1.1;
|
|
@@ -531,8 +567,8 @@ server {
|
|
|
531
567
|
proxy_read_timeout 86400s;
|
|
532
568
|
proxy_send_timeout 86400s;
|
|
533
569
|
|
|
534
|
-
# Authentication failure -> redirect to Authelia login
|
|
535
|
-
error_page 401 =302 https://auth.<domain>/?rd
|
|
570
|
+
# Authentication failure -> redirect to Authelia login, back to this vhost
|
|
571
|
+
error_page 401 =302 https://auth.<domain>/?rd=https://restricted-app.example.com$request_uri;
|
|
536
572
|
|
|
537
573
|
# Authorization failure -> Gatekeeper serves inline access-request page
|
|
538
574
|
error_page 403 = /internal/lamaste/authz;
|
|
@@ -546,6 +582,16 @@ Key differences from the authenticated vhost:
|
|
|
546
582
|
- `error_page 403 = /internal/lamaste/authz` replays the request to the Gatekeeper internal location. The `=` prefix causes nginx to use the response body and status code from the Gatekeeper, which serves an inline HTML access-request page with pre-filled message templates
|
|
547
583
|
- 401 (unauthenticated) still redirects to Authelia login as before
|
|
548
584
|
|
|
585
|
+
### Authorization subrequests name their own hostname
|
|
586
|
+
|
|
587
|
+
Every authorization subrequest — to Gatekeeper from an `authenticated` or `restricted` tunnel vhost (app and plugin tunnels), to Authelia from a protected static site and from the auth portal's `/api/user-access/authorize` — names the vhost by its own hostname, written into the configuration: `X-Original-URL https://<fqdn>$request_uri`; for Gatekeeper also the path, `/authz/tunnel/<fqdn>`, and `Host <fqdn>`. A tunnel's 401 login redirect returns to `https://<fqdn>$request_uri`. The client's `X-Forwarded-Host`, `-Proto`, `-Uri` and `-Method` are cleared in these subrequests. Generated vhosts contain no `$http_host`.
|
|
588
|
+
|
|
589
|
+
nginx selects the vhost from the request line when a request is in absolute form (`GET https://a.example.com/x HTTP/1.1`) and from `Host` otherwise, so a client can send the request line of one hostname with the `Host` header of another. Vhosts written before 4.3 built `X-Original-URL` and `Host` from `$http_host` — the client's `Host` header — so the subrequest asked about the other hostname while nginx served this one: an Authelia user allowed on any tunnel (every user, on an `authenticated` tunnel) reached any `restricted` tunnel, app or plugin, without a grant, and any Authelia user reached a static site restricted to other users. `$host` would not fix it — on the default server it is whatever `Host` the client sent. `$request_uri` is what follows the host in the request target — it starts with `/`, or with `?` for an absolute-form target without a path — so it cannot change the URL's host; the scheme is fixed because every such vhost listens on 443 with TLS only. The generators refuse a hostname that is not a plain DNS name, so nothing but a literal reaches these headers. For the auth portal the change is hardening: no rule Lamaste writes for Authelia is more permissive than the portal's own (the default, `two_factor`), so naming another hostname there gained nothing.
|
|
590
|
+
|
|
591
|
+
Gatekeeper authorizes the tunnel named in the path, and refuses a check whose `X-Original-URL` or `Host` names another hostname, or has no `Host` (401). It refuses `/authz/check` — what vhosts written before 4.3 call, with the client's `Host` in both headers — outright (403, logged as `vhost_predates_host_binding`): a tunnel vhost the panel has not rewritten is closed, not open to the users of other tunnels. The panel rewrites them at startup (see [Rewritten at Startup](#rewritten-at-startup)).
|
|
592
|
+
|
|
593
|
+
Authelia evaluates the raw `$request_uri`, while nginx serves the normalized `$uri` (`/a/../b` is `/b`, `%2e` is `.`). Lamaste writes no path (`resources`) rules into Authelia's `access_control`, only hostname rules; a path rule added by hand would see a different path than nginx serves.
|
|
594
|
+
|
|
549
595
|
## nginx Cache Zone (`lamaste_authz`)
|
|
550
596
|
|
|
551
597
|
The Gatekeeper authorization responses are cached by nginx to reduce subrequest overhead. The cache zone is defined in a shared snippet:
|
|
@@ -562,12 +608,19 @@ proxy_cache_path /var/cache/nginx/authz levels=1:2 keys_zone=lamaste_authz:1m ma
|
|
|
562
608
|
| `max_size` | `10m` | 10 MB maximum disk storage for cached responses |
|
|
563
609
|
| `inactive` | `5m` | Evict entries not accessed within 5 minutes |
|
|
564
610
|
|
|
565
|
-
|
|
611
|
+
The installer also writes the location-level policy, `/etc/nginx/snippets/lamalibre-lamaste-authz-cache-loc.conf`:
|
|
612
|
+
|
|
613
|
+
```nginx
|
|
614
|
+
proxy_cache lamaste_authz;
|
|
615
|
+
proxy_cache_key "$server_name|$cookie_authelia_session";
|
|
616
|
+
proxy_cache_valid 200 10s;
|
|
617
|
+
proxy_cache_valid 403 0;
|
|
618
|
+
proxy_cache_use_stale off;
|
|
619
|
+
```
|
|
566
620
|
|
|
567
|
-
**Cache
|
|
621
|
+
**Cache key:** `$server_name|$cookie_authelia_session` — one entry per vhost and user session. `$server_name` is the vhost's own name, first and followed by a character no hostname contains, so no other vhost and session give the same key. `$http_host` (in the key before 4.3) is the client's `Host` header, which can name another tunnel than the vhost serving the request (see [above](#authorization-subrequests-name-their-own-hostname)), so a decision cached for that other tunnel would have been replayed. A relay installed before 4.3 keeps the old snippet until it is reinstalled; no vhost includes it.
|
|
568
622
|
|
|
569
|
-
|
|
570
|
-
- `401` / `403` (denied): 10 seconds — allows quick re-check after login or grant changes
|
|
623
|
+
**Cache TTLs (when used):** `200` (authorized) for 10 seconds, matching the `Cache-Control: max-age=10` Gatekeeper sends; `403` never, so a new grant takes effect at once. Gatekeeper marks a plugin tunnel's answer `no-store` — it carries a single-use relay assertion.
|
|
571
624
|
|
|
572
625
|
> **Note:** The cache zone is installed by the Gatekeeper installer task, but the generated vhosts (`buildGatekeeperVhost()`) do not currently include `proxy_cache` directives in the internal location block. Authorization caching currently relies on the Gatekeeper's in-memory session cache (30-second TTL). The nginx cache zone is available for future use if nginx-level caching is needed.
|
|
573
626
|
|
|
@@ -674,14 +727,15 @@ Adding a directive to a generator therefore means adding it to the allow-list in
|
|
|
674
727
|
|
|
675
728
|
## Forward Auth Block
|
|
676
729
|
|
|
677
|
-
The Authelia forward auth integration uses nginx's `auth_request` module. Protected static sites call Authelia directly as shown below; `restricted` and `authenticated` tunnels send the same subrequest to Gatekeeper (`127.0.0.1:9294/authz/
|
|
730
|
+
The Authelia forward auth integration uses nginx's `auth_request` module. Protected static sites call Authelia directly as shown below; `restricted` and `authenticated` tunnels send the same subrequest to Gatekeeper (`127.0.0.1:9294/authz/tunnel/<fqdn>`), which validates the Authelia session and, for restricted tunnels, the grant (sections 8 and 9). Here is the flow:
|
|
678
731
|
|
|
679
732
|
```
|
|
680
733
|
1. User requests https://myapp.example.com/page
|
|
681
734
|
│
|
|
682
735
|
2. nginx sends internal subrequest to /internal/lamaste/authz
|
|
683
736
|
│ ├── proxy_pass → http://127.0.0.1:9091/api/authz/auth-request
|
|
684
|
-
│ ├── Sends X-Original-URL
|
|
737
|
+
│ ├── Sends X-Original-URL (https://myapp.example.com/page — the vhost's
|
|
738
|
+
│ │ own hostname, never the client's Host header), X-Original-Method
|
|
685
739
|
│ └── Strips request body (Content-Length: "")
|
|
686
740
|
│
|
|
687
741
|
3. Authelia checks the session cookie
|
|
@@ -783,12 +837,23 @@ Without it nothing listens on port 80 and `http://` links to the relay's hostnam
|
|
|
783
837
|
|
|
784
838
|
### Created/Removed at Runtime
|
|
785
839
|
|
|
786
|
-
| Vhost
|
|
787
|
-
|
|
|
788
|
-
| `lamalibre-lamaste-app-<name>`
|
|
840
|
+
| Vhost | Created | Removed |
|
|
841
|
+
| ------------------------------- | ----------------- | ----------------------- |
|
|
842
|
+
| `lamalibre-lamaste-app-<name>` | POST /api/tunnels | DELETE /api/tunnels/:id |
|
|
843
|
+
| `lamalibre-lamaste-site-<uuid>` | POST /api/sites | DELETE /api/sites/:id |
|
|
789
844
|
|
|
790
845
|
`PATCH /api/tunnels/:id` removes or restores the `sites-enabled` symlink when a tunnel is disabled or enabled (the file in `sites-available` is kept), and re-renders the vhost in place when `accessMode` or `maxBodySizeMb` changes. `PATCH /api/sites/:id` re-renders a site vhost when `spaMode`, `autheliaProtected` or `aliases` change.
|
|
791
|
-
|
|
846
|
+
|
|
847
|
+
### Rewritten at Startup
|
|
848
|
+
|
|
849
|
+
A vhost is written when its tunnel or site is created or changed, so a generator change does not reach existing vhosts by itself. At startup, once onboarding is complete, the panel (`lib/vhost-reconcile.js`) renders installed vhosts again and rewrites each one whose text differs through the write-with-rollback pattern, on the certificate directory the installed file names (a wildcard certificate stays a wildcard certificate). A rewrite never links a vhost that is not linked now; a disabled tunnel's vhost is unlinked:
|
|
850
|
+
|
|
851
|
+
- **Before it listens:** the auth portal (`lamalibre-lamaste-auth`) and every static site, from `sites.json` and the domain. Site changes take no lock, so this runs while none can arrive.
|
|
852
|
+
- **Once it listens**, under the tunnel lock: every agent-panel, app and plugin tunnel, from `tunnels.json` — the stored access mode (`restricted` when absent, as Gatekeeper reads it), the plugin route, and the body limit only when the tunnel has one. After listen because a rewritten agent-panel vhost asks the panel about every request.
|
|
853
|
+
|
|
854
|
+
That is how vhosts written before 4.3 start forwarding relay assertions (agent-panel and plugin tunnels) and start naming their own hostname to Gatekeeper and Authelia (see [above](#authorization-subrequests-name-their-own-hostname)). A rewrite never adds or removes authentication: a tunnel or site vhost whose `auth_request` disagrees with its state (a public vhost for a restricted tunnel, say) is logged and left alone until the tunnel or site is reconfigured — the agent-panel vhost, which gains the panel's `auth_request` beside its mTLS check, is the exception. Missing vhosts are logged (a site still waiting for its certificate has none yet); a failed rewrite restores the previous file and the others still run.
|
|
855
|
+
|
|
856
|
+
A tunnel vhost left as it was fails closed: Gatekeeper refuses its `/authz/check` subrequests, so the tunnel answers 403 until it is rewritten. A static site or auth portal vhost left as it was still names the client's `Host` to Authelia. After an upgrade, check the panel's startup log for `Left tunnel vhosts as they are`, `Left site vhosts as they are` and `Failed to rewrite … vhost`, fix what they name (a failing `nginx -t`, a missing certificate) and restart `lamalibre-lamaste-serverd`.
|
|
792
857
|
|
|
793
858
|
## Key Files
|
|
794
859
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Lamaste has three main pieces:
|
|
8
8
|
|
|
9
|
-
1. **The Installer** (`npx @lamalibre/create-lamaste@4.
|
|
9
|
+
1. **The Installer** (`npx @lamalibre/create-lamaste@4.3.0`) — a one-shot script you run once on a fresh VPS. It sets up nginx, generates security certificates, and deploys a web-based control panel. After it finishes, you never SSH into the server again.
|
|
10
10
|
|
|
11
11
|
2. **The Panel** (browser UI + backend API) — a management interface protected by a client certificate. Through it, you configure your domain, create tunnels, manage users, and monitor services.
|
|
12
12
|
|
|
@@ -176,7 +176,7 @@ The Gatekeeper is a dedicated Fastify service on `127.0.0.1:9294` that acts as a
|
|
|
176
176
|
**Architecture:**
|
|
177
177
|
|
|
178
178
|
```
|
|
179
|
-
End User → nginx :443 → auth_request → 127.0.0.1:9294/authz/
|
|
179
|
+
End User → nginx :443 → auth_request → 127.0.0.1:9294/authz/tunnel/<fqdn>
|
|
180
180
|
│
|
|
181
181
|
┌─────────┴──────────┐
|
|
182
182
|
│ Gatekeeper │
|
|
@@ -198,8 +198,8 @@ End User → nginx :443 → auth_request → 127.0.0.1:9294/authz/check
|
|
|
198
198
|
|
|
199
199
|
**Two-layer caching:**
|
|
200
200
|
|
|
201
|
-
1. **nginx `proxy_cache`** — the `lamaste_authz` cache zone (1m keys, 10m storage)
|
|
202
|
-
2. **In-memory session cache** — the Gatekeeper
|
|
201
|
+
1. **nginx `proxy_cache`** — the installer defines the `lamaste_authz` cache zone (1m keys, 10m storage) and a location-level policy (key `$server_name|$cookie_authelia_session`, `200` cached for 10 seconds, `403` never), but the generated vhosts do not include it today (see [nginx Configuration](nginx-configuration.md#nginx-cache-zone-lamaste_authz)).
|
|
202
|
+
2. **In-memory session cache** — the Gatekeeper keeps validated Authelia sessions in a `Map` keyed by an HMAC of the session cookie, so a check within the TTL does not ask Authelia again. It caches the identity, not the decision: grants are checked on every request.
|
|
203
203
|
|
|
204
204
|
**State files:** All stored in `/etc/lamalibre/lamaste/` with 0600 permissions, `lamaste:lamaste` ownership, and atomic writes (tmp, fsync, rename):
|
|
205
205
|
|
|
@@ -321,7 +321,7 @@ lamaste/
|
|
|
321
321
|
│ │ ├── files.js ← Static site file operations (upload, delete, list)
|
|
322
322
|
│ │ └── app-error.js ← Operational error class (AppError)
|
|
323
323
|
│ │
|
|
324
|
-
│ ├── lamaste-server-ui/ ← React 18 + Vite + Tailwind SPA + shared admin components (@lamalibre/lamaste-server-ui@4.
|
|
324
|
+
│ ├── lamaste-server-ui/ ← React 18 + Vite + Tailwind SPA + shared admin components (@lamalibre/lamaste-server-ui@4.3.0)
|
|
325
325
|
│ │ └── src/
|
|
326
326
|
│ │ ├── index.js ← Package exports (pages, context, utilities)
|
|
327
327
|
│ │ ├── context/
|
|
@@ -337,7 +337,7 @@ lamaste/
|
|
|
337
337
|
│ │ │ └── formatters.js ← Byte, uptime, relative time formatters
|
|
338
338
|
│ │ └── pages/ ← Admin pages (Dashboard, Tunnels, Sites, Users, Certs, Services, Plugins, Tickets, Settings)
|
|
339
339
|
│ │
|
|
340
|
-
│ ├── lamaste-agent-ui/ ← Shared React agent UI (pages, context, components) (@lamalibre/lamaste-agent-ui@4.
|
|
340
|
+
│ ├── lamaste-agent-ui/ ← Shared React agent UI (pages, context, components) (@lamalibre/lamaste-agent-ui@4.3.0)
|
|
341
341
|
│ │
|
|
342
342
|
│ ├── lamaste-tickets/ ← Agent-to-agent authorization SDK (mTLS, undici)
|
|
343
343
|
│ │ └── src/
|
|
@@ -377,7 +377,7 @@ lamaste/
|
|
|
377
377
|
│ │ │ └── CompleteStep.jsx ← Credentials + TOTP QR + next steps
|
|
378
378
|
│ │ ├── management/ ← Legacy standalone implementations (kept for reference)
|
|
379
379
|
│ │ │ ├── Certificates.jsx ← Standalone certificates page (dead code)
|
|
380
|
-
│ │ │ ├── Dashboard.jsx ← Standalone dashboard (not imported — App.jsx uses @lamalibre/lamaste-server-ui@4.
|
|
380
|
+
│ │ │ ├── Dashboard.jsx ← Standalone dashboard (not imported — App.jsx uses @lamalibre/lamaste-server-ui@4.3.0)
|
|
381
381
|
│ │ │ ├── Plugins.jsx ← Standalone plugins page (dead code)
|
|
382
382
|
│ │ │ ├── Services.jsx ← Standalone services page (dead code)
|
|
383
383
|
│ │ │ ├── Settings.jsx ← Standalone settings page (dead code)
|
|
@@ -486,7 +486,7 @@ The Panel Server runs as the `lamaste` user with minimal privileges and cannot b
|
|
|
486
486
|
The full lifecycle of a Lamaste installation:
|
|
487
487
|
|
|
488
488
|
```
|
|
489
|
-
Phase 1: Installation (npx @lamalibre/create-lamaste@4.
|
|
489
|
+
Phase 1: Installation (npx @lamalibre/create-lamaste@4.3.0)
|
|
490
490
|
└─ OS hardening, Node.js, mTLS certs, nginx, panel deploy
|
|
491
491
|
└─ Result: Panel accessible at https://<ip>:9292
|
|
492
492
|
|
|
@@ -10,9 +10,9 @@ The client has two modes. On first visit, it shows an onboarding wizard that wal
|
|
|
10
10
|
|
|
11
11
|
## Shared Panel Packages
|
|
12
12
|
|
|
13
|
-
The management-mode admin pages (Dashboard, Tunnels, Sites, Users, Certificates, Services, Plugins, Tickets, Logs, Settings) live in `@lamalibre/lamaste-server-ui@4.
|
|
13
|
+
The management-mode admin pages (Dashboard, Tunnels, Sites, Users, Certificates, Services, Plugins, Tickets, Logs, Settings) live in `@lamalibre/lamaste-server-ui@4.3.0`. This package provides both the browser SPA build and the shared admin components consumed by `lamaste-desktop` (Tauri app).
|
|
14
14
|
|
|
15
|
-
The agent-mode pages (Dashboard, Tunnels, Services, Plugins, Logs, Settings) are extracted into a separate shared package: `@lamalibre/lamaste-agent-ui@4.
|
|
15
|
+
The agent-mode pages (Dashboard, Tunnels, Services, Plugins, Logs, Settings) are extracted into a separate shared package: `@lamalibre/lamaste-agent-ui@4.3.0`. This package is consumed by `lamaste-desktop` for agent mode.
|
|
16
16
|
|
|
17
17
|
The shared package exposes an `AdminClientContext` abstraction that decouples admin UI pages from their data transport. Each host provides its own client implementation:
|
|
18
18
|
|
|
@@ -118,7 +118,7 @@ The shell renders the appropriate step component and shows a progress indicator
|
|
|
118
118
|
|
|
119
119
|
### Management Mode
|
|
120
120
|
|
|
121
|
-
React Router handles navigation within the `Layout` component (sidebar + content area). Management page components are imported from the shared `@lamalibre/lamaste-server-ui@4.
|
|
121
|
+
React Router handles navigation within the `Layout` component (sidebar + content area). Management page components are imported from the shared `@lamalibre/lamaste-server-ui@4.3.0` package and wrapped with the `AdminClientProvider` (which supplies the web-specific data client):
|
|
122
122
|
|
|
123
123
|
| Path | Component | Description |
|
|
124
124
|
| ------------------------ | ------------------- | ------------------------------------------------------------------- |
|
|
@@ -350,9 +350,9 @@ The app root wraps all content in five providers:
|
|
|
350
350
|
```
|
|
351
351
|
|
|
352
352
|
1. **QueryClientProvider** — react-query cache with global defaults (`retry: 1`, `refetchOnWindowFocus: false`)
|
|
353
|
-
2. **AdminClientProvider** — from `@lamalibre/lamaste-server-ui@4.
|
|
354
|
-
3. **ToastProvider** — from `@lamalibre/lamaste-server-ui@4.
|
|
355
|
-
4. **TwoFaProvider** — from `@lamalibre/lamaste-server-ui@4.
|
|
353
|
+
2. **AdminClientProvider** — from `@lamalibre/lamaste-server-ui@4.3.0`, supplies the host-specific data client (`webAdminClient` for browser fetch, Tauri `invoke` for desktop)
|
|
354
|
+
3. **ToastProvider** — from `@lamalibre/lamaste-server-ui@4.3.0`, notification toast system
|
|
355
|
+
4. **TwoFaProvider** — from `@lamalibre/lamaste-server-ui@4.3.0`, handles 2FA verification prompts when the panel has 2FA enabled
|
|
356
356
|
5. **BrowserRouter** — React Router for client-side navigation
|
|
357
357
|
|
|
358
358
|
## Documentation Viewer
|
|
@@ -453,7 +453,7 @@ See [nginx-configuration.md](./nginx-configuration.md) for detailed coverage.
|
|
|
453
453
|
|
|
454
454
|
### chisel-users.js — Chisel Credentials and Grants
|
|
455
455
|
|
|
456
|
-
- Thin shim over `@lamalibre/lamaste@4.
|
|
456
|
+
- Thin shim over `@lamalibre/lamaste@4.3.0/server`: per-agent credentials in `chisel-credentials.json` (0600)
|
|
457
457
|
- Renders the authfile `chisel-users` from the credentials and `tunnels.json`: each `agent-<label>` may bind exactly `^R:127\.0\.0\.1:<port>$` for its enabled tunnels
|
|
458
458
|
- Adds the `lamaste-no-grants` sentinel user (password in `chisel-sentinel`, 0600) so the authfile is never empty — Chisel disables authentication on an empty authfile
|
|
459
459
|
- Never grants a reserved port (3100, 9090, 9091, 9292, 9294) or a port claimed by two tunnels
|
|
@@ -535,7 +535,7 @@ Agent-to-agent authorization with scopes, instances, tickets, and sessions. Prov
|
|
|
535
535
|
- **Periodic cleanup** — stale/dead instances, expired tickets, dead sessions
|
|
536
536
|
- **Concurrency** — promise-chain mutex with atomic file writes (temp → fsync → rename)
|
|
537
537
|
|
|
538
|
-
Client SDK: `@lamalibre/lamaste-tickets@4.
|
|
538
|
+
Client SDK: `@lamalibre/lamaste-tickets@4.3.0` (TypeScript, undici) provides `TicketClient`, `TicketInstanceManager` (source side), and `TicketSessionManager` (target side) for plugin integration.
|
|
539
539
|
|
|
540
540
|
State files: `/etc/lamalibre/lamaste/ticket-scopes.json` (scopes, instances, assignments) and `/etc/lamalibre/lamaste/tickets.json` (tickets, sessions).
|
|
541
541
|
|
|
@@ -331,7 +331,7 @@ Read returns `[]` for missing files (ENOENT), enabling the system to start with
|
|
|
331
331
|
5. `DELETE /api/certs/agent/:label` (revocation) — the agent's app and plugin tunnels lose their `agentLabel` (unassigned), and its `agent-<label>` panel tunnel is removed
|
|
332
332
|
6. At startup (background, fail closed): an ownerless `agent-<label>` panel tunnel is bound to the agent it names when that agent is active; other ownerless tunnels (from versions before tunnel ownership) are bound to the sole active agent, or left unassigned when there are several. Then the pinned Chisel binary, the unit and the authfile are re-established — and Chisel is stopped if they cannot be
|
|
333
333
|
|
|
334
|
-
Every one of these workflows runs under the single tunnel lock (`withTunnelLock` in `@lamalibre/lamaste@4.
|
|
334
|
+
Every one of these workflows runs under the single tunnel lock (`withTunnelLock` in `@lamalibre/lamaste@4.3.0/server`), with its validation inside the lock.
|
|
335
335
|
|
|
336
336
|
## Site State (`sites.json`)
|
|
337
337
|
|
|
@@ -486,7 +486,7 @@ The PKI directory belongs to `lamaste` because the panel is the CA (it signs age
|
|
|
486
486
|
|
|
487
487
|
The Panel Server is a single-process Node.js application, but a single-threaded event loop does not serialize asynchronous workflows: two requests that each read `tunnels.json`, await a certificate or an nginx test, and then write it back would interleave, and both could pass the same uniqueness check. Serialization is therefore explicit:
|
|
488
488
|
|
|
489
|
-
**Tunnel state** is guarded by one promise-chain lock, `withTunnelLock` in `@lamalibre/lamaste@4.
|
|
489
|
+
**Tunnel state** is guarded by one promise-chain lock, `withTunnelLock` in `@lamalibre/lamaste@4.3.0/server`'s `tunnels.ts`. Every tunnel workflow — create, delete, toggle, assign, reconfigure, the combined `PATCH /api/tunnels/:id`, releasing a revoked agent's tunnels, and startup reconciliation — takes it for its whole duration, and does its validation (subdomain and port uniqueness, reserved ports, agent checks, authorization against the current entry) inside it.
|
|
490
490
|
|
|
491
491
|
**Chisel authfile renders** are serialized by a promise-chain mutex in `chisel-users.ts`, keyed by the credentials file, around every render (enroll, revoke, rotate, tunnel change). The daemon's `syncChisel()` renders after the tunnel state is written and restarts Chisel (`systemctl try-restart`) only when the render withdrew something.
|
|
492
492
|
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
│ │ │ │
|
|
59
59
|
│ │ Lamaste Desktop (Tauri v2) — dual mode │ │
|
|
60
60
|
│ │ ├─ Agent mode: service discovery + tunnel management │ │
|
|
61
|
-
│ │ │ (pages from @lamalibre/lamaste-agent-ui@4.
|
|
61
|
+
│ │ │ (pages from @lamalibre/lamaste-agent-ui@4.3.0) │ │
|
|
62
62
|
│ │ ├─ Server mode: full admin panel (via lamaste-server-ui) │ │
|
|
63
63
|
│ │ ├─ Multi-server registry (~/.lamalibre/lamaste/servers.json) │ │
|
|
64
64
|
│ │ ├─ Cloud provisioning (DigitalOcean via lamaste-cloud) │ │
|