@lamalibre/create-lamaste 4.4.0 → 4.5.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.
Files changed (150) hide show
  1. package/package.json +2 -2
  2. package/scripts/lamaste-priv +200 -16
  3. package/src/index.js +1 -0
  4. package/src/lib/backend-cookie.js +65 -0
  5. package/src/lib/ip-panel-vhost.js +208 -0
  6. package/src/lib/service-config.js +5 -3
  7. package/src/tasks/nginx.js +18 -135
  8. package/src/tasks/redeploy.js +39 -1
  9. package/vendor/docs/00-introduction/how-it-works.md +2 -2
  10. package/vendor/docs/00-introduction/quickstart.md +5 -5
  11. package/vendor/docs/00-introduction/what-is-lamaste.md +5 -5
  12. package/vendor/docs/01-concepts/authentication.md +1 -1
  13. package/vendor/docs/01-concepts/authorization.md +66 -48
  14. package/vendor/docs/01-concepts/certificates.md +3 -3
  15. package/vendor/docs/01-concepts/mtls.md +2 -2
  16. package/vendor/docs/01-concepts/security-model.md +70 -33
  17. package/vendor/docs/01-concepts/tickets.md +2 -2
  18. package/vendor/docs/01-concepts/tunneling.md +4 -4
  19. package/vendor/docs/02-guides/agent-setup.md +5 -5
  20. package/vendor/docs/02-guides/certificate-management.md +2 -2
  21. package/vendor/docs/02-guides/cloud-provisioning.md +3 -3
  22. package/vendor/docs/02-guides/desktop-app-setup.md +10 -10
  23. package/vendor/docs/02-guides/installation.md +3 -3
  24. package/vendor/docs/02-guides/managing-users.md +113 -25
  25. package/vendor/docs/02-guides/onboarding.md +2 -2
  26. package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +2 -2
  27. package/vendor/docs/03-architecture/installer-flow.md +2 -2
  28. package/vendor/docs/03-architecture/installer.md +4 -4
  29. package/vendor/docs/03-architecture/management-flow.md +3 -1
  30. package/vendor/docs/03-architecture/nginx-configuration.md +165 -18
  31. package/vendor/docs/03-architecture/overview.md +13 -17
  32. package/vendor/docs/03-architecture/panel-client.md +6 -6
  33. package/vendor/docs/03-architecture/panel-server.md +3 -2
  34. package/vendor/docs/03-architecture/state-management.md +4 -8
  35. package/vendor/docs/03-architecture/system-overview.md +1 -1
  36. package/vendor/docs/04-api-reference/agent-hosted-plugins.md +292 -23
  37. package/vendor/docs/04-api-reference/app-host.md +3 -3
  38. package/vendor/docs/04-api-reference/gatekeeper.md +60 -5
  39. package/vendor/docs/04-api-reference/lifecycle-hooks.md +15 -13
  40. package/vendor/docs/04-api-reference/onboarding.md +1 -1
  41. package/vendor/docs/04-api-reference/registry-and-oidc.md +6 -4
  42. package/vendor/docs/04-api-reference/tunnels.md +67 -29
  43. package/vendor/docs/04-api-reference/users.md +241 -45
  44. package/vendor/docs/05-operations/backup-and-restore.md +35 -26
  45. package/vendor/docs/05-operations/monitoring.md +39 -0
  46. package/vendor/docs/05-operations/uninstalling.md +2 -1
  47. package/vendor/docs/05-operations/upgrades.md +15 -6
  48. package/vendor/docs/06-reference/config-files.md +83 -189
  49. package/vendor/docs/06-reference/glossary.md +3 -3
  50. package/vendor/docs/06-reference/installer-flags.md +13 -13
  51. package/vendor/docs/06-reference/ports-and-services.md +25 -26
  52. package/vendor/docs/06-reference/troubleshooting.md +8 -8
  53. package/vendor/docs/gatekeeper-design.md +10 -8
  54. package/vendor/docs/identity-directory-design.md +48 -14
  55. package/vendor/docs/package.json +1 -1
  56. package/vendor/docs/todo/lib-cli-desktop-refactor.md +27 -27
  57. package/vendor/gatekeeper/dist/index.d.ts +2 -1
  58. package/vendor/gatekeeper/dist/index.d.ts.map +1 -1
  59. package/vendor/gatekeeper/dist/index.js +2 -0
  60. package/vendor/gatekeeper/dist/index.js.map +1 -1
  61. package/vendor/gatekeeper/dist/lib/grants.d.ts +6 -0
  62. package/vendor/gatekeeper/dist/lib/grants.d.ts.map +1 -1
  63. package/vendor/gatekeeper/dist/lib/grants.js +6 -1
  64. package/vendor/gatekeeper/dist/lib/grants.js.map +1 -1
  65. package/vendor/gatekeeper/dist/lib/groups.d.ts +5 -0
  66. package/vendor/gatekeeper/dist/lib/groups.d.ts.map +1 -1
  67. package/vendor/gatekeeper/dist/lib/groups.js +5 -1
  68. package/vendor/gatekeeper/dist/lib/groups.js.map +1 -1
  69. package/vendor/gatekeeper/dist/lib/principals.d.ts +29 -0
  70. package/vendor/gatekeeper/dist/lib/principals.d.ts.map +1 -0
  71. package/vendor/gatekeeper/dist/lib/principals.js +67 -0
  72. package/vendor/gatekeeper/dist/lib/principals.js.map +1 -0
  73. package/vendor/gatekeeper/dist/lib/tunnels.d.ts +13 -0
  74. package/vendor/gatekeeper/dist/lib/tunnels.d.ts.map +1 -0
  75. package/vendor/gatekeeper/dist/lib/tunnels.js +68 -0
  76. package/vendor/gatekeeper/dist/lib/tunnels.js.map +1 -0
  77. package/vendor/gatekeeper/dist/lib/types.d.ts +19 -0
  78. package/vendor/gatekeeper/dist/lib/types.d.ts.map +1 -1
  79. package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -1
  80. package/vendor/gatekeeper/dist/server/index.js +22 -48
  81. package/vendor/gatekeeper/dist/server/index.js.map +1 -1
  82. package/vendor/gatekeeper/dist/server/routes/authz.d.ts +15 -1
  83. package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -1
  84. package/vendor/gatekeeper/dist/server/routes/authz.js +250 -103
  85. package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -1
  86. package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts.map +1 -1
  87. package/vendor/gatekeeper/dist/server/routes/diagnostic.js +61 -4
  88. package/vendor/gatekeeper/dist/server/routes/diagnostic.js.map +1 -1
  89. package/vendor/gatekeeper/dist/server/routes/principals.d.ts +3 -0
  90. package/vendor/gatekeeper/dist/server/routes/principals.d.ts.map +1 -0
  91. package/vendor/gatekeeper/dist/server/routes/principals.js +27 -0
  92. package/vendor/gatekeeper/dist/server/routes/principals.js.map +1 -0
  93. package/vendor/gatekeeper/package.json +2 -2
  94. package/vendor/server/bin/lamaste-server.js +15 -1
  95. package/vendor/server/package.json +2 -2
  96. package/vendor/server/src/commands/plugin-identity.js +335 -0
  97. package/vendor/server/src/commands/plugins.js +99 -11
  98. package/vendor/server/src/commands/registry-credential.js +2 -1
  99. package/vendor/server/src/commands/tunnels.js +102 -2
  100. package/vendor/server/src/commands/uninstall.js +3 -1
  101. package/vendor/server/src/commands/users.js +358 -0
  102. package/vendor/server/src/plugin-approval.js +107 -5
  103. package/vendor/server/src/plugin-identity.js +382 -0
  104. package/vendor/server-ui/dist/assets/index-D-fer5ef.css +1 -0
  105. package/vendor/server-ui/dist/assets/index-DSaDPJaw.js +84 -0
  106. package/vendor/server-ui/dist/index.html +2 -2
  107. package/vendor/serverd/package.json +2 -2
  108. package/vendor/serverd/src/index.js +25 -1
  109. package/vendor/serverd/src/lib/audience.js +576 -0
  110. package/vendor/serverd/src/lib/authelia.js +78 -7
  111. package/vendor/serverd/src/lib/config.js +68 -43
  112. package/vendor/serverd/src/lib/directory.js +1113 -0
  113. package/vendor/serverd/src/lib/entitlements.js +517 -0
  114. package/vendor/serverd/src/lib/gatekeeper-client.js +215 -10
  115. package/vendor/serverd/src/lib/hook-pipelines.js +13 -0
  116. package/vendor/serverd/src/lib/identity-sync.js +905 -0
  117. package/vendor/serverd/src/lib/migrations/state/0013_identity_directory.sql +158 -0
  118. package/vendor/serverd/src/lib/migrations/state/0014_identity_ledger.sql +20 -0
  119. package/vendor/serverd/src/lib/mtls.js +6 -2
  120. package/vendor/serverd/src/lib/nginx.js +279 -48
  121. package/vendor/serverd/src/lib/plugin-hooks.js +98 -45
  122. package/vendor/serverd/src/lib/plugin-hosting.js +33 -5
  123. package/vendor/serverd/src/lib/plugin-lifecycle.js +100 -9
  124. package/vendor/serverd/src/lib/plugin-packages.js +30 -22
  125. package/vendor/serverd/src/lib/plugins.js +94 -15
  126. package/vendor/serverd/src/lib/registry-credential.js +46 -6
  127. package/vendor/serverd/src/lib/request-logging.js +19 -0
  128. package/vendor/serverd/src/lib/retained-data.js +50 -4
  129. package/vendor/serverd/src/lib/state.js +18 -0
  130. package/vendor/serverd/src/lib/tunnel-hooks.js +4 -1
  131. package/vendor/serverd/src/lib/user-access-session.js +21 -11
  132. package/vendor/serverd/src/lib/user-caller.js +67 -0
  133. package/vendor/serverd/src/lib/user-surface.js +733 -0
  134. package/vendor/serverd/src/lib/vhost-reconcile.js +136 -34
  135. package/vendor/serverd/src/middleware/user-access-session.js +9 -0
  136. package/vendor/serverd/src/routes/agent-forwarder.js +4 -2
  137. package/vendor/serverd/src/routes/invite.js +53 -12
  138. package/vendor/serverd/src/routes/management/gatekeeper-proxy.js +107 -16
  139. package/vendor/serverd/src/routes/management/identity-directory.js +79 -0
  140. package/vendor/serverd/src/routes/management/invitations.js +78 -68
  141. package/vendor/serverd/src/routes/management/plugin-identity.js +221 -0
  142. package/vendor/serverd/src/routes/management/registry-credential.js +1 -1
  143. package/vendor/serverd/src/routes/management/tunnels.js +98 -2
  144. package/vendor/serverd/src/routes/management/users.js +290 -208
  145. package/vendor/serverd/src/routes/management.js +4 -0
  146. package/vendor/serverd/src/routes/onboarding/provision.js +14 -1
  147. package/vendor/serverd/src/routes/user-access.js +17 -4
  148. package/vendor/serverd/src/routes/user-surface.js +69 -0
  149. package/vendor/server-ui/dist/assets/index-25wo_8av.css +0 -1
  150. package/vendor/server-ui/dist/assets/index-CESyyK8V.js +0 -84
@@ -1,7 +1,9 @@
1
1
  import { execa } from 'execa';
2
2
  import { writeFile, unlink } from 'node:fs/promises';
3
3
  import { existsSync } from 'node:fs';
4
+ import { ensureBackendCookieMap } from '../lib/backend-cookie.js';
4
5
  import { ensureHttpRedirect } from '../lib/http-redirect.js';
6
+ import { IP_PANEL_SITE, generateIpPanelVhost } from '../lib/ip-panel-vhost.js';
5
7
 
6
8
  /**
7
9
  * nginx IP-based mTLS configuration subtasks.
@@ -63,140 +65,10 @@ ssl_verify_client optional;
63
65
  {
64
66
  title: 'Writing IP-based panel vhost',
65
67
  task: async (_ctx, subtask) => {
66
- const vhostConfig = `# Rate limit zone for public enrollment endpoint (5 requests/minute per IP)
67
- limit_req_zone $binary_remote_addr zone=enroll:1m rate=5r/m;
68
+ const vhostPath = `/etc/nginx/sites-available/${IP_PANEL_SITE}`;
69
+ await writeFile(vhostPath, generateIpPanelVhost(pkiDir));
68
70
 
69
- map $http_upgrade $connection_upgrade {
70
- default upgrade;
71
- '' close;
72
- }
73
-
74
- server {
75
- listen 9292 ssl;
76
- server_name _;
77
-
78
- ssl_certificate ${pkiDir}/self-signed.pem;
79
- ssl_certificate_key ${pkiDir}/self-signed-key.pem;
80
-
81
- include /etc/nginx/snippets/lamalibre-lamaste-mtls.conf;
82
-
83
- # SSL settings
84
- ssl_protocols TLSv1.2 TLSv1.3;
85
- ssl_ciphers HIGH:!aNULL:!MD5;
86
- ssl_prefer_server_ciphers on;
87
-
88
- # Show certificate help page when client cert is missing or invalid
89
- error_page 495 496 /cert-help.html;
90
- location = /cert-help.html {
91
- root /opt/lamalibre/lamaste/lamaste-server-ui;
92
- internal;
93
- }
94
-
95
- # Proxy to serverd (mTLS required — reject if cert missing or invalid)
96
- location / {
97
- if ($ssl_client_verify != SUCCESS) {
98
- return 496;
99
- }
100
- proxy_pass http://127.0.0.1:3100;
101
-
102
- # Client cert headers — set from nginx TLS variables, never passed through from client
103
- proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
104
- proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
105
- proxy_set_header X-SSL-Client-Serial $ssl_client_serial;
106
-
107
- # Strip Authelia identity headers — not trusted on mTLS vhost
108
- proxy_set_header Remote-User "";
109
- proxy_set_header Remote-Groups "";
110
- proxy_set_header Remote-Name "";
111
- proxy_set_header Remote-Email "";
112
-
113
- # Standard proxy headers
114
- proxy_set_header Host $host;
115
- proxy_set_header X-Real-IP $remote_addr;
116
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
117
- proxy_set_header X-Forwarded-Proto $scheme;
118
- }
119
-
120
- # Public API paths (no mTLS verification check).
121
- # ssl_verify_client is 'optional' at server level, so TLS handshake
122
- # succeeds without a cert. These locations skip the $ssl_client_verify
123
- # check and clear cert headers so the backend sees no client identity.
124
- location /api/enroll {
125
- limit_req zone=enroll burst=5 nodelay;
126
- proxy_pass http://127.0.0.1:3100;
127
- proxy_http_version 1.1;
128
-
129
- # Clear cert headers so the backend sees no client cert
130
- proxy_set_header X-SSL-Client-Verify "";
131
- proxy_set_header X-SSL-Client-DN "";
132
- proxy_set_header X-SSL-Client-Serial "";
133
-
134
- # Strip Authelia identity headers — not trusted on public endpoint
135
- proxy_set_header Remote-User "";
136
- proxy_set_header Remote-Groups "";
137
- proxy_set_header Remote-Name "";
138
- proxy_set_header Remote-Email "";
139
-
140
- proxy_set_header Host $host;
141
- proxy_set_header X-Real-IP $remote_addr;
142
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
143
- proxy_set_header X-Forwarded-Proto $scheme;
144
- }
145
-
146
- location /api/invite {
147
- proxy_pass http://127.0.0.1:3100;
148
- proxy_http_version 1.1;
149
-
150
- proxy_set_header X-SSL-Client-Verify "";
151
- proxy_set_header X-SSL-Client-DN "";
152
- proxy_set_header X-SSL-Client-Serial "";
153
-
154
- # Strip Authelia identity headers — not trusted on public endpoint
155
- proxy_set_header Remote-User "";
156
- proxy_set_header Remote-Groups "";
157
- proxy_set_header Remote-Name "";
158
- proxy_set_header Remote-Email "";
159
-
160
- proxy_set_header Host $host;
161
- proxy_set_header X-Real-IP $remote_addr;
162
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
163
- proxy_set_header X-Forwarded-Proto $scheme;
164
- }
165
-
166
- # API paths with WebSocket upgrade support (mTLS required)
167
- location /api {
168
- if ($ssl_client_verify != SUCCESS) {
169
- return 496;
170
- }
171
- proxy_pass http://127.0.0.1:3100;
172
- proxy_http_version 1.1;
173
-
174
- # Client cert headers — set from nginx TLS variables, never passed through from client
175
- proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
176
- proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
177
- proxy_set_header X-SSL-Client-Serial $ssl_client_serial;
178
-
179
- # Strip Authelia identity headers — not trusted on mTLS vhost
180
- proxy_set_header Remote-User "";
181
- proxy_set_header Remote-Groups "";
182
- proxy_set_header Remote-Name "";
183
- proxy_set_header Remote-Email "";
184
-
185
- # Standard proxy headers
186
- proxy_set_header Host $host;
187
- proxy_set_header X-Real-IP $remote_addr;
188
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
189
- proxy_set_header X-Forwarded-Proto $scheme;
190
-
191
- # WebSocket: only upgrade when client requests it
192
- proxy_set_header Upgrade $http_upgrade;
193
- proxy_set_header Connection $connection_upgrade;
194
- }
195
- }
196
- `;
197
- await writeFile('/etc/nginx/sites-available/lamalibre-lamaste-panel-ip', vhostConfig);
198
-
199
- subtask.output = 'Vhost written to /etc/nginx/sites-available/lamalibre-lamaste-panel-ip';
71
+ subtask.output = `Vhost written to ${vhostPath}`;
200
72
  },
201
73
  rendererOptions: { persistentOutput: true },
202
74
  },
@@ -206,8 +78,8 @@ server {
206
78
  // Create symlink
207
79
  await execa('ln', [
208
80
  '-sf',
209
- '/etc/nginx/sites-available/lamalibre-lamaste-panel-ip',
210
- '/etc/nginx/sites-enabled/lamalibre-lamaste-panel-ip',
81
+ `/etc/nginx/sites-available/${IP_PANEL_SITE}`,
82
+ `/etc/nginx/sites-enabled/${IP_PANEL_SITE}`,
211
83
  ]);
212
84
 
213
85
  // Remove only the default site from sites-enabled (preserve other existing sites)
@@ -221,6 +93,17 @@ server {
221
93
  },
222
94
  rendererOptions: { persistentOutput: true },
223
95
  },
96
+ {
97
+ title: "Keeping Authelia's session cookie from tunnel backends",
98
+ task: async (_ctx, subtask) => {
99
+ // Before the panel writes its first tunnel vhost, each of which sets
100
+ // the backend's Cookie from this map's variable. nginx is
101
+ // (re)started by the last task; no reload here.
102
+ await ensureBackendCookieMap({ reload: false });
103
+ subtask.output = 'nginx map installed: backends never receive the authelia_session cookie';
104
+ },
105
+ rendererOptions: { persistentOutput: true },
106
+ },
224
107
  {
225
108
  title: 'Redirecting plain HTTP to HTTPS',
226
109
  task: async (_ctx, subtask) => {
@@ -7,7 +7,9 @@ import { fileURLToPath } from 'node:url';
7
7
  import { dirname, join } from 'node:path';
8
8
  import { generateServiceUnit, generateSudoersContent } from '../lib/service-config.js';
9
9
  import { lookupId } from '../lib/accounts.js';
10
+ import { ensureBackendCookieMap } from '../lib/backend-cookie.js';
10
11
  import { ensureHttpRedirect } from '../lib/http-redirect.js';
12
+ import { refreshIpPanelVhost } from '../lib/ip-panel-vhost.js';
11
13
  import { generateCertHelpPage } from '../lib/cert-help-page.js';
12
14
  import { ensureRootOwnedInstallDir, removeTree, writeFileNoFollow } from '../lib/ownership.js';
13
15
  import { provisionRelayServices } from '../lib/relay-services.js';
@@ -67,7 +69,13 @@ async function getLatestNpmVersion() {
67
69
  * self-update (`lamaste-priv self-update`). Updates the panel, CLI, UI, docs
68
70
  * and gatekeeper; installs the pinned chisel and Authelia releases, their
69
71
  * units, the sudoers rules and the programs they name; restarts what
70
- * changed. Does not touch OS hardening, mTLS certs or the panel's vhosts.
72
+ * changed. Does not touch OS hardening or mTLS certs. Of the panel's
73
+ * vhosts it refreshes only the IP-based one, which it installed and the
74
+ * panel cannot write (its text; the panel's 2FA owns its link) — the panel
75
+ * reconciles the others itself when it starts. Before anything else
76
+ * changes, it installs the nginx map those vhosts take the backends'
77
+ * `Cookie` from (Authelia's session cookie stripped): a relay whose nginx
78
+ * refuses it stops here, with the running panel untouched.
71
79
  *
72
80
  * Runs as root and may be started by a compromised panel, so it never
73
81
  * writes through a path the lamaste user controls (see lib/ownership.js).
@@ -96,6 +104,21 @@ export function redeployTasks(ctx, task) {
96
104
  },
97
105
  rendererOptions: { persistentOutput: true },
98
106
  },
107
+ {
108
+ title: "Keeping Authelia's session cookie from tunnel backends",
109
+ task: async (_ctx, subtask) => {
110
+ // First, while the running panel is untouched: the panel this
111
+ // redeploy starts renders every tunnel and agent-panel vhost with
112
+ // `proxy_set_header Cookie $lamaste_backend_cookie;`, which nginx
113
+ // refuses without the map. Fatal — a relay whose nginx rejects it
114
+ // keeps the version it runs.
115
+ const { changed } = await ensureBackendCookieMap();
116
+ subtask.output = changed
117
+ ? 'nginx map installed: backends never receive the authelia_session cookie'
118
+ : 'nginx map already in place';
119
+ },
120
+ rendererOptions: { persistentOutput: true },
121
+ },
99
122
  {
100
123
  title: 'Stopping panel services',
101
124
  task: async (_ctx, subtask) => {
@@ -393,6 +416,21 @@ export function redeployTasks(ctx, task) {
393
416
  },
394
417
  rendererOptions: { persistentOutput: true },
395
418
  },
419
+ {
420
+ title: 'Refreshing the IP panel vhost',
421
+ task: async (_ctx, subtask) => {
422
+ // Not fatal: the panel keeps working on the vhost it had.
423
+ try {
424
+ const { changed } = await refreshIpPanelVhost(ctx.pkiDir);
425
+ subtask.output = changed
426
+ ? 'IP panel vhost brought up to date'
427
+ : 'IP panel vhost already up to date (or not installed)';
428
+ } catch (err) {
429
+ subtask.skip(`Skipped — ${err.message}`);
430
+ }
431
+ },
432
+ rendererOptions: { persistentOutput: true },
433
+ },
396
434
  {
397
435
  title: 'Reloading systemd and restarting panel',
398
436
  task: async (_ctx, subtask) => {
@@ -55,14 +55,14 @@ SSH into a fresh Ubuntu droplet and run one command:
55
55
 
56
56
  ```bash
57
57
  apt install -y npm
58
- npx @lamalibre/create-lamaste@4.4.0
58
+ npx @lamalibre/create-lamaste@4.5.0
59
59
  ```
60
60
 
61
61
  This installs everything the relay needs: nginx, Node.js, firewall rules, certificates, and the management panel. At the end, it prints a client certificate file and password. You download the certificate, import it into your browser, and disconnect SSH. You never SSH in again.
62
62
 
63
63
  **Option B: Desktop app (cloud provisioning)**
64
64
 
65
- Install the desktop app (`npx @lamalibre/create-lamaste-desktop@4.4.0`) and use the Servers tab to create a DigitalOcean droplet. The app handles everything — droplet creation, Lamaste installation, certificate download, and connection — without you ever touching SSH. Cloud API tokens are stored in your OS credential store (macOS Keychain or Linux libsecret), never in plaintext. See the [Cloud Provisioning guide](../02-guides/cloud-provisioning.md) for a step-by-step walkthrough.
65
+ Install the desktop app (`npx @lamalibre/create-lamaste-desktop@4.5.0`) and use the Servers tab to create a DigitalOcean droplet. The app handles everything — droplet creation, Lamaste installation, certificate download, and connection — without you ever touching SSH. Cloud API tokens are stored in your OS credential store (macOS Keychain or Linux libsecret), never in plaintext. See the [Cloud Provisioning guide](../02-guides/cloud-provisioning.md) for a step-by-step walkthrough.
66
66
 
67
67
  **Phase 2: Onboarding (10 minutes, browser)**
68
68
 
@@ -59,7 +59,7 @@ Once connected:
59
59
 
60
60
  ```bash
61
61
  apt install -y npm
62
- npx @lamalibre/create-lamaste@4.4.0
62
+ npx @lamalibre/create-lamaste@4.5.0
63
63
  ```
64
64
 
65
65
  The installer displays a confirmation banner showing what it will do:
@@ -253,7 +253,7 @@ Every tunnel is carried by one **agent** — the machine that runs the app. Enro
253
253
  Install the Lamaste Desktop app, which provides a GUI with automatic service discovery and one-click tunnel creation:
254
254
 
255
255
  ```bash
256
- npx @lamalibre/create-lamaste-desktop@4.4.0
256
+ npx @lamalibre/create-lamaste-desktop@4.5.0
257
257
  ```
258
258
 
259
259
  The app auto-detects local services (Ollama, ComfyUI, PostgreSQL, Docker containers, etc.) and lets you expose them with one click. See the [Desktop App Setup](../02-guides/desktop-app-setup.md) guide for details.
@@ -263,7 +263,7 @@ The app auto-detects local services (Ollama, ComfyUI, PostgreSQL, Docker contain
263
263
  In the panel, go to **Certificates → Enrollment Token** and create a token for a label such as `laptop`. Then, on the machine that runs your app, install the agent globally and run setup:
264
264
 
265
265
  ```bash
266
- npm install -g @lamalibre/lamaste-agent@4.4.0
266
+ npm install -g @lamalibre/lamaste-agent@4.5.0
267
267
  LAMALIBRE_LAMASTE_ENROLLMENT_TOKEN=<token> \
268
268
  lamaste-agent setup --label laptop --panel-url https://203.0.113.42:9292
269
269
  ```
@@ -345,7 +345,7 @@ Your Mac DigitalOcean Droplet Internet
345
345
  The installer accepts optional flags:
346
346
 
347
347
  ```bash
348
- npx @lamalibre/create-lamaste@4.4.0 [flags]
348
+ npx @lamalibre/create-lamaste@4.5.0 [flags]
349
349
  ```
350
350
 
351
351
  | Flag | Effect |
@@ -435,7 +435,7 @@ In development mode (`NODE_ENV=development` or `NODE_ENV` unset), the panel serv
435
435
  # 2. SSH in and install
436
436
  ssh root@203.0.113.42
437
437
  apt install -y npm
438
- npx @lamalibre/create-lamaste@4.4.0
438
+ npx @lamalibre/create-lamaste@4.5.0
439
439
 
440
440
  # 3. Download cert (from your local machine)
441
441
  scp root@203.0.113.42:/etc/lamalibre/lamaste/pki/client.p12 .
@@ -36,7 +36,7 @@ Lamaste solves the problem of exposing local services to the internet securely.
36
36
  **Option A: Manual setup (SSH)**
37
37
 
38
38
  1. Create a fresh Ubuntu 24.04 droplet on DigitalOcean
39
- 2. SSH in once and run `npx @lamalibre/create-lamaste@4.4.0`
39
+ 2. SSH in once and run `npx @lamalibre/create-lamaste@4.5.0`
40
40
  3. Download the client certificate it prints
41
41
  4. Import the certificate into your browser
42
42
  5. Open `https://<droplet-ip>:9292` — you are in the admin panel
@@ -46,7 +46,7 @@ Lamaste solves the problem of exposing local services to the internet securely.
46
46
 
47
47
  **Option B: Desktop app cloud provisioning**
48
48
 
49
- 1. Install the desktop app (`npx @lamalibre/create-lamaste-desktop@4.4.0`)
49
+ 1. Install the desktop app (`npx @lamalibre/create-lamaste-desktop@4.5.0`)
50
50
  2. Go to the Servers tab and click "Create a new server"
51
51
  3. Enter your DigitalOcean API token — the app validates scopes and stores it securely in your OS credential store
52
52
  4. Pick a region (the app measures latency to each) and a domain name
@@ -93,14 +93,14 @@ All backend services bind `127.0.0.1` only — nginx is the sole public-facing s
93
93
 
94
94
  State is stored in JSON files (`/etc/lamalibre/lamaste/panel.json`, `tunnels.json`) with atomic writes (write to temp → fsync → rename). No database is needed at this scale.
95
95
 
96
- The installer (`npx @lamalibre/create-lamaste@4.4.0`) is completely non-interactive — it detects the environment, provisions everything, and prints a summary. All configuration happens through the browser-based onboarding wizard after installation.
96
+ The installer (`npx @lamalibre/create-lamaste@4.5.0`) is completely non-interactive — it detects the environment, provisions everything, and prints a summary. All configuration happens through the browser-based onboarding wizard after installation.
97
97
 
98
98
  ## Quick Reference
99
99
 
100
100
  | Item | Value |
101
101
  | -------------------- | ------------------------------- |
102
102
  | **Minimum VPS** | 512MB RAM, Ubuntu 24.04 |
103
- | **Install command** | `npx @lamalibre/create-lamaste@4.4.0` |
103
+ | **Install command** | `npx @lamalibre/create-lamaste@4.5.0` |
104
104
  | **Admin panel** | `https://<ip>:9292` (mTLS) |
105
105
  | **Auth for admin** | Client certificate (zero-login) |
106
106
  | **Auth for apps** | TOTP 2FA via Authelia |
@@ -108,5 +108,5 @@ The installer (`npx @lamalibre/create-lamaste@4.4.0`) is completely non-interact
108
108
  | **TLS certificates** | Let's Encrypt (auto-renewing) |
109
109
  | **State storage** | JSON files (no database) |
110
110
  | **RAM usage** | ~245MB total (all services) |
111
- | **npm package** | `@lamalibre/create-lamaste@4.4.0` |
111
+ | **npm package** | `@lamalibre/create-lamaste@4.5.0` |
112
112
  | **License** | Polyform Noncommercial 1.0.0 |
@@ -362,7 +362,7 @@ export async function writeUsers(usersData) {
362
362
 
363
363
  The `writeAutheliaFile` helper writes a random temp file in `/etc/authelia/`, `fsync`s it and renames it over the target. Since a rename on the same filesystem is atomic at the kernel level, Authelia never sees a half-written file. No sudo is involved: `/etc/authelia` belongs to the panel's `lamaste` user with group `lamaste-authelia` and the setgid bit, so new files belong to Authelia's group. Authelia itself runs as the unprivileged `lamaste-authelia` account, not root.
364
364
 
365
- Storing a user's TOTP secret is the one Authelia operation the panel cannot do itself — the secret lives in Authelia's database, which is that account's file. The panel runs `sudo lamaste-priv authelia-totp <username>` with the secret on stdin (never on a command line), and the root helper runs `authelia storage user totp generate` as `lamaste-authelia`.
365
+ What lives in Authelia's database — that account's file — the panel cannot touch itself. Storing a user's TOTP secret: the panel runs `sudo lamaste-priv authelia-totp <username>` with the secret on stdin (never on a command line), and the root helper runs `authelia storage user totp generate` as `lamaste-authelia`. Deleting a removed user's second factors: `lamaste-priv authelia-user purge-second-factors <username>` (`authelia storage user totp delete` and `webauthn delete --all`). Telling whether a username has an OpenID Connect identity — an opaque identifier Authelia gave a relying party, which it cannot delete, so the username is never given to a new user: `lamaste-priv authelia-user has-oidc-identifier <username>` (the identifiers exported as `lamaste-authelia` into a `/run` directory only that account and root can enter, read, overwritten and removed; it prints `yes` or `no` and nothing else). See [Users API — Reused usernames](../04-api-reference/users.md#reused-usernames).
366
366
 
367
367
  ### Secrets management
368
368
 
@@ -30,7 +30,7 @@ Lamaste has two separate group systems that serve different purposes:
30
30
  | `internal` | Internal team members |
31
31
  | `external` | External collaborators, clients, or guests |
32
32
 
33
- **Lamaste groups** are access-control groups stored in `groups.json` and managed through the Gatekeeper dashboard. These are project-oriented groups that describe what a user can access:
33
+ **Lamaste groups** are access-control groups stored in Gatekeeper's database (`gatekeeper.db`) and managed through the Gatekeeper dashboard. These are project-oriented groups that describe what a user can access:
34
34
 
35
35
  | Example group | Members |
36
36
  | -------------- | ----------------- |
@@ -56,6 +56,17 @@ When a group is the principal, the grant expands to all members of that group at
56
56
 
57
57
  Grants are not tunnel-specific. The resource model is generic: each grant has a resource type (e.g., `tunnel`, `plugin`) and a resource identifier. This means the same grant system works for tunnels, plugins, and any future resource types.
58
58
 
59
+ ### Plugin roles (entitlements)
60
+
61
+ An agent-hosted plugin that declares `identity` has roles of its own — `viewer`, `editor`, whatever its manifest names. A grant on such a plugin (`resourceType: "plugin"`, `resourceId: <route>`) whose `context.roles` lists some of them is an **entitlement**: the user — or every member of the group — holds those roles in the plugin. The panel checks the roles against the plugin's manifest, keeps each plugin's **audience** (who holds which role) and sends it to the plugin's agent half, and on every request a user makes to the plugin's user surface tells the agent half who is calling, with which roles. A plugin grant without roles never makes anyone a plugin user. Manage entitlements from the plugin's **People** panel on the Plugins page, or `lamaste-server plugins entitlements` — see [Agent-Hosted Plugins](../04-api-reference/agent-hosted-plugins.md#users-and-identity).
62
+
63
+ ### Who attests what
64
+
65
+ Gatekeeper authenticates the Authelia session; it does not tell applications who the user is. For a plain app tunnel nginx sets `Remote-User`, `Remote-Groups`, `Remote-Name` and `Remote-Email` from Gatekeeper's answer — replacing what the client sent, but **unsigned**: anything else that reaches the backend's loopback port can send them too. Signed identity exists in two places:
66
+
67
+ - **Plugin tunnels** (`type: 'plugin'`, into an agent's lamaste-agentd): Gatekeeper signs a relay assertion for the agent with the session's user ([Security Model](security-model.md#5c-the-agent-daemon-lamaste-agentd-and-relay-assertions)).
68
+ - **Plugin user surfaces** (`/api/<route>/app/*` on a host bound to an agent-hosted plugin): Gatekeeper **attests the session** — a relay assertion for the panel naming the username alone, bound to the method and the raw request path — and the **panel issues the identity** the application sees: the directory's `userId`, the username and the user's roles in the plugin, in the caller assertion it signs for the agent half ([Security Model](security-model.md#5f-users-of-agent-hosted-plugins-who-attests-what)).
69
+
59
70
  ### Access modes for tunnels
60
71
 
61
72
  Each tunnel can be configured with one of three access modes:
@@ -89,7 +100,7 @@ From the Gatekeeper dashboard in the admin panel:
89
100
 
90
101
  ### How changes take effect
91
102
 
92
- Gatekeeper watches its state files (`groups.json`, `access-grants.json`) for changes. When you create a group, add a member, or create a grant through the admin panel, the change takes effect immediately — no service restart required.
103
+ Gatekeeper reads groups and grants from its SQLite database (`gatekeeper.db`) on every check, so a group, member or grant created through the admin panel takes effect on the next request — no service restart required. The tunnel list (the panel's `tunnels.json`) and Gatekeeper's settings (`gatekeeper.json`) are watched and reloaded when they change. Changes through the panel also recompute the audiences of agent-hosted plugins at once; edits made with Gatekeeper's own CLI are picked up by the panel within a minute.
93
104
 
94
105
  ## For Developers
95
106
 
@@ -105,22 +116,30 @@ The request flow for a restricted tunnel:
105
116
  (127.0.0.1:9294/authz/tunnel/staging.example.com) with
106
117
  - X-Original-URL https://staging.example.com<request URI> and
107
118
  Host staging.example.com — the vhost's own hostname, from its configuration
108
- - X-Original-Method and the visitor's Cookie header
119
+ - X-Original-Method, X-Forwarded-For (the visitor's address, as nginx
120
+ accepted it) and the visitor's Cookie header
109
121
  3. Gatekeeper takes the tunnel named in the path (X-Original-URL and Host
110
- must agree) and validates the Authelia session cookie with Authelia for
111
- that URL:
122
+ must agree), takes the visitor's authelia_session cookie — refusing a
123
+ request with more than one — and validates it with Authelia for that
124
+ URL, sending Authelia that cookie alone:
112
125
  a. No valid session -> 401 -> nginx redirects to
113
126
  auth.example.com/?rd=https://staging.example.com<request URI>
114
- b. Valid session -> the user (cached, see Session caching)
127
+ b. Valid session -> the user (cached per session and host, see Session caching)
115
128
  4. Gatekeeper checks for a matching grant:
116
129
  a. Grant exists (user or group match) -> 200 with Remote-User, Remote-Groups,
117
130
  Remote-Name and Remote-Email
118
- b. No grant -> 403 with access request HTML page
119
- 5. On 200 nginx proxies to the tunnel backend with those identity headers
131
+ b. No grant -> 403 with access request HTML page (logged with the tunnel
132
+ and the reason, never the username)
133
+ 5. On 200 nginx proxies to the tunnel backend with those identity headers —
134
+ and the visitor's cookies without authelia_session
120
135
  ```
121
136
 
137
+ The backend never receives Authelia's session cookie: nginx sets its `Cookie` from a map that removes every `authelia_session` pair, or sends none when it cannot ([nginx Configuration](../03-architecture/nginx-configuration.md#the-backend-cookie-map)). Gatekeeper's authorization routes are rate-limited per visitor address and tunnel host (6000 a minute each): nginx is their only caller and sets `X-Forwarded-For` to the visitor's address, which Gatekeeper trusts from `127.0.0.1` only, so one visitor exhausting its bucket leaves every other visitor theirs.
138
+
122
139
  Public tunnel vhosts have no `auth_request`. For authenticated tunnels Gatekeeper answers 200 for any valid session without looking for a grant.
123
140
 
141
+ A tunnel bound as an agent-hosted plugin's user surface has a second subrequest for `/api/<route>/app*` — `/authz/user-surface/<fqdn>` — where Gatekeeper checks that the tunnel's agent hosts the plugin (it carries the plugin's internal tunnel, as `tunnels.json` records it), the session (and, on a restricted tunnel, the grant) as above, then answers with a single-use relay assertion for the panel instead of `Remote-*` headers, and a missing session with a JSON 401 instead of a login redirect ([Gatekeeper API](../04-api-reference/gatekeeper.md#user-surface-authorization-nginx-auth-request)).
142
+
124
143
  ### Which tunnel is authorized
125
144
 
126
145
  Gatekeeper authorizes the tunnel the vhost names, never one the client names. nginx selects the vhost from the request line when a request is in absolute form (`GET https://staging.example.com/ HTTP/1.1`) and from `Host` otherwise, so a client can send the request line of one tunnel with the `Host` header of another. Vhosts written before 4.3 called `/authz/check` with `X-Original-URL` and `Host` built from the client's `Host` header (`$http_host`): an Authelia user allowed on any tunnel — and every user is allowed on an `authenticated` one — reached any `restricted` tunnel, app or plugin, by naming the allowed tunnel in `Host`, without a grant, and Authelia evaluated that other URL as well.
@@ -138,7 +157,7 @@ The grant model is generic, not tunnel-specific:
138
157
  principalId: 'alice', // username or group name
139
158
  resourceType: 'tunnel', // 'tunnel', 'plugin', or custom
140
159
  resourceId: 'staging-app', // resource identifier
141
- context: {}, // optional metadata
160
+ context: {}, // optional metadata; { roles: [...] } on a plugin entitlement
142
161
  used: false, // whether grant has been consumed
143
162
  createdAt: '2026-04-01T...',
144
163
  usedAt: null // consumption timestamp, or null
@@ -158,24 +177,17 @@ Authorization checks happen on every request to a restricted tunnel. A page load
158
177
 
159
178
  Gatekeeper uses an in-memory session cache to solve this:
160
179
 
161
- **Gatekeeper in-memory session cache.** When Gatekeeper receives a request, it extracts the user identity from the Authelia session. Validated sessions are cached in memory for 30 seconds (configurable via `sessionCacheTtlMs` in `gatekeeper.json`), keyed by the session cookie value. This means repeated Gatekeeper calls for the same session do not re-validate against Authelia.
180
+ **Gatekeeper in-memory session cache.** When Gatekeeper receives a request, it extracts the user identity from the Authelia session. Validated sessions are cached in memory for 30 seconds (configurable via `sessionCacheTtlMs` in `gatekeeper.json`), keyed by the session cookie value **and the tunnel host** (an HMAC under a per-process key). Repeated Gatekeeper calls for the same session on the same host do not re-validate against Authelia; on another host they do — Authelia decides per host (a static site's or plugin tunnel's rules name users), so a session it accepted for one host proves nothing on another.
162
181
 
163
182
  > **Note:** The installer also writes an nginx `proxy_cache_path` snippet (`/etc/nginx/snippets/lamalibre-lamaste-authz-cache.conf`) for optional nginx-level caching, but the generated vhosts do not currently include `proxy_cache` directives. All authorization caching relies on the Gatekeeper's in-memory session cache.
164
183
 
165
- **Effect:** On a warm cache, repeated subrequests for the same session are resolved from the in-memory cache without re-validating against Authelia. The 30-second default TTL ensures that revoked grants and expired sessions take effect within half a minute.
166
-
167
- ### File watching
168
-
169
- Gatekeeper watches its state files using `fs.watch`:
184
+ **Effect:** On a warm cache, repeated subrequests for the same session are resolved from the in-memory cache without re-validating against Authelia. Grants and groups are read on every check, so a revoked grant takes effect at once; the session itself — a user disabled or deleted in Authelia, a session that expired — is noticed within the 30-second TTL. (A plugin's user surface does not wait for it: the panel refuses a disabled or removed user on the next request.)
170
185
 
171
- | File | Contains |
172
- | -------------------- | -------------------------------- |
173
- | `groups.json` | Lamaste groups and their members |
174
- | `access-grants.json` | All grants (user and group) |
186
+ ### State and change propagation
175
187
 
176
- When a file changes, Gatekeeper reloads its in-memory state and invalidates the session cache. This means changes made through the admin panel API (which writes to these files atomically) take effect immediately without restarting the Gatekeeper service.
188
+ Groups, grants and the access-request log live in Gatekeeper's SQLite database, `/etc/lamalibre/lamaste/gatekeeper.db` (0600 with its WAL and SHM files; numbered migrations under `src/lib/migrations/gatekeeper/`). Every check reads it, and every write through Gatekeeper's API runs under the groups and grants locks and invalidates the session cache. Gatekeeper watches two JSON files with `fs.watch` (on their parent directory, since they are replaced by rename): the panel's `tunnels.json` — tunnels, access modes, plugin targets and user-surface bindings — and its own settings, `gatekeeper.json`.
177
189
 
178
- State files use the same atomic write pattern as the rest of Lamaste (temp file with `O_EXCL` + mode `0600` -> `fsync` -> rename).
190
+ The JSON files of earlier versions (`groups.json`, `access-grants.json`, `access-request-log.json`) are no longer read; the installer still creates empty `groups.json` and `access-grants.json`. The legacy `user-plugin-access.json` grants are not migrated into Gatekeeper (`migrateFromLegacy` exists but nothing calls it): they stay where the desktop's user-access flow reads them, and never make anyone a plugin user.
179
191
 
180
192
  ### Access request page generation
181
193
 
@@ -190,37 +202,43 @@ The HTML is self-contained (inline CSS, no external dependencies) so it renders
190
202
 
191
203
  ### State files
192
204
 
193
- | File | Purpose | Format |
194
- | ------------------------------------------- | -------------------------- | ------ |
195
- | `/etc/lamalibre/lamaste/groups.json` | Lamaste groups and members | JSON |
196
- | `/etc/lamalibre/lamaste/access-grants.json` | All grants | JSON |
205
+ | File | Purpose | Format |
206
+ | ----------------------------------------------------------- | ----------------------------------------------------------- | ------ |
207
+ | `/etc/lamalibre/lamaste/gatekeeper.db` | Groups and members, grants, the access-request log | SQLite |
208
+ | `/etc/lamalibre/lamaste/gatekeeper.json` | Settings | JSON |
209
+ | `/etc/lamalibre/lamaste/gatekeeper-secret.json` | The secret the panel calls Gatekeeper's management API with | JSON |
210
+ | `/etc/lamalibre/lamaste/tunnels.json` (read only) | The panel's tunnels and their user-surface bindings | JSON |
211
+ | `/etc/lamalibre/lamaste/relay-signing-key.json` (read only) | The relay signing key Gatekeeper signs assertions with | JSON |
197
212
 
198
213
  ### Source files
199
214
 
200
- | File | Purpose |
201
- | -------------------------------------------------------------------- | ----------------------------------------------------- |
202
- | `packages/lamaste-gatekeeper/src/server/index.ts` | Fastify server setup, file watching |
203
- | `packages/lamaste-gatekeeper/src/server/routes/authz.ts` | Authorization check endpoint (nginx auth_request) |
204
- | `packages/lamaste-gatekeeper/src/server/routes/grants.ts` | Grant CRUD API |
205
- | `packages/lamaste-gatekeeper/src/server/routes/groups.ts` | Group CRUD API |
206
- | `packages/lamaste-gatekeeper/src/server/routes/access-request.ts` | Access request submission and listing |
207
- | `packages/lamaste-gatekeeper/src/lib/authz.ts` | Authorization logic (grant matching, group expansion) |
208
- | `packages/lamaste-gatekeeper/src/lib/grants.ts` | Grant storage and atomic writes |
209
- | `packages/lamaste-gatekeeper/src/lib/groups.ts` | Group storage and atomic writes |
210
- | `packages/lamaste-gatekeeper/src/lib/templates.ts` | Access request HTML page generation |
211
- | `packages/lamaste-serverd/src/routes/management/gatekeeper-proxy.js` | Admin panel proxy to Gatekeeper API |
212
- | `packages/lamaste-serverd/src/lib/nginx.js` | Vhost generation with Gatekeeper auth_request block |
215
+ | File | Purpose |
216
+ | ------------------------------------------------------------------ | ---------------------------------------------------------- |
217
+ | `packages/sdks/gatekeeper/src/server/index.ts` | Fastify server setup, file watching |
218
+ | `packages/sdks/gatekeeper/src/server/routes/authz.ts` | Tunnel and user-surface authorization (nginx auth_request) |
219
+ | `packages/sdks/gatekeeper/src/server/routes/grants.ts` | Grant CRUD API |
220
+ | `packages/sdks/gatekeeper/src/server/routes/groups.ts` | Group CRUD API |
221
+ | `packages/sdks/gatekeeper/src/server/routes/principals.ts` | Removing a user principal everywhere |
222
+ | `packages/sdks/gatekeeper/src/server/routes/diagnostic.ts` | Access check, settings, cache bust, access-request log |
223
+ | `packages/sdks/gatekeeper/src/server/routes/access-request.ts` | Access request submission and listing |
224
+ | `packages/sdks/gatekeeper/src/lib/authz.ts` | Authorization logic (grant matching, group expansion) |
225
+ | `packages/sdks/gatekeeper/src/lib/state-db.ts` | `gatekeeper.db` and its migrations |
226
+ | `packages/sdks/gatekeeper/src/lib/grants.ts`, `groups.ts` | Grant and group storage |
227
+ | `packages/sdks/gatekeeper/src/lib/tunnels.ts` | What Gatekeeper reads of `tunnels.json` |
228
+ | `packages/sdks/gatekeeper/src/lib/templates.ts` | Access request HTML page generation |
229
+ | `packages/server/daemon/src/routes/management/gatekeeper-proxy.js` | Admin panel proxy to Gatekeeper API (role grants checked) |
230
+ | `packages/server/daemon/src/lib/nginx.js` | Vhost generation with Gatekeeper auth_request blocks |
213
231
 
214
232
  ## Quick Reference
215
233
 
216
234
  ### Service
217
235
 
218
- | Property | Value |
219
- | ----------- | ---------------------------------------------------------------- |
220
- | Listen | `127.0.0.1:9294` |
221
- | Runtime | Fastify (TypeScript) |
222
- | State files | `groups.json`, `access-grants.json` in `/etc/lamalibre/lamaste/` |
223
- | RAM usage | Minimal (in-process) |
236
+ | Property | Value |
237
+ | --------- | ------------------------------------------------------------------------ |
238
+ | Listen | `127.0.0.1:9294` |
239
+ | Runtime | Fastify (TypeScript) |
240
+ | State | `gatekeeper.db` (SQLite), `gatekeeper.json` in `/etc/lamalibre/lamaste/` |
241
+ | RAM usage | Minimal (in-process) |
224
242
 
225
243
  ### Access modes
226
244
 
@@ -232,16 +250,16 @@ The HTML is self-contained (inline CSS, no external dependencies) so it renders
232
250
 
233
251
  ### Cache TTLs
234
252
 
235
- | Layer | TTL | Key |
236
- | ---------------------------- | --- | -------------------- |
237
- | Gatekeeper in-memory session | 30s | Session cookie value |
253
+ | Layer | TTL | Key |
254
+ | ---------------------------- | --- | ------------------------------------ |
255
+ | Gatekeeper in-memory session | 30s | Session cookie value and tunnel host |
238
256
 
239
257
  ### Group comparison
240
258
 
241
259
  | Property | Authelia groups | Lamaste groups |
242
260
  | ----------- | -------------------------------- | -------------------------------------- |
243
261
  | Purpose | Identity classification | Access control |
244
- | Storage | `users.yml` | `groups.json` |
262
+ | Storage | `users.yml` | `gatekeeper.db` |
245
263
  | Managed by | Authelia (via panel Users page) | Gatekeeper (via panel Gatekeeper page) |
246
264
  | Examples | `admins`, `internal`, `external` | `developers`, `design-team` |
247
265
  | Granularity | Broad (who you are) | Fine (what you access) |
@@ -29,7 +29,7 @@ For the IP-based admin panel (`https://<ip>:9292`), there is no domain to verify
29
29
 
30
30
  ### When certificates are issued
31
31
 
32
- - **Self-signed + mTLS certs** — created during `npx @lamalibre/create-lamaste@4.4.0` installation
32
+ - **Self-signed + mTLS certs** — created during `npx @lamalibre/create-lamaste@4.5.0` installation
33
33
  - **Core Let's Encrypt certs** — issued during onboarding for `panel`, `auth`, and `tunnel` subdomains
34
34
  - **App Let's Encrypt certs** — issued when you create a new tunnel or a managed static site
35
35
  - **Custom-domain site certs** — issued when a custom-domain site's DNS is verified: one certificate lineage, named after the site's domain, covering the domain and every redirect alias (re-issued when aliases change)
@@ -82,7 +82,7 @@ The most critical scenario is the mTLS CA expiring, but it has a 10-year validit
82
82
 
83
83
  ### Let's Encrypt implementation
84
84
 
85
- Lamaste uses certbot with the nginx plugin for certificate issuance. The implementation lives in `@lamalibre/lamaste@4.4.0/server` (`packages/core/lib/src/server/certbot.ts`), wrapped by `packages/server/daemon/src/lib/certbot.js`.
85
+ Lamaste uses certbot with the nginx plugin for certificate issuance. The implementation lives in `@lamalibre/lamaste@4.5.0/server` (`packages/core/lib/src/server/certbot.ts`), wrapped by `packages/server/daemon/src/lib/certbot.js`.
86
86
 
87
87
  The panel runs as the unprivileged `lamaste` user and never runs `certbot` or `openssl` on Let's Encrypt files directly. It calls two root-owned wrapper scripts through sudo:
88
88
 
@@ -193,7 +193,7 @@ This three-step check prevents unnecessary certificate issuance and respects rat
193
193
 
194
194
  #### Custom-domain site certificate issuance
195
195
 
196
- A custom-domain static site may have redirect aliases (e.g. `www.myblog.net`). `issueSiteCert(fqdn, aliases, email)` in `@lamalibre/lamaste@4.4.0/server` keeps them on **one** lineage named after the primary domain:
196
+ A custom-domain static site may have redirect aliases (e.g. `www.myblog.net`). `issueSiteCert(fqdn, aliases, email)` in `@lamalibre/lamaste@4.5.0/server` keeps them on **one** lineage named after the primary domain:
197
197
 
198
198
  ```bash
199
199
  sudo /usr/local/sbin/lamaste-certbot issue admin@example.com myblog.net myblog.net www.myblog.net
@@ -25,7 +25,7 @@ You encounter mTLS during two moments:
25
25
 
26
26
  ### Importing the certificate
27
27
 
28
- After running `npx @lamalibre/create-lamaste@4.4.0`, the installer prints something like:
28
+ After running `npx @lamalibre/create-lamaste@4.5.0`, the installer prints something like:
29
29
 
30
30
  ```
31
31
  ╔══════════════════════════════════════════════════════════════╗
@@ -101,7 +101,7 @@ When connecting a machine to Lamaste using `lamaste-agent`, you should **not** u
101
101
  2. Click "Generate" and enter a label (e.g., `macbook-pro`)
102
102
  3. Save the displayed password — it cannot be retrieved later
103
103
  4. Download the `.p12` file
104
- 5. Share the `.p12` file and password with the agent machine through a secure channel, then install the agent there (`npm install -g @lamalibre/lamaste-agent@4.4.0`) and run `lamaste-agent setup` (an enrollment token avoids moving the key at all — see [Agent Setup](../02-guides/agent-setup.md))
104
+ 5. Share the `.p12` file and password with the agent machine through a secure channel, then install the agent there (`npm install -g @lamalibre/lamaste-agent@4.5.0`) and run `lamaste-agent setup` (an enrollment token avoids moving the key at all — see [Agent Setup](../02-guides/agent-setup.md))
105
105
 
106
106
  Agent certificates have capability-based access. By default, a new agent can only read tunnel configuration. Admins can grant additional capabilities per-agent from the panel UI:
107
107