@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.
- package/package.json +2 -2
- package/scripts/lamaste-priv +200 -16
- package/src/index.js +1 -0
- package/src/lib/backend-cookie.js +65 -0
- package/src/lib/ip-panel-vhost.js +208 -0
- package/src/lib/service-config.js +5 -3
- package/src/tasks/nginx.js +18 -135
- package/src/tasks/redeploy.js +39 -1
- package/vendor/docs/00-introduction/how-it-works.md +2 -2
- package/vendor/docs/00-introduction/quickstart.md +5 -5
- package/vendor/docs/00-introduction/what-is-lamaste.md +5 -5
- package/vendor/docs/01-concepts/authentication.md +1 -1
- package/vendor/docs/01-concepts/authorization.md +66 -48
- package/vendor/docs/01-concepts/certificates.md +3 -3
- package/vendor/docs/01-concepts/mtls.md +2 -2
- package/vendor/docs/01-concepts/security-model.md +70 -33
- package/vendor/docs/01-concepts/tickets.md +2 -2
- package/vendor/docs/01-concepts/tunneling.md +4 -4
- package/vendor/docs/02-guides/agent-setup.md +5 -5
- package/vendor/docs/02-guides/certificate-management.md +2 -2
- package/vendor/docs/02-guides/cloud-provisioning.md +3 -3
- package/vendor/docs/02-guides/desktop-app-setup.md +10 -10
- package/vendor/docs/02-guides/installation.md +3 -3
- package/vendor/docs/02-guides/managing-users.md +113 -25
- package/vendor/docs/02-guides/onboarding.md +2 -2
- package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +2 -2
- package/vendor/docs/03-architecture/installer-flow.md +2 -2
- package/vendor/docs/03-architecture/installer.md +4 -4
- package/vendor/docs/03-architecture/management-flow.md +3 -1
- package/vendor/docs/03-architecture/nginx-configuration.md +165 -18
- package/vendor/docs/03-architecture/overview.md +13 -17
- package/vendor/docs/03-architecture/panel-client.md +6 -6
- package/vendor/docs/03-architecture/panel-server.md +3 -2
- package/vendor/docs/03-architecture/state-management.md +4 -8
- package/vendor/docs/03-architecture/system-overview.md +1 -1
- package/vendor/docs/04-api-reference/agent-hosted-plugins.md +292 -23
- package/vendor/docs/04-api-reference/app-host.md +3 -3
- package/vendor/docs/04-api-reference/gatekeeper.md +60 -5
- package/vendor/docs/04-api-reference/lifecycle-hooks.md +15 -13
- package/vendor/docs/04-api-reference/onboarding.md +1 -1
- package/vendor/docs/04-api-reference/registry-and-oidc.md +6 -4
- package/vendor/docs/04-api-reference/tunnels.md +67 -29
- package/vendor/docs/04-api-reference/users.md +241 -45
- package/vendor/docs/05-operations/backup-and-restore.md +35 -26
- package/vendor/docs/05-operations/monitoring.md +39 -0
- package/vendor/docs/05-operations/uninstalling.md +2 -1
- package/vendor/docs/05-operations/upgrades.md +15 -6
- package/vendor/docs/06-reference/config-files.md +83 -189
- package/vendor/docs/06-reference/glossary.md +3 -3
- package/vendor/docs/06-reference/installer-flags.md +13 -13
- package/vendor/docs/06-reference/ports-and-services.md +25 -26
- package/vendor/docs/06-reference/troubleshooting.md +8 -8
- package/vendor/docs/gatekeeper-design.md +10 -8
- package/vendor/docs/identity-directory-design.md +48 -14
- package/vendor/docs/package.json +1 -1
- package/vendor/docs/todo/lib-cli-desktop-refactor.md +27 -27
- package/vendor/gatekeeper/dist/index.d.ts +2 -1
- package/vendor/gatekeeper/dist/index.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/index.js +2 -0
- package/vendor/gatekeeper/dist/index.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/grants.d.ts +6 -0
- package/vendor/gatekeeper/dist/lib/grants.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/lib/grants.js +6 -1
- package/vendor/gatekeeper/dist/lib/grants.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/groups.d.ts +5 -0
- package/vendor/gatekeeper/dist/lib/groups.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/lib/groups.js +5 -1
- package/vendor/gatekeeper/dist/lib/groups.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/principals.d.ts +29 -0
- package/vendor/gatekeeper/dist/lib/principals.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/principals.js +67 -0
- package/vendor/gatekeeper/dist/lib/principals.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/tunnels.d.ts +13 -0
- package/vendor/gatekeeper/dist/lib/tunnels.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/tunnels.js +68 -0
- package/vendor/gatekeeper/dist/lib/tunnels.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/types.d.ts +19 -0
- package/vendor/gatekeeper/dist/lib/types.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/index.js +22 -48
- package/vendor/gatekeeper/dist/server/index.js.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts +15 -1
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/authz.js +250 -103
- package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js +61 -4
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/principals.d.ts +3 -0
- package/vendor/gatekeeper/dist/server/routes/principals.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/principals.js +27 -0
- package/vendor/gatekeeper/dist/server/routes/principals.js.map +1 -0
- package/vendor/gatekeeper/package.json +2 -2
- package/vendor/server/bin/lamaste-server.js +15 -1
- package/vendor/server/package.json +2 -2
- package/vendor/server/src/commands/plugin-identity.js +335 -0
- package/vendor/server/src/commands/plugins.js +99 -11
- package/vendor/server/src/commands/registry-credential.js +2 -1
- package/vendor/server/src/commands/tunnels.js +102 -2
- package/vendor/server/src/commands/uninstall.js +3 -1
- package/vendor/server/src/commands/users.js +358 -0
- package/vendor/server/src/plugin-approval.js +107 -5
- package/vendor/server/src/plugin-identity.js +382 -0
- package/vendor/server-ui/dist/assets/index-D-fer5ef.css +1 -0
- package/vendor/server-ui/dist/assets/index-DSaDPJaw.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 +25 -1
- package/vendor/serverd/src/lib/audience.js +576 -0
- package/vendor/serverd/src/lib/authelia.js +78 -7
- package/vendor/serverd/src/lib/config.js +68 -43
- package/vendor/serverd/src/lib/directory.js +1113 -0
- package/vendor/serverd/src/lib/entitlements.js +517 -0
- package/vendor/serverd/src/lib/gatekeeper-client.js +215 -10
- package/vendor/serverd/src/lib/hook-pipelines.js +13 -0
- package/vendor/serverd/src/lib/identity-sync.js +905 -0
- package/vendor/serverd/src/lib/migrations/state/0013_identity_directory.sql +158 -0
- package/vendor/serverd/src/lib/migrations/state/0014_identity_ledger.sql +20 -0
- package/vendor/serverd/src/lib/mtls.js +6 -2
- package/vendor/serverd/src/lib/nginx.js +279 -48
- package/vendor/serverd/src/lib/plugin-hooks.js +98 -45
- package/vendor/serverd/src/lib/plugin-hosting.js +33 -5
- package/vendor/serverd/src/lib/plugin-lifecycle.js +100 -9
- package/vendor/serverd/src/lib/plugin-packages.js +30 -22
- package/vendor/serverd/src/lib/plugins.js +94 -15
- package/vendor/serverd/src/lib/registry-credential.js +46 -6
- package/vendor/serverd/src/lib/request-logging.js +19 -0
- package/vendor/serverd/src/lib/retained-data.js +50 -4
- package/vendor/serverd/src/lib/state.js +18 -0
- package/vendor/serverd/src/lib/tunnel-hooks.js +4 -1
- package/vendor/serverd/src/lib/user-access-session.js +21 -11
- package/vendor/serverd/src/lib/user-caller.js +67 -0
- package/vendor/serverd/src/lib/user-surface.js +733 -0
- package/vendor/serverd/src/lib/vhost-reconcile.js +136 -34
- package/vendor/serverd/src/middleware/user-access-session.js +9 -0
- package/vendor/serverd/src/routes/agent-forwarder.js +4 -2
- package/vendor/serverd/src/routes/invite.js +53 -12
- package/vendor/serverd/src/routes/management/gatekeeper-proxy.js +107 -16
- package/vendor/serverd/src/routes/management/identity-directory.js +79 -0
- package/vendor/serverd/src/routes/management/invitations.js +78 -68
- package/vendor/serverd/src/routes/management/plugin-identity.js +221 -0
- package/vendor/serverd/src/routes/management/registry-credential.js +1 -1
- package/vendor/serverd/src/routes/management/tunnels.js +98 -2
- package/vendor/serverd/src/routes/management/users.js +290 -208
- package/vendor/serverd/src/routes/management.js +4 -0
- package/vendor/serverd/src/routes/onboarding/provision.js +14 -1
- package/vendor/serverd/src/routes/user-access.js +17 -4
- package/vendor/serverd/src/routes/user-surface.js +69 -0
- package/vendor/server-ui/dist/assets/index-25wo_8av.css +0 -1
- package/vendor/server-ui/dist/assets/index-CESyyK8V.js +0 -84
package/src/tasks/nginx.js
CHANGED
|
@@ -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
|
|
67
|
-
|
|
68
|
+
const vhostPath = `/etc/nginx/sites-available/${IP_PANEL_SITE}`;
|
|
69
|
+
await writeFile(vhostPath, generateIpPanelVhost(pkiDir));
|
|
68
70
|
|
|
69
|
-
|
|
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
|
-
|
|
210
|
-
|
|
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) => {
|
package/src/tasks/redeploy.js
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 `
|
|
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
|
|
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
|
|
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)
|
|
111
|
-
that
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
194
|
-
|
|
|
195
|
-
| `/etc/lamalibre/lamaste/
|
|
196
|
-
| `/etc/lamalibre/lamaste/
|
|
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
|
|
201
|
-
|
|
|
202
|
-
| `packages/
|
|
203
|
-
| `packages/
|
|
204
|
-
| `packages/
|
|
205
|
-
| `packages/
|
|
206
|
-
| `packages/
|
|
207
|
-
| `packages/
|
|
208
|
-
| `packages/
|
|
209
|
-
| `packages/
|
|
210
|
-
| `packages/
|
|
211
|
-
| `packages/
|
|
212
|
-
| `packages/
|
|
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
|
|
219
|
-
|
|
|
220
|
-
| Listen
|
|
221
|
-
| Runtime
|
|
222
|
-
| State
|
|
223
|
-
| RAM usage
|
|
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` | `
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|