@lamalibre/create-lamaste 4.0.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/LICENSE.md +118 -0
- package/README.md +114 -0
- package/bin/create-lamaste.js +15 -0
- package/package.json +54 -0
- package/scripts/bundle-vendor.js +110 -0
- package/scripts/lamaste-cert-info +62 -0
- package/scripts/lamaste-certbot +96 -0
- package/scripts/lamaste-priv +693 -0
- package/src/index.js +646 -0
- package/src/lib/accounts.js +86 -0
- package/src/lib/binaries.js +137 -0
- package/src/lib/cert-help-page.js +160 -0
- package/src/lib/env.js +132 -0
- package/src/lib/http-redirect.js +38 -0
- package/src/lib/ownership.js +231 -0
- package/src/lib/relay-services.js +103 -0
- package/src/lib/secrets.js +19 -0
- package/src/lib/service-config.js +311 -0
- package/src/lib/summary.js +131 -0
- package/src/lib/wrappers.js +63 -0
- package/src/tasks/gatekeeper.js +233 -0
- package/src/tasks/harden.js +293 -0
- package/src/tasks/mtls.js +193 -0
- package/src/tasks/nginx.js +265 -0
- package/src/tasks/node.js +110 -0
- package/src/tasks/panel.js +408 -0
- package/src/tasks/redeploy.js +438 -0
- package/vendor/docs/00-introduction/how-it-works.md +447 -0
- package/vendor/docs/00-introduction/quickstart.md +472 -0
- package/vendor/docs/00-introduction/what-is-lamaste.md +112 -0
- package/vendor/docs/01-concepts/authentication.md +464 -0
- package/vendor/docs/01-concepts/authorization.md +242 -0
- package/vendor/docs/01-concepts/certificates.md +452 -0
- package/vendor/docs/01-concepts/dns-and-domains.md +343 -0
- package/vendor/docs/01-concepts/mtls.md +490 -0
- package/vendor/docs/01-concepts/nginx-reverse-proxy.md +540 -0
- package/vendor/docs/01-concepts/security-model.md +600 -0
- package/vendor/docs/01-concepts/tickets.md +414 -0
- package/vendor/docs/01-concepts/tunneling.md +367 -0
- package/vendor/docs/02-guides/agent-setup.md +194 -0
- package/vendor/docs/02-guides/certificate-management.md +341 -0
- package/vendor/docs/02-guides/cloud-provisioning.md +406 -0
- package/vendor/docs/02-guides/desktop-app-setup.md +226 -0
- package/vendor/docs/02-guides/disaster-recovery.md +464 -0
- package/vendor/docs/02-guides/first-tunnel.md +230 -0
- package/vendor/docs/02-guides/installation.md +393 -0
- package/vendor/docs/02-guides/managing-users.md +255 -0
- package/vendor/docs/02-guides/onboarding.md +244 -0
- package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +375 -0
- package/vendor/docs/02-guides/static-sites.md +364 -0
- package/vendor/docs/03-architecture/e2e-three-vm-sequences.md +457 -0
- package/vendor/docs/03-architecture/installer-flow.md +151 -0
- package/vendor/docs/03-architecture/installer.md +445 -0
- package/vendor/docs/03-architecture/management-flow.md +288 -0
- package/vendor/docs/03-architecture/nginx-configuration.md +830 -0
- package/vendor/docs/03-architecture/onboarding-flow.md +164 -0
- package/vendor/docs/03-architecture/overview.md +541 -0
- package/vendor/docs/03-architecture/panel-client.md +416 -0
- package/vendor/docs/03-architecture/panel-server.md +695 -0
- package/vendor/docs/03-architecture/state-management.md +529 -0
- package/vendor/docs/03-architecture/system-overview.md +167 -0
- package/vendor/docs/04-api-reference/certificates.md +1141 -0
- package/vendor/docs/04-api-reference/gatekeeper.md +592 -0
- package/vendor/docs/04-api-reference/onboarding.md +473 -0
- package/vendor/docs/04-api-reference/overview.md +404 -0
- package/vendor/docs/04-api-reference/services.md +267 -0
- package/vendor/docs/04-api-reference/settings.md +343 -0
- package/vendor/docs/04-api-reference/sites.md +716 -0
- package/vendor/docs/04-api-reference/system.md +268 -0
- package/vendor/docs/04-api-reference/tickets.md +618 -0
- package/vendor/docs/04-api-reference/tunnels.md +417 -0
- package/vendor/docs/04-api-reference/users.md +333 -0
- package/vendor/docs/05-operations/backup-and-restore.md +342 -0
- package/vendor/docs/05-operations/monitoring.md +285 -0
- package/vendor/docs/05-operations/uninstalling.md +344 -0
- package/vendor/docs/05-operations/upgrades.md +270 -0
- package/vendor/docs/06-reference/config-files.md +1080 -0
- package/vendor/docs/06-reference/glossary.md +528 -0
- package/vendor/docs/06-reference/installer-flags.md +260 -0
- package/vendor/docs/06-reference/ports-and-services.md +244 -0
- package/vendor/docs/06-reference/troubleshooting.md +665 -0
- package/vendor/docs/_index.json +233 -0
- package/vendor/docs/gatekeeper-design.md +953 -0
- package/vendor/docs/package.json +15 -0
- package/vendor/docs/todo/lib-cli-desktop-refactor.md +641 -0
- package/vendor/gatekeeper/dist/boot.d.ts +1 -0
- package/vendor/gatekeeper/dist/boot.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/boot.js +17 -0
- package/vendor/gatekeeper/dist/boot.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/access.d.ts +2 -0
- package/vendor/gatekeeper/dist/cli/commands/access.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/access.js +33 -0
- package/vendor/gatekeeper/dist/cli/commands/access.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.d.ts +2 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.js +110 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.d.ts +2 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.js +98 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/index.d.ts +3 -0
- package/vendor/gatekeeper/dist/cli/index.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/index.js +45 -0
- package/vendor/gatekeeper/dist/cli/index.js.map +1 -0
- package/vendor/gatekeeper/dist/index.d.ts +9 -0
- package/vendor/gatekeeper/dist/index.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/index.js +13 -0
- package/vendor/gatekeeper/dist/index.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/authz.d.ts +16 -0
- package/vendor/gatekeeper/dist/lib/authz.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/authz.js +42 -0
- package/vendor/gatekeeper/dist/lib/authz.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.d.ts +15 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.js +19 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/constants.d.ts +82 -0
- package/vendor/gatekeeper/dist/lib/constants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/constants.js +79 -0
- package/vendor/gatekeeper/dist/lib/constants.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/grants.d.ts +32 -0
- package/vendor/gatekeeper/dist/lib/grants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/grants.js +314 -0
- package/vendor/gatekeeper/dist/lib/grants.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/groups.d.ts +10 -0
- package/vendor/gatekeeper/dist/lib/groups.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/groups.js +247 -0
- package/vendor/gatekeeper/dist/lib/groups.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/migration.d.ts +17 -0
- package/vendor/gatekeeper/dist/lib/migration.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/migration.js +79 -0
- package/vendor/gatekeeper/dist/lib/migration.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0001_groups.sql +7 -0
- package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0002_access_grants.sql +14 -0
- package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0003_access_request_log.sql +9 -0
- package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0001_groups.sql +7 -0
- package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0002_access_grants.sql +14 -0
- package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0003_access_request_log.sql +9 -0
- package/vendor/gatekeeper/dist/lib/state-db.d.ts +16 -0
- package/vendor/gatekeeper/dist/lib/state-db.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/state-db.js +154 -0
- package/vendor/gatekeeper/dist/lib/state-db.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/templates.d.ts +21 -0
- package/vendor/gatekeeper/dist/lib/templates.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/templates.js +151 -0
- package/vendor/gatekeeper/dist/lib/templates.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/types.d.ts +178 -0
- package/vendor/gatekeeper/dist/lib/types.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/types.js +2 -0
- package/vendor/gatekeeper/dist/lib/types.js.map +1 -0
- package/vendor/gatekeeper/dist/server/index.d.ts +15 -0
- package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/index.js +251 -0
- package/vendor/gatekeeper/dist/server/index.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.d.ts +7 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.js +17 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts +3 -0
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/authz.js +279 -0
- package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts +13 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js +249 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/grants.d.ts +3 -0
- package/vendor/gatekeeper/dist/server/routes/grants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/grants.js +112 -0
- package/vendor/gatekeeper/dist/server/routes/grants.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/groups.d.ts +3 -0
- package/vendor/gatekeeper/dist/server/routes/groups.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/groups.js +139 -0
- package/vendor/gatekeeper/dist/server/routes/groups.js.map +1 -0
- package/vendor/gatekeeper/package.json +49 -0
- package/vendor/server/bin/lamaste-server.js +204 -0
- package/vendor/server/package.json +31 -0
- package/vendor/server/src/commands/certs.js +224 -0
- package/vendor/server/src/commands/chisel.js +132 -0
- package/vendor/server/src/commands/logs.js +49 -0
- package/vendor/server/src/commands/plugins.js +321 -0
- package/vendor/server/src/commands/reset-admin.js +478 -0
- package/vendor/server/src/commands/restart.js +94 -0
- package/vendor/server/src/commands/sites.js +257 -0
- package/vendor/server/src/commands/status.js +158 -0
- package/vendor/server/src/commands/tunnels.js +365 -0
- package/vendor/server/src/commands/uninstall.js +326 -0
- package/vendor/server/src/config.js +38 -0
- package/vendor/server/src/exec.js +26 -0
- package/vendor/server/src/logger.js +39 -0
- package/vendor/server/src/ndjson.js +56 -0
- package/vendor/server/src/panel-api.js +85 -0
- package/vendor/server/src/state.js +45 -0
- package/vendor/server-ui/dist/assets/index-B0nWuUyL.js +257 -0
- package/vendor/server-ui/dist/assets/index-BqWWFlTU.css +1 -0
- package/vendor/server-ui/dist/index.html +13 -0
- package/vendor/serverd/package.json +44 -0
- package/vendor/serverd/src/boot.js +13 -0
- package/vendor/serverd/src/index.js +191 -0
- package/vendor/serverd/src/lib/app-error.js +14 -0
- package/vendor/serverd/src/lib/authelia.js +85 -0
- package/vendor/serverd/src/lib/certbot.js +74 -0
- package/vendor/serverd/src/lib/chisel-args.js +6 -0
- package/vendor/serverd/src/lib/chisel-reconcile.js +232 -0
- package/vendor/serverd/src/lib/chisel-runtime.js +46 -0
- package/vendor/serverd/src/lib/chisel-users.js +109 -0
- package/vendor/serverd/src/lib/chisel.js +81 -0
- package/vendor/serverd/src/lib/config.js +190 -0
- package/vendor/serverd/src/lib/constants.js +8 -0
- package/vendor/serverd/src/lib/csr-signing.js +331 -0
- package/vendor/serverd/src/lib/enrollment.js +302 -0
- package/vendor/serverd/src/lib/files.js +19 -0
- package/vendor/serverd/src/lib/gatekeeper-client.js +135 -0
- package/vendor/serverd/src/lib/invite-page.js +281 -0
- package/vendor/serverd/src/lib/ip-utils.js +79 -0
- package/vendor/serverd/src/lib/migrations/state/0001_enrollment_tokens.sql +17 -0
- package/vendor/serverd/src/lib/migrations/state/0002_tickets.sql +70 -0
- package/vendor/serverd/src/lib/migrations/state/0003_plugins.sql +15 -0
- package/vendor/serverd/src/lib/migrations/state/0004_agents.sql +19 -0
- package/vendor/serverd/src/lib/mtls.js +795 -0
- package/vendor/serverd/src/lib/nginx.js +1090 -0
- package/vendor/serverd/src/lib/pki-paths.js +41 -0
- package/vendor/serverd/src/lib/pki-sign.js +103 -0
- package/vendor/serverd/src/lib/plugins.js +538 -0
- package/vendor/serverd/src/lib/push-install.js +390 -0
- package/vendor/serverd/src/lib/revocation.js +112 -0
- package/vendor/serverd/src/lib/services.js +131 -0
- package/vendor/serverd/src/lib/session.js +151 -0
- package/vendor/serverd/src/lib/state-db.js +130 -0
- package/vendor/serverd/src/lib/state.js +140 -0
- package/vendor/serverd/src/lib/storage.js +63 -0
- package/vendor/serverd/src/lib/system-stats.js +58 -0
- package/vendor/serverd/src/lib/tickets.js +1556 -0
- package/vendor/serverd/src/lib/totp.js +76 -0
- package/vendor/serverd/src/lib/tunnel-deps.js +41 -0
- package/vendor/serverd/src/lib/user-access-session.js +142 -0
- package/vendor/serverd/src/lib/user-access.js +52 -0
- package/vendor/serverd/src/middleware/errors.js +63 -0
- package/vendor/serverd/src/middleware/mtls.js +120 -0
- package/vendor/serverd/src/middleware/onboarding-guard.js +30 -0
- package/vendor/serverd/src/middleware/role-guard.js +58 -0
- package/vendor/serverd/src/middleware/twofa-session.js +65 -0
- package/vendor/serverd/src/middleware/user-access-session.js +52 -0
- package/vendor/serverd/src/routes/enrollment.js +120 -0
- package/vendor/serverd/src/routes/health.js +22 -0
- package/vendor/serverd/src/routes/invite.js +163 -0
- package/vendor/serverd/src/routes/management/agents.js +182 -0
- package/vendor/serverd/src/routes/management/certs.js +911 -0
- package/vendor/serverd/src/routes/management/gatekeeper-proxy.js +160 -0
- package/vendor/serverd/src/routes/management/identity.js +107 -0
- package/vendor/serverd/src/routes/management/invitations.js +166 -0
- package/vendor/serverd/src/routes/management/logs.js +218 -0
- package/vendor/serverd/src/routes/management/plugins.js +560 -0
- package/vendor/serverd/src/routes/management/services.js +63 -0
- package/vendor/serverd/src/routes/management/settings.js +325 -0
- package/vendor/serverd/src/routes/management/sites.js +522 -0
- package/vendor/serverd/src/routes/management/storage.js +192 -0
- package/vendor/serverd/src/routes/management/system.js +201 -0
- package/vendor/serverd/src/routes/management/tickets.js +457 -0
- package/vendor/serverd/src/routes/management/tunnels.js +709 -0
- package/vendor/serverd/src/routes/management/users.js +377 -0
- package/vendor/serverd/src/routes/management.js +36 -0
- package/vendor/serverd/src/routes/onboarding/dns.js +101 -0
- package/vendor/serverd/src/routes/onboarding/domain.js +42 -0
- package/vendor/serverd/src/routes/onboarding/index.js +18 -0
- package/vendor/serverd/src/routes/onboarding/provision.js +327 -0
- package/vendor/serverd/src/routes/onboarding/status.js +12 -0
- package/vendor/serverd/src/routes/plugin-router.js +79 -0
- package/vendor/serverd/src/routes/user-access.js +389 -0
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
# Lamaste Gatekeeper — Design Document
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`@lamalibre/lamaste-gatekeeper@4.0.0` is a standalone authorization package for Lamaste. It manages **groups** and **grants**, enforces tunnel access control via a lightweight Fastify service, and serves user-friendly "request access" pages when access is denied.
|
|
6
|
+
|
|
7
|
+
It follows the same architectural pattern as `@lamalibre/lamaste-tickets@4.0.0` — a solid library at the core, with a service, CLI, and desktop as consumers.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Motivation
|
|
12
|
+
|
|
13
|
+
### Problem
|
|
14
|
+
|
|
15
|
+
- App tunnels currently allow **all Authelia-authenticated users** — no per-user or per-group restrictions
|
|
16
|
+
- Plugin tunnel access uses a plugin-specific grant model (`user-plugin-access.json`) that doesn't generalize
|
|
17
|
+
- There is no group management — groups are free-form strings in Authelia's `users.yml` with no CRUD, no metadata, and every change requires an Authelia restart
|
|
18
|
+
- Access denial shows a generic Authelia 403 page with no context or actionable next steps
|
|
19
|
+
|
|
20
|
+
### Solution
|
|
21
|
+
|
|
22
|
+
A two-layer authentication/authorization architecture:
|
|
23
|
+
|
|
24
|
+
1. **Authelia** — authentication layer (who are you?)
|
|
25
|
+
2. **Gatekeeper** — authorization layer (can you access this resource?)
|
|
26
|
+
|
|
27
|
+
When access is denied, users see a helpful page with pre-filled message templates to request access from their administrator.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Dual Group System
|
|
32
|
+
|
|
33
|
+
Two separate, orthogonal group systems:
|
|
34
|
+
|
|
35
|
+
### Authelia Groups (Identity Tier)
|
|
36
|
+
|
|
37
|
+
Fixed role tiers stored in `users.yml`. Determines _what kind of user you are_.
|
|
38
|
+
|
|
39
|
+
| Group | Purpose |
|
|
40
|
+
| ---------- | ------------------------------------------------------------------------- |
|
|
41
|
+
| `admins` | Full panel access (mTLS certificate holders) |
|
|
42
|
+
| `internal` | Power users — can install standalone plugins, serve resources via tunnels |
|
|
43
|
+
| `external` | Basic users — can access tunnels they've been granted access to |
|
|
44
|
+
|
|
45
|
+
- Managed in `users.yml`, changes require Authelia restart
|
|
46
|
+
- User creation UI uses hardcoded checkboxes: admin (disables others), internal (disables external), external
|
|
47
|
+
- Rarely change after initial user setup
|
|
48
|
+
|
|
49
|
+
### Lamaste Groups (Access Control Tier)
|
|
50
|
+
|
|
51
|
+
Admin-created, arbitrary groups for fine-grained resource authorization. Determines _what resources you can access_.
|
|
52
|
+
|
|
53
|
+
| Example Group | Purpose |
|
|
54
|
+
| ------------- | -------------------------------------------------- |
|
|
55
|
+
| `developers` | Access to dev/staging tunnels |
|
|
56
|
+
| `design-team` | Access to design tool tunnels |
|
|
57
|
+
| `client-acme` | External client access to specific project tunnels |
|
|
58
|
+
|
|
59
|
+
- Full CRUD via gatekeeper library/CLI/API
|
|
60
|
+
- Stored in `/etc/lamalibre/lamaste/groups.json`
|
|
61
|
+
- No Authelia involvement — membership changes don't require any restart
|
|
62
|
+
- Used in grants: "group `client-acme` can access tunnel `staging-app`"
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Package Structure
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
packages/lamaste-gatekeeper/
|
|
70
|
+
src/
|
|
71
|
+
lib/
|
|
72
|
+
groups.js # Group CRUD (atomic JSON writes, promise-chain mutex)
|
|
73
|
+
grants.js # Generic grant CRUD (atomic JSON writes, promise-chain mutex)
|
|
74
|
+
authz.js # Authorization check logic (resolves groups, evaluates grants)
|
|
75
|
+
templates.js # Access request message templates (email, Teams, Slack, WhatsApp)
|
|
76
|
+
constants.js # Principal types, resource types, access modes, hard caps
|
|
77
|
+
migration.js # Migrate user-plugin-access.json → access-grants.json
|
|
78
|
+
server/
|
|
79
|
+
index.js # Fastify service entry point (127.0.0.1:9294)
|
|
80
|
+
routes/
|
|
81
|
+
authz.js # GET /authz/check — nginx auth_request target
|
|
82
|
+
access-request.js # GET /access-request — denial page with message templates
|
|
83
|
+
groups.js # REST API for group management
|
|
84
|
+
grants.js # REST API for grant management
|
|
85
|
+
cli/
|
|
86
|
+
index.js # CLI entry point
|
|
87
|
+
commands/
|
|
88
|
+
groups.js # group create|list|show|delete|rename|add-member|remove-member
|
|
89
|
+
grants.js # grant create|list|show|revoke
|
|
90
|
+
access.js # access check <username> <resource> — diagnostic tool
|
|
91
|
+
index.js # Library exports (groups, grants, authz, templates, constants)
|
|
92
|
+
package.json
|
|
93
|
+
tsconfig.json
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Generic Grant Model
|
|
99
|
+
|
|
100
|
+
The grant system is resource-agnostic. It doesn't know what a "tunnel" or "plugin" is — it stores generic principal-to-resource bindings.
|
|
101
|
+
|
|
102
|
+
### Schema
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
{
|
|
106
|
+
grantId: string, // UUID (crypto.randomUUID())
|
|
107
|
+
|
|
108
|
+
// WHO — the principal
|
|
109
|
+
principalType: 'user' | 'group',
|
|
110
|
+
principalId: string, // username or Lamaste group name
|
|
111
|
+
|
|
112
|
+
// WHAT — the resource
|
|
113
|
+
resourceType: string, // 'tunnel', 'plugin', or any future type
|
|
114
|
+
resourceId: string, // tunnel UUID, plugin package name, etc.
|
|
115
|
+
|
|
116
|
+
// TYPE-SPECIFIC CONTEXT
|
|
117
|
+
context: object, // { target: 'agent:myagent' } for plugins, {} for tunnels
|
|
118
|
+
|
|
119
|
+
// LIFECYCLE
|
|
120
|
+
used: boolean, // tracks single-use grants (local plugin enrollment)
|
|
121
|
+
createdAt: string, // ISO 8601
|
|
122
|
+
usedAt: string | null // ISO 8601, null if unused
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Why Generic?
|
|
127
|
+
|
|
128
|
+
- Adding a new resource type (e.g., `storage`, `site`) requires zero schema changes
|
|
129
|
+
- The grant system doesn't need to understand resource semantics
|
|
130
|
+
- Consumers validate resource-specific context at their layer
|
|
131
|
+
- Filter/query works uniformly across all resource types
|
|
132
|
+
|
|
133
|
+
### Examples
|
|
134
|
+
|
|
135
|
+
**App tunnel grant (user):**
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"grantId": "a1b2c3...",
|
|
140
|
+
"principalType": "user",
|
|
141
|
+
"principalId": "alice",
|
|
142
|
+
"resourceType": "tunnel",
|
|
143
|
+
"resourceId": "uuid-of-gitlab-tunnel",
|
|
144
|
+
"context": {},
|
|
145
|
+
"used": true,
|
|
146
|
+
"createdAt": "2026-04-04T10:00:00Z",
|
|
147
|
+
"usedAt": "2026-04-04T10:00:00Z"
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**App tunnel grant (group):**
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"grantId": "d4e5f6...",
|
|
156
|
+
"principalType": "group",
|
|
157
|
+
"principalId": "developers",
|
|
158
|
+
"resourceType": "tunnel",
|
|
159
|
+
"resourceId": "uuid-of-gitlab-tunnel",
|
|
160
|
+
"context": {},
|
|
161
|
+
"used": true,
|
|
162
|
+
"createdAt": "2026-04-04T10:00:00Z",
|
|
163
|
+
"usedAt": "2026-04-04T10:00:00Z"
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Plugin grant (agent-side):**
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"grantId": "g7h8i9...",
|
|
172
|
+
"principalType": "user",
|
|
173
|
+
"principalId": "bob",
|
|
174
|
+
"resourceType": "plugin",
|
|
175
|
+
"resourceId": "@lamalibre/herd-server",
|
|
176
|
+
"context": { "target": "agent:myagent" },
|
|
177
|
+
"used": true,
|
|
178
|
+
"createdAt": "2026-04-04T10:00:00Z",
|
|
179
|
+
"usedAt": "2026-04-04T10:00:00Z"
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Plugin grant (local/desktop):**
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"grantId": "j0k1l2...",
|
|
188
|
+
"principalType": "user",
|
|
189
|
+
"principalId": "carol",
|
|
190
|
+
"resourceType": "plugin",
|
|
191
|
+
"resourceId": "@lamalibre/sync-server",
|
|
192
|
+
"context": { "target": "local" },
|
|
193
|
+
"used": false,
|
|
194
|
+
"createdAt": "2026-04-04T10:00:00Z",
|
|
195
|
+
"usedAt": null
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Library API
|
|
202
|
+
|
|
203
|
+
### Groups
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
import { createGroup, listGroups, getGroup, updateGroup, deleteGroup,
|
|
207
|
+
addMembers, removeMembers, getGroupsForUser } from '@lamalibre/lamaste-gatekeeper@4.0.0';
|
|
208
|
+
|
|
209
|
+
createGroup(name, { description?, createdBy? })
|
|
210
|
+
// → { name, description, members: [], createdAt, createdBy }
|
|
211
|
+
// Validates: lowercase alphanumeric + hyphens, not 'admins'/'internal'/'external'
|
|
212
|
+
// Rejects duplicates
|
|
213
|
+
|
|
214
|
+
listGroups()
|
|
215
|
+
// → [{ name, description, members, createdAt, createdBy }]
|
|
216
|
+
|
|
217
|
+
getGroup(name)
|
|
218
|
+
// → group or null
|
|
219
|
+
|
|
220
|
+
updateGroup(name, { name?: newName, description? })
|
|
221
|
+
// Cannot rename to reserved names
|
|
222
|
+
// Renames update all grants referencing this group
|
|
223
|
+
|
|
224
|
+
deleteGroup(name)
|
|
225
|
+
// Cannot delete reserved Authelia groups
|
|
226
|
+
// Auto-revokes all grants referencing this group
|
|
227
|
+
// Returns { deletedGrants: number } for audit
|
|
228
|
+
|
|
229
|
+
addMembers(groupName, usernames)
|
|
230
|
+
// Idempotent — already-present members silently skipped
|
|
231
|
+
// Validates usernames exist in Authelia users.yml
|
|
232
|
+
|
|
233
|
+
removeMembers(groupName, usernames)
|
|
234
|
+
// Idempotent — already-absent members silently skipped
|
|
235
|
+
|
|
236
|
+
getGroupsForUser(username)
|
|
237
|
+
// → [groupName, ...] — scans all groups for membership
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Grants
|
|
241
|
+
|
|
242
|
+
```js
|
|
243
|
+
import { createGrant, listGrants, getGrant, revokeGrant } from '@lamalibre/lamaste-gatekeeper@4.0.0';
|
|
244
|
+
|
|
245
|
+
createGrant({ principalType, principalId, resourceType, resourceId, context? })
|
|
246
|
+
// → grant object
|
|
247
|
+
// Validates principal exists (user in Authelia or group in groups.json)
|
|
248
|
+
// Validates no duplicate grant (same principal + resource + context)
|
|
249
|
+
// Tunnel/agent-side plugin grants: auto-consumed (used: true)
|
|
250
|
+
// Local plugin grants: created unused (used: false, consumed on enrollment)
|
|
251
|
+
|
|
252
|
+
listGrants(filter?)
|
|
253
|
+
// filter: { principalType?, principalId?, resourceType?, resourceId?, used? }
|
|
254
|
+
// → [grant, ...]
|
|
255
|
+
// No filter = all grants
|
|
256
|
+
|
|
257
|
+
getGrant(grantId)
|
|
258
|
+
// → grant or null
|
|
259
|
+
|
|
260
|
+
revokeGrant(grantId)
|
|
261
|
+
// Removes the grant
|
|
262
|
+
// Local plugin grants: only revocable if used === false
|
|
263
|
+
// All others: always revocable
|
|
264
|
+
// Returns revoked grant for audit
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Authorization
|
|
268
|
+
|
|
269
|
+
```js
|
|
270
|
+
import { checkAccess } from '@lamalibre/lamaste-gatekeeper@4.0.0';
|
|
271
|
+
|
|
272
|
+
checkAccess(username, resourceType, resourceId);
|
|
273
|
+
// 1. Find direct user grants: principalType='user', principalId=username
|
|
274
|
+
// 2. Find user's Lamaste groups via getGroupsForUser(username)
|
|
275
|
+
// 3. Find group grants: principalType='group', principalId in user's groups
|
|
276
|
+
// 4. Any match → { allowed: true }
|
|
277
|
+
// 5. No match → { allowed: false, resource: { type, id }, templates: [...] }
|
|
278
|
+
|
|
279
|
+
// Templates include pre-filled messages with username and resource name
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Templates
|
|
283
|
+
|
|
284
|
+
```js
|
|
285
|
+
import { getAccessRequestTemplates } from '@lamalibre/lamaste-gatekeeper@4.0.0';
|
|
286
|
+
|
|
287
|
+
getAccessRequestTemplates(username, resourceName, { adminContact?, domain? })
|
|
288
|
+
// → {
|
|
289
|
+
// email: { subject, body },
|
|
290
|
+
// slack: string,
|
|
291
|
+
// teams: string,
|
|
292
|
+
// whatsapp: string,
|
|
293
|
+
// generic: string
|
|
294
|
+
// }
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Performance: Caching Architecture
|
|
300
|
+
|
|
301
|
+
### Current State (Authelia Only)
|
|
302
|
+
|
|
303
|
+
Today, **every single request** to a tunneled service triggers a localhost HTTP roundtrip from nginx to Authelia (`127.0.0.1:9091/api/authz/forward-auth`). For a page load with 1000 requests (HTML, CSS, JS, images, fonts, API calls), that's **1000 Authelia calls**. No caching exists.
|
|
304
|
+
|
|
305
|
+
Authelia handles this because it's a lightweight Go binary with in-memory session lookup (~1-2ms per call), but it's still 1000 localhost HTTP roundtrips per page load.
|
|
306
|
+
|
|
307
|
+
### With Gatekeeper: Two-Layer Caching
|
|
308
|
+
|
|
309
|
+
Adding gatekeeper as the auth_request target doesn't increase overhead — it **reduces** it by introducing caching that benefits all tunnel types.
|
|
310
|
+
|
|
311
|
+
#### Layer 1 — nginx `proxy_cache` (eliminates most gatekeeper calls)
|
|
312
|
+
|
|
313
|
+
```nginx
|
|
314
|
+
proxy_cache_path /var/cache/nginx/authz levels=1:2
|
|
315
|
+
keys_zone=authz_cache:1m max_size=10m inactive=5m;
|
|
316
|
+
|
|
317
|
+
location /internal/lamaste/authz {
|
|
318
|
+
internal;
|
|
319
|
+
proxy_pass http://127.0.0.1:9294/authz/check;
|
|
320
|
+
proxy_cache authz_cache;
|
|
321
|
+
proxy_cache_key "$cookie_authelia_session$http_host";
|
|
322
|
+
proxy_cache_valid 200 30s; # cache successful auth for 30s
|
|
323
|
+
proxy_cache_valid 403 10s; # cache denial for 10s (shorter — revocations take effect faster)
|
|
324
|
+
# Never cache 401 — user needs fresh login redirect
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Cache key is `session_cookie + hostname`:
|
|
329
|
+
|
|
330
|
+
- Same user, same tunnel = cached (correct)
|
|
331
|
+
- Different users = separate cache entries (correct)
|
|
332
|
+
- Different tunnels = separate cache entries (correct)
|
|
333
|
+
|
|
334
|
+
1000 requests on page load → **1 hits gatekeeper, 999 served from nginx cache**.
|
|
335
|
+
|
|
336
|
+
#### Layer 2 — Gatekeeper in-memory session cache (eliminates most Authelia calls)
|
|
337
|
+
|
|
338
|
+
```js
|
|
339
|
+
// In-memory LRU cache, keyed by Authelia session cookie hash
|
|
340
|
+
const sessionCache = new Map(); // cookie_hash → { username, groups, expiresAt }
|
|
341
|
+
|
|
342
|
+
async function validateAuthelia(cookie) {
|
|
343
|
+
const key = hash(cookie);
|
|
344
|
+
const cached = sessionCache.get(key);
|
|
345
|
+
if (cached && cached.expiresAt > Date.now()) return cached;
|
|
346
|
+
|
|
347
|
+
// Only hits Authelia on cache miss
|
|
348
|
+
const result = await forwardToAuthelia(cookie);
|
|
349
|
+
sessionCache.set(key, { ...result, expiresAt: Date.now() + 30_000 });
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Even without nginx cache (e.g., after a deploy), gatekeeper handles 1000 requests with:
|
|
355
|
+
|
|
356
|
+
- **1 Authelia HTTP call** (first request, then cached 30s)
|
|
357
|
+
- **1000 in-memory grant lookups** (microseconds each — small array scan)
|
|
358
|
+
|
|
359
|
+
#### Performance Comparison
|
|
360
|
+
|
|
361
|
+
| Scenario | Authelia calls | Gatekeeper calls | Added latency per request |
|
|
362
|
+
| --------------------------------------- | -------------- | ---------------- | ------------------------------------- |
|
|
363
|
+
| **Current (no gatekeeper)** | **1000** | N/A | ~1-2ms each |
|
|
364
|
+
| nginx cache warm | 0 | 0 | ~0.1ms (nginx cache lookup) |
|
|
365
|
+
| nginx cache cold, gatekeeper cache warm | 0 | 1 | ~1ms (localhost HTTP + memory lookup) |
|
|
366
|
+
| Both cold (first request of session) | 1 | 1 | ~5-10ms (one-time) |
|
|
367
|
+
|
|
368
|
+
**Net result:** Gatekeeper with caching turns 1000 Authelia calls into 1 (or 0 with warm cache). This is a performance improvement over the current setup, not a regression.
|
|
369
|
+
|
|
370
|
+
#### Universal Caching Benefit
|
|
371
|
+
|
|
372
|
+
The nginx `proxy_cache` layer applies to **all access modes**, not just restricted tunnels:
|
|
373
|
+
|
|
374
|
+
- `authenticated` tunnels: gatekeeper validates Authelia cookie, returns 200. Cached 30s.
|
|
375
|
+
- `restricted` tunnels: gatekeeper validates cookie + checks grants. Cached 30s.
|
|
376
|
+
|
|
377
|
+
Both modes benefit equally from caching. Current Authelia-only tunnels that migrate to `authenticated` mode get free caching they didn't have before.
|
|
378
|
+
|
|
379
|
+
#### Grant Revocation Latency
|
|
380
|
+
|
|
381
|
+
The tradeoff: a revoked grant takes up to 30 seconds to take effect (nginx cache TTL). This is acceptable for the use case (admin revokes access, user loses access within 30s). For immediate revocation, gatekeeper exposes `POST /api/cache/bust` — lamalibre-lamaste-serverd calls this after revoking a grant, which increments an internal version counter causing all cached responses to miss.
|
|
382
|
+
|
|
383
|
+
#### Memory Footprint
|
|
384
|
+
|
|
385
|
+
- Gatekeeper session cache: ~200 bytes per entry, 1000 concurrent users = ~200KB
|
|
386
|
+
- Grants/groups in memory: loaded via file watch, typically < 100KB
|
|
387
|
+
- nginx cache zone: 1MB key zone + 10MB data store
|
|
388
|
+
- **Total overhead: < 12MB** — trivial on the 512MB droplet (~245MB used by existing stack)
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Gatekeeper Service
|
|
393
|
+
|
|
394
|
+
### Overview
|
|
395
|
+
|
|
396
|
+
Lightweight Fastify server on `127.0.0.1:9294`. Three responsibilities:
|
|
397
|
+
|
|
398
|
+
1. **nginx auth_request target** — validate access for all tunnel types (authentication via Authelia + authorization via grants)
|
|
399
|
+
2. **Access request page** — user-friendly denial page with message templates
|
|
400
|
+
3. **Management REST API** — group and grant CRUD for CLI and desktop consumers
|
|
401
|
+
|
|
402
|
+
### nginx Integration
|
|
403
|
+
|
|
404
|
+
For all non-mTLS tunnel types, nginx uses gatekeeper as the auth_request target. Gatekeeper handles both authentication (by forwarding to Authelia) and authorization (by checking grants):
|
|
405
|
+
|
|
406
|
+
```nginx
|
|
407
|
+
# Restricted tunnel vhost — e.g., gitlab.example.com
|
|
408
|
+
server {
|
|
409
|
+
listen 443 ssl;
|
|
410
|
+
server_name gitlab.example.com;
|
|
411
|
+
|
|
412
|
+
# TLS (Let's Encrypt)
|
|
413
|
+
ssl_certificate /etc/letsencrypt/live/gitlab.example.com/fullchain.pem;
|
|
414
|
+
ssl_certificate_key /etc/letsencrypt/live/gitlab.example.com/privkey.pem;
|
|
415
|
+
|
|
416
|
+
# Gatekeeper auth (handles both authentication via Authelia + authorization via grants)
|
|
417
|
+
location /internal/lamaste/authz {
|
|
418
|
+
internal;
|
|
419
|
+
proxy_pass http://127.0.0.1:9294/authz/check;
|
|
420
|
+
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
|
|
421
|
+
proxy_set_header X-Forwarded-For $remote_addr;
|
|
422
|
+
proxy_set_header Host $http_host;
|
|
423
|
+
proxy_pass_request_body off;
|
|
424
|
+
proxy_set_header Content-Length "";
|
|
425
|
+
# Forward cookies so gatekeeper can validate Authelia session
|
|
426
|
+
proxy_set_header Cookie $http_cookie;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
location / {
|
|
430
|
+
auth_request /internal/lamaste/authz;
|
|
431
|
+
auth_request_set $remote_user $upstream_http_remote_user;
|
|
432
|
+
auth_request_set $remote_groups $upstream_http_remote_groups;
|
|
433
|
+
auth_request_set $remote_name $upstream_http_remote_name;
|
|
434
|
+
auth_request_set $remote_email $upstream_http_remote_email;
|
|
435
|
+
|
|
436
|
+
# Authentication failure → Authelia login
|
|
437
|
+
error_page 401 =302 https://auth.example.com/?rd=$scheme://$http_host$request_uri;
|
|
438
|
+
|
|
439
|
+
# Authorization failure → access request page
|
|
440
|
+
error_page 403 =302 https://auth.example.com/access-request?tunnel=$http_host;
|
|
441
|
+
|
|
442
|
+
# Set headers for backend
|
|
443
|
+
proxy_set_header Remote-User $remote_user;
|
|
444
|
+
proxy_set_header Remote-Groups $remote_groups;
|
|
445
|
+
proxy_set_header Remote-Name $remote_name;
|
|
446
|
+
proxy_set_header Remote-Email $remote_email;
|
|
447
|
+
|
|
448
|
+
# Proxy to tunnel backend
|
|
449
|
+
proxy_pass http://127.0.0.1:PORT;
|
|
450
|
+
proxy_http_version 1.1;
|
|
451
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
452
|
+
proxy_set_header Connection "upgrade";
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Authz Check Endpoint
|
|
458
|
+
|
|
459
|
+
`GET /authz/check`
|
|
460
|
+
|
|
461
|
+
1. Read `Cookie` and `X-Original-URL` headers from nginx
|
|
462
|
+
2. Forward cookie to Authelia's verification endpoint (`http://127.0.0.1:9091/api/authz/forward-auth`)
|
|
463
|
+
3. If Authelia returns 401 → return 401 (not authenticated)
|
|
464
|
+
4. If Authelia returns 200 → extract `Remote-User`, `Remote-Groups`, `Remote-Name`, `Remote-Email` from Authelia response headers
|
|
465
|
+
5. Parse hostname from `X-Original-URL` → look up tunnel
|
|
466
|
+
6. Check tunnel's `accessMode`:
|
|
467
|
+
- `public` → return 200 (should not reach here, but safe fallback)
|
|
468
|
+
- `authenticated` → return 200 (Authelia passed = sufficient)
|
|
469
|
+
- `restricted` → call `checkAccess(username, 'tunnel', tunnelId)`
|
|
470
|
+
- Allowed → return 200 + forward Authelia headers
|
|
471
|
+
- Denied → return 403
|
|
472
|
+
7. Response headers on 200: `Remote-User`, `Remote-Groups`, `Remote-Name`, `Remote-Email` (passed through from Authelia)
|
|
473
|
+
|
|
474
|
+
### Access Request Page
|
|
475
|
+
|
|
476
|
+
`GET /access-request?tunnel=gitlab.example.com`
|
|
477
|
+
|
|
478
|
+
Serves an HTML page (server-rendered, no SPA dependency):
|
|
479
|
+
|
|
480
|
+
- User's Authelia cookie is valid (they passed authentication)
|
|
481
|
+
- Gatekeeper calls Authelia verify to get username
|
|
482
|
+
- Page shows:
|
|
483
|
+
- "You don't have access to **gitlab.example.com**"
|
|
484
|
+
- "Contact your administrator to request access"
|
|
485
|
+
- Admin contact info (from `/etc/lamalibre/lamaste/panel.json` or gatekeeper config)
|
|
486
|
+
- Pre-filled message templates with copy buttons:
|
|
487
|
+
|
|
488
|
+
```
|
|
489
|
+
Email:
|
|
490
|
+
Subject: Access request for gitlab.example.com
|
|
491
|
+
Body: Hi, I'd like to request access to gitlab.example.com.
|
|
492
|
+
My username is alice. Thank you.
|
|
493
|
+
|
|
494
|
+
Slack / Teams:
|
|
495
|
+
Hi, could I get access to gitlab.example.com? My username is alice.
|
|
496
|
+
|
|
497
|
+
WhatsApp:
|
|
498
|
+
Hi, I need access to gitlab.example.com (username: alice). Thanks!
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
- Styled with Lamaste's dark terminal aesthetic (inline CSS, no external deps)
|
|
502
|
+
- Copy button per template (vanilla JS, no framework)
|
|
503
|
+
|
|
504
|
+
### Service REST API
|
|
505
|
+
|
|
506
|
+
For CLI and desktop consumption (binds 127.0.0.1 only):
|
|
507
|
+
|
|
508
|
+
**Groups:**
|
|
509
|
+
|
|
510
|
+
- `POST /api/groups` — create group
|
|
511
|
+
- `GET /api/groups` — list groups
|
|
512
|
+
- `GET /api/groups/:name` — get group with members
|
|
513
|
+
- `PATCH /api/groups/:name` — update group (rename, description)
|
|
514
|
+
- `DELETE /api/groups/:name` — delete group + auto-revoke grants
|
|
515
|
+
- `POST /api/groups/:name/members` — add members
|
|
516
|
+
- `DELETE /api/groups/:name/members/:username` — remove member
|
|
517
|
+
|
|
518
|
+
**Grants:**
|
|
519
|
+
|
|
520
|
+
- `POST /api/grants` — create grant
|
|
521
|
+
- `GET /api/grants` — list grants (query params for filtering)
|
|
522
|
+
- `GET /api/grants/:grantId` — get grant
|
|
523
|
+
- `DELETE /api/grants/:grantId` — revoke grant
|
|
524
|
+
|
|
525
|
+
**Diagnostic:**
|
|
526
|
+
|
|
527
|
+
- `GET /api/access/check?username=alice&resourceType=tunnel&resourceId=xxx` — test access
|
|
528
|
+
|
|
529
|
+
### File Watching
|
|
530
|
+
|
|
531
|
+
Gatekeeper watches its state files for changes (using `fs.watch` with debounce):
|
|
532
|
+
|
|
533
|
+
- `/etc/lamalibre/lamaste/groups.json` — group membership changes
|
|
534
|
+
- `/etc/lamalibre/lamaste/access-grants.json` — grant changes
|
|
535
|
+
- `/etc/lamalibre/lamaste/tunnels.json` — tunnel access mode changes
|
|
536
|
+
|
|
537
|
+
On change: reload into memory. No restart needed. Panel-server writes, gatekeeper reads.
|
|
538
|
+
|
|
539
|
+
### systemd Unit
|
|
540
|
+
|
|
541
|
+
```ini
|
|
542
|
+
[Unit]
|
|
543
|
+
Description=Lamaste Gatekeeper — tunnel authorization service
|
|
544
|
+
After=network.target authelia.service
|
|
545
|
+
|
|
546
|
+
[Service]
|
|
547
|
+
ExecStart=/usr/bin/node /usr/lib/lamaste/gatekeeper/server/index.js
|
|
548
|
+
Restart=always
|
|
549
|
+
User=lamaste
|
|
550
|
+
Environment=NODE_ENV=production
|
|
551
|
+
|
|
552
|
+
[Install]
|
|
553
|
+
WantedBy=multi-user.target
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
## Tunnel Access Model
|
|
559
|
+
|
|
560
|
+
### Access Modes
|
|
561
|
+
|
|
562
|
+
Tunnel creation form offers four access levels:
|
|
563
|
+
|
|
564
|
+
| Mode | Authelia | Gatekeeper | nginx Template |
|
|
565
|
+
| --------------- | -------- | ------------ | ------------------------------------------------------ |
|
|
566
|
+
| `admin` | N/A | N/A | mTLS only (existing panel vhost pattern) |
|
|
567
|
+
| `public` | No auth | No auth | Direct proxy, no auth_request |
|
|
568
|
+
| `authenticated` | Yes | Pass-through | auth_request to gatekeeper, always 200 after Authelia |
|
|
569
|
+
| `restricted` | Yes | Grant check | auth_request to gatekeeper, 200 or 403 based on grants |
|
|
570
|
+
|
|
571
|
+
### Tunnel Schema Changes
|
|
572
|
+
|
|
573
|
+
```js
|
|
574
|
+
{
|
|
575
|
+
// Existing fields...
|
|
576
|
+
id, subdomain, port, type, description, fqdn, enabled, createdAt,
|
|
577
|
+
pluginName, agentLabel, pluginRoute,
|
|
578
|
+
|
|
579
|
+
// New field
|
|
580
|
+
accessMode: 'public' | 'authenticated' | 'restricted'
|
|
581
|
+
// Default: 'restricted' for new tunnels
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### nginx Vhost Variants
|
|
586
|
+
|
|
587
|
+
Panel-server's `nginx.js` gains new vhost template functions:
|
|
588
|
+
|
|
589
|
+
- `writePublicVhost(subdomain, domain, port, certPath, { pathPrefix, maxBodySizeMb })` — no auth_request, direct proxy
|
|
590
|
+
- `writeAuthenticatedVhost(subdomain, domain, port, certPath, { pathPrefix, maxBodySizeMb })` — auth_request to gatekeeper, Authelia headers forwarded
|
|
591
|
+
- `writeRestrictedVhost(subdomain, domain, port, certPath, { pathPrefix, maxBodySizeMb })` — auth_request to gatekeeper, Authelia headers forwarded, 403 → access-request redirect
|
|
592
|
+
|
|
593
|
+
The former `writeAppVhost()` (direct Authelia forward auth) was replaced by `writeAuthenticatedVhost()` and has since been removed; every app tunnel vhost is rendered by one of the three writers above. `maxBodySizeMb` becomes the vhost's `client_max_body_size`.
|
|
594
|
+
Existing `writeAgentPanelVhost()` unchanged (mTLS, no gatekeeper involvement).
|
|
595
|
+
|
|
596
|
+
### Default Behavior & Migration
|
|
597
|
+
|
|
598
|
+
- **New tunnels:** default to `restricted` (admin must explicitly grant access)
|
|
599
|
+
- **Existing app tunnels:** migrated to `authenticated` (preserves current behavior — all Authelia users can access)
|
|
600
|
+
- **Existing plugin tunnels:** continue using grant system (migrated to new grant model)
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
## Admin UI Changes
|
|
605
|
+
|
|
606
|
+
### New "Groups" Page (lamaste-server-ui)
|
|
607
|
+
|
|
608
|
+
Sidebar entry under a new "Access" section.
|
|
609
|
+
|
|
610
|
+
**List view:**
|
|
611
|
+
|
|
612
|
+
- Table: name, description, member count, created date
|
|
613
|
+
- Create button → modal with name + description
|
|
614
|
+
- Row actions: edit, delete (with confirmation showing affected grant count)
|
|
615
|
+
|
|
616
|
+
**Detail view (click group name):**
|
|
617
|
+
|
|
618
|
+
- Group info (name, description, created by, created at)
|
|
619
|
+
- Members table with remove button
|
|
620
|
+
- "Add Members" button → modal with Authelia user dropdown (multi-select)
|
|
621
|
+
|
|
622
|
+
### Updated "Users" Page
|
|
623
|
+
|
|
624
|
+
- User creation: Authelia group as radio buttons (Admin / Internal / External)
|
|
625
|
+
- Admin checked → Internal and External disabled
|
|
626
|
+
- Internal checked → External disabled
|
|
627
|
+
- External is default
|
|
628
|
+
- User invitation: same radio button pattern
|
|
629
|
+
- User list: show Authelia group badge (admin/internal/external)
|
|
630
|
+
|
|
631
|
+
### Updated Tunnel Creation Form
|
|
632
|
+
|
|
633
|
+
```
|
|
634
|
+
Subdomain: [____________]
|
|
635
|
+
Port: [____]
|
|
636
|
+
Type: [App ▼] (app | panel | plugin)
|
|
637
|
+
|
|
638
|
+
Access: (o) Restricted — specific users and groups
|
|
639
|
+
( ) All Authelia Users
|
|
640
|
+
( ) Public — no authentication
|
|
641
|
+
|
|
642
|
+
[When "Restricted" selected:]
|
|
643
|
+
Users: [alice] [x] [bob] [x] [+ Add]
|
|
644
|
+
Groups: [developers] [x] [+ Add]
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
- Default selection: Restricted
|
|
648
|
+
- Adding users/groups creates grants via gatekeeper API
|
|
649
|
+
- Removing users/groups revokes grants
|
|
650
|
+
- Changing access mode updates tunnel and rewrites nginx vhost
|
|
651
|
+
|
|
652
|
+
### Updated Tunnel List
|
|
653
|
+
|
|
654
|
+
- Access mode badge on each tunnel card (Public / Authenticated / Restricted)
|
|
655
|
+
- For restricted tunnels: show granted user/group count
|
|
656
|
+
- Click to expand: list of granted users and groups with revoke buttons
|
|
657
|
+
|
|
658
|
+
### Grant Management (integrated, not standalone page)
|
|
659
|
+
|
|
660
|
+
Grants are managed in context:
|
|
661
|
+
|
|
662
|
+
- On tunnel detail: manage who can access this tunnel
|
|
663
|
+
- On group detail: see which resources this group can access
|
|
664
|
+
- On user access page: see which resources this user can access
|
|
665
|
+
|
|
666
|
+
No separate "Grants" page needed — grants are always viewed in the context of their principal or resource.
|
|
667
|
+
|
|
668
|
+
---
|
|
669
|
+
|
|
670
|
+
## CLI Commands
|
|
671
|
+
|
|
672
|
+
```bash
|
|
673
|
+
# Group management
|
|
674
|
+
lamalibre-lamaste-gatekeeper group create <name> [--description "..."]
|
|
675
|
+
lamalibre-lamaste-gatekeeper group list
|
|
676
|
+
lamalibre-lamaste-gatekeeper group show <name>
|
|
677
|
+
lamalibre-lamaste-gatekeeper group rename <name> <new-name>
|
|
678
|
+
lamalibre-lamaste-gatekeeper group delete <name> [--force] # --force skips confirmation
|
|
679
|
+
lamalibre-lamaste-gatekeeper group add-member <group> <username> [<username>...]
|
|
680
|
+
lamalibre-lamaste-gatekeeper group remove-member <group> <username> [<username>...]
|
|
681
|
+
|
|
682
|
+
# Grant management
|
|
683
|
+
lamalibre-lamaste-gatekeeper grant create --user <username> --tunnel <tunnelId>
|
|
684
|
+
lamalibre-lamaste-gatekeeper grant create --group <groupname> --tunnel <tunnelId>
|
|
685
|
+
lamalibre-lamaste-gatekeeper grant create --user <username> --plugin <pluginName> --target agent:<label>
|
|
686
|
+
lamalibre-lamaste-gatekeeper grant list [--user <username>] [--group <groupname>] [--tunnel <tunnelId>]
|
|
687
|
+
lamalibre-lamaste-gatekeeper grant show <grantId>
|
|
688
|
+
lamalibre-lamaste-gatekeeper grant revoke <grantId>
|
|
689
|
+
|
|
690
|
+
# Diagnostic
|
|
691
|
+
lamalibre-lamaste-gatekeeper access check <username> tunnel <tunnelId>
|
|
692
|
+
lamalibre-lamaste-gatekeeper access check <username> plugin <pluginName>
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## Desktop Integration
|
|
698
|
+
|
|
699
|
+
### Gatekeeper Menu (Sidebar Section)
|
|
700
|
+
|
|
701
|
+
A new top-level "Gatekeeper" section in the desktop app sidebar (visible when connected to a server). Provides full access to all gatekeeper functionality — groups, grants, access diagnostics, and configuration.
|
|
702
|
+
|
|
703
|
+
**Sidebar structure:**
|
|
704
|
+
|
|
705
|
+
```
|
|
706
|
+
Gatekeeper
|
|
707
|
+
├─ Dashboard # Overview: group count, grant count, recent activity
|
|
708
|
+
├─ Groups # Full group CRUD + member management
|
|
709
|
+
├─ Grants # Grant list, create, revoke (filterable by resource/principal)
|
|
710
|
+
├─ Access Requests # Pending access request log (if request logging enabled)
|
|
711
|
+
└─ Settings # Gatekeeper configuration (admin contact, templates, cache TTL)
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
### Dashboard Page
|
|
715
|
+
|
|
716
|
+
At-a-glance overview of the access control system:
|
|
717
|
+
|
|
718
|
+
- **Stats cards:** total groups, total grants, total users with grants, active tunnels with restricted access
|
|
719
|
+
- **Recent activity:** last 10 grant creates/revokes with timestamps
|
|
720
|
+
- **Quick actions:** "Create Group", "Grant Access" buttons
|
|
721
|
+
|
|
722
|
+
### Groups Page
|
|
723
|
+
|
|
724
|
+
Full CRUD for Lamaste access control groups.
|
|
725
|
+
|
|
726
|
+
**List view:**
|
|
727
|
+
|
|
728
|
+
- Table: name, description, member count, grant count (how many resources this group can access), created date
|
|
729
|
+
- Search/filter bar
|
|
730
|
+
- "Create Group" button → modal with name + description fields
|
|
731
|
+
- Row actions: edit (rename, description), manage members, delete (with confirmation showing affected grant count)
|
|
732
|
+
|
|
733
|
+
**Member management (inline expand or detail view):**
|
|
734
|
+
|
|
735
|
+
- Current members list with remove button per member
|
|
736
|
+
- "Add Members" button → dropdown of Authelia users (fetched from lamalibre-lamaste-serverd `/api/users`) with multi-select
|
|
737
|
+
- Shows which Authelia role tier each user belongs to (admin/internal/external badge)
|
|
738
|
+
|
|
739
|
+
### Grants Page
|
|
740
|
+
|
|
741
|
+
Unified view of all grants across all resource types.
|
|
742
|
+
|
|
743
|
+
**List view:**
|
|
744
|
+
|
|
745
|
+
- Table: principal (user/group badge + name), resource (tunnel/plugin badge + name), context, status, created date
|
|
746
|
+
- Filter bar: by principal type, principal name, resource type, resource name
|
|
747
|
+
- "Create Grant" button → modal:
|
|
748
|
+
- Principal: radio (User / Group) + dropdown
|
|
749
|
+
- Resource: radio (Tunnel / Plugin) + dropdown (tunnels list / plugin list)
|
|
750
|
+
- Context: shown conditionally (target field for plugin grants)
|
|
751
|
+
- Row actions: revoke (with confirmation)
|
|
752
|
+
|
|
753
|
+
**Contextual views (also accessible from tunnel/group detail pages):**
|
|
754
|
+
|
|
755
|
+
- "Grants for this tunnel" — filtered grant list shown on tunnel detail
|
|
756
|
+
- "Grants for this group" — filtered grant list shown on group detail
|
|
757
|
+
|
|
758
|
+
### Access Requests Page
|
|
759
|
+
|
|
760
|
+
Log of denied access attempts (if enabled in settings):
|
|
761
|
+
|
|
762
|
+
- Table: timestamp, username, requested resource (tunnel FQDN), status (pending/noted)
|
|
763
|
+
- Helps admins see who is trying to access what and proactively grant access
|
|
764
|
+
- Optional: gatekeeper logs denied attempts to `/etc/lamalibre/lamaste/access-request-log.json` (configurable, off by default, auto-rotated)
|
|
765
|
+
|
|
766
|
+
### Settings Page
|
|
767
|
+
|
|
768
|
+
Gatekeeper configuration managed via the desktop app:
|
|
769
|
+
|
|
770
|
+
**Admin Contact Info** (used in access-request page templates):
|
|
771
|
+
|
|
772
|
+
- Admin email address
|
|
773
|
+
- Admin display name
|
|
774
|
+
- Optional: Slack channel, Teams channel
|
|
775
|
+
- Saved to `/etc/lamalibre/lamaste/gatekeeper.json`
|
|
776
|
+
|
|
777
|
+
**Message Templates:**
|
|
778
|
+
|
|
779
|
+
- Preview and customize the access-request message templates
|
|
780
|
+
- Per-channel toggle (email, Slack, Teams, WhatsApp)
|
|
781
|
+
- Template variables: `{{username}}`, `{{resource}}`, `{{adminName}}`, `{{adminEmail}}`
|
|
782
|
+
|
|
783
|
+
**Cache Configuration:**
|
|
784
|
+
|
|
785
|
+
- nginx cache TTL display (informational — actual value in nginx config)
|
|
786
|
+
- Gatekeeper session cache TTL (configurable, default 30s)
|
|
787
|
+
- "Bust Cache" button — immediately invalidates all cached auth decisions
|
|
788
|
+
|
|
789
|
+
**Access Request Logging:**
|
|
790
|
+
|
|
791
|
+
- Toggle on/off
|
|
792
|
+
- Retention period (default 30 days)
|
|
793
|
+
- "Clear Log" button
|
|
794
|
+
|
|
795
|
+
### Tauri Commands
|
|
796
|
+
|
|
797
|
+
```rust
|
|
798
|
+
// Groups
|
|
799
|
+
get_gatekeeper_groups(server_id) → Vec<Group>
|
|
800
|
+
create_gatekeeper_group(server_id, name, description) → Group
|
|
801
|
+
update_gatekeeper_group(server_id, name, updates) → Group
|
|
802
|
+
delete_gatekeeper_group(server_id, name) → DeleteResult { deleted_grants: u32 }
|
|
803
|
+
add_gatekeeper_group_members(server_id, group_name, usernames) → Group
|
|
804
|
+
remove_gatekeeper_group_members(server_id, group_name, usernames) → Group
|
|
805
|
+
get_gatekeeper_groups_for_user(server_id, username) → Vec<String>
|
|
806
|
+
|
|
807
|
+
// Grants
|
|
808
|
+
get_gatekeeper_grants(server_id, filter?) → Vec<Grant>
|
|
809
|
+
create_gatekeeper_grant(server_id, principal_type, principal_id, resource_type, resource_id, context?) → Grant
|
|
810
|
+
get_gatekeeper_grant(server_id, grant_id) → Grant
|
|
811
|
+
revoke_gatekeeper_grant(server_id, grant_id) → Grant
|
|
812
|
+
|
|
813
|
+
// Authorization diagnostic
|
|
814
|
+
check_gatekeeper_access(server_id, username, resource_type, resource_id) → AccessResult
|
|
815
|
+
|
|
816
|
+
// Settings
|
|
817
|
+
get_gatekeeper_settings(server_id) → GatekeeperSettings
|
|
818
|
+
update_gatekeeper_settings(server_id, settings) → GatekeeperSettings
|
|
819
|
+
|
|
820
|
+
// Cache
|
|
821
|
+
bust_gatekeeper_cache(server_id) → ()
|
|
822
|
+
|
|
823
|
+
// Access request log
|
|
824
|
+
get_access_request_log(server_id, limit?, offset?) → Vec<AccessRequestEntry>
|
|
825
|
+
clear_access_request_log(server_id) → ()
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
**Communication:** Desktop calls gatekeeper's REST API via lamalibre-lamaste-serverd's SSH tunnel (for remote servers) or directly on `127.0.0.1:9294` (for local server installs). Uses `curl_panel`-style HTTP helper in `gatekeeper.rs` — same pattern as existing Tauri → lamalibre-lamaste-serverd communication but targeting gatekeeper's port.
|
|
829
|
+
|
|
830
|
+
### React Pages (lamaste-server-ui)
|
|
831
|
+
|
|
832
|
+
Gatekeeper pages are implemented in `lamaste-server-ui` as shared components, following the same pattern as other admin pages:
|
|
833
|
+
|
|
834
|
+
```
|
|
835
|
+
packages/lamaste-server-ui/src/pages/
|
|
836
|
+
GatekeeperDashboard.jsx
|
|
837
|
+
GatekeeperGroups.jsx
|
|
838
|
+
GatekeeperGrants.jsx
|
|
839
|
+
GatekeeperAccessRequests.jsx
|
|
840
|
+
GatekeeperSettings.jsx
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
These use `useAdminClient()` context hook — desktop provides Tauri invoke implementation, web panel provides `apiFetch()` implementation. `AdminClientContext` interface extended with all gatekeeper methods.
|
|
844
|
+
|
|
845
|
+
---
|
|
846
|
+
|
|
847
|
+
## Migration
|
|
848
|
+
|
|
849
|
+
### State File Migration
|
|
850
|
+
|
|
851
|
+
On first startup, gatekeeper checks for legacy files:
|
|
852
|
+
|
|
853
|
+
1. **`/etc/lamalibre/lamaste/user-plugin-access.json`** → migrate to `/etc/lamalibre/lamaste/access-grants.json`
|
|
854
|
+
- Each legacy grant maps to new schema:
|
|
855
|
+
- `username` → `principalType: 'user'`, `principalId: username`
|
|
856
|
+
- `pluginName` → `resourceType: 'plugin'`, `resourceId: pluginName`
|
|
857
|
+
- `target` → `context: { target }`
|
|
858
|
+
- Legacy file renamed to `user-plugin-access.json.migrated` (backup)
|
|
859
|
+
|
|
860
|
+
2. **Groups extracted from Authelia `users.yml`** → NOT migrated (Lamaste groups are a new concept, start empty)
|
|
861
|
+
|
|
862
|
+
### Panel-Server Refactoring
|
|
863
|
+
|
|
864
|
+
- `lib/user-access.js` → delegates to gatekeeper library (import from `@lamalibre/lamaste-gatekeeper@4.0.0`)
|
|
865
|
+
- `lib/access-control-sync.js` → removed (gatekeeper handles authorization at nginx layer, no more Authelia config rewriting for per-user access rules)
|
|
866
|
+
- `routes/user-access.js` → simplified, delegates grant CRUD to gatekeeper
|
|
867
|
+
- Tunnel creation/update routes → call gatekeeper for access mode changes
|
|
868
|
+
|
|
869
|
+
### Installer Updates (create-lamaste)
|
|
870
|
+
|
|
871
|
+
- Install `@lamalibre/lamaste-gatekeeper@4.0.0` package
|
|
872
|
+
- Create `/etc/lamalibre/lamaste/groups.json` (empty initial state)
|
|
873
|
+
- Create `/etc/lamalibre/lamaste/access-grants.json` (empty or migrated)
|
|
874
|
+
- Create `lamalibre-lamaste-gatekeeper.service` systemd unit
|
|
875
|
+
- Enable and start service
|
|
876
|
+
- Update nginx templates for new vhost variants
|
|
877
|
+
|
|
878
|
+
---
|
|
879
|
+
|
|
880
|
+
## Security
|
|
881
|
+
|
|
882
|
+
### Boundaries
|
|
883
|
+
|
|
884
|
+
| Component | Binds to | Auth | Purpose |
|
|
885
|
+
| ------------------ | ---------------- | ---------------------- | ----------------------------- |
|
|
886
|
+
| Gatekeeper service | `127.0.0.1:9294` | None (localhost trust) | nginx auth_request + REST API |
|
|
887
|
+
| Panel-server | `127.0.0.1:9292` | mTLS | Admin operations |
|
|
888
|
+
| Authelia | `127.0.0.1:9091` | Session cookie | User authentication |
|
|
889
|
+
|
|
890
|
+
### Attack Surface Analysis
|
|
891
|
+
|
|
892
|
+
- **Gatekeeper is not externally reachable** — binds `127.0.0.1`, nginx is the sole caller
|
|
893
|
+
- **No credential storage** — reads Authelia session via cookie forwarding to Authelia's own verify endpoint
|
|
894
|
+
- **No mTLS certs** — completely separate from the admin/agent certificate system
|
|
895
|
+
- **State files** — `0600` permissions, atomic writes (temp → fsync → rename)
|
|
896
|
+
- **DoS protection** — hard caps on groups (200), grants (1000), matching ticket system limits
|
|
897
|
+
- **Group name validation** — lowercase alphanumeric + hyphens, reserved names rejected
|
|
898
|
+
- **Grant uniqueness** — duplicate principal+resource+context rejected
|
|
899
|
+
- **Timing-safe checks** — not needed here (no secret comparison, just membership lookup)
|
|
900
|
+
|
|
901
|
+
### What Gatekeeper Cannot Do
|
|
902
|
+
|
|
903
|
+
- Access lamalibre-lamaste-serverd API
|
|
904
|
+
- Read/write mTLS certificates
|
|
905
|
+
- Modify Authelia `users.yml`
|
|
906
|
+
- Restart any service
|
|
907
|
+
- Access any state file outside its own scope (`groups.json`, `access-grants.json`, `tunnels.json` read-only)
|
|
908
|
+
|
|
909
|
+
---
|
|
910
|
+
|
|
911
|
+
## Relationship to lamaste-tickets
|
|
912
|
+
|
|
913
|
+
Gatekeeper and tickets are separate packages that coexist. They solve different authorization problems:
|
|
914
|
+
|
|
915
|
+
| | **Tickets** | **Gatekeeper** |
|
|
916
|
+
| -------------- | ------------------------------------------------ | --------------------------------------- |
|
|
917
|
+
| **Who** | Agent (mTLS cert CN) | Human (Authelia username) |
|
|
918
|
+
| **Authorizes** | Agent-to-agent communication | User-to-resource access |
|
|
919
|
+
| **Identity** | mTLS certificate | Authelia session cookie |
|
|
920
|
+
| **Mechanism** | Issue token → validate → session with heartbeats | Check grant → allow/deny (stateless) |
|
|
921
|
+
| **Lifecycle** | Ephemeral (30s tokens, heartbeat sessions) | Persistent (grants live until revoked) |
|
|
922
|
+
| **Concepts** | Scopes, instances, transport negotiation | Groups, access modes, request templates |
|
|
923
|
+
| **nginx** | Not involved | auth_request target |
|
|
924
|
+
|
|
925
|
+
They share infrastructure patterns (atomic JSON writes, promise-chain mutex) but their domain logic is fundamentally different. Unifying them would create confused abstractions — every function would need to ask "am I in machine mode or human mode?"
|
|
926
|
+
|
|
927
|
+
- **Tickets** = machine-to-machine authorization protocol
|
|
928
|
+
- **Gatekeeper** = human-to-resource access control
|
|
929
|
+
|
|
930
|
+
---
|
|
931
|
+
|
|
932
|
+
## Affected Packages
|
|
933
|
+
|
|
934
|
+
| Package | Changes |
|
|
935
|
+
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
936
|
+
| **lamalibre-lamaste-gatekeeper** (NEW) | Full package — library, service, CLI |
|
|
937
|
+
| **lamalibre-lamaste-serverd** | Refactor user-access to delegate to gatekeeper lib, remove access-control-sync, new nginx vhost templates, tunnel schema gains accessMode |
|
|
938
|
+
| **lamaste-server-ui** | New Gatekeeper sidebar section (Dashboard, Groups, Grants, Access Requests, Settings), updated Users page (role radio buttons), updated tunnel creation form (access mode + user/group picker), tunnel list access badges |
|
|
939
|
+
| **create-lamaste** | Install gatekeeper package + service, state file creation, nginx template updates, nginx proxy_cache zone |
|
|
940
|
+
| **lamaste-agent** | Minimal — agent reports tunnel access mode in status |
|
|
941
|
+
| **lamaste-desktop** | New Gatekeeper sidebar section with full management UI, Tauri commands in `gatekeeper.rs`, React pages from admin-panel |
|
|
942
|
+
| **tests/e2e** | Group CRUD, grant CRUD, tunnel access modes, access-request page, caching behavior, migration |
|
|
943
|
+
| **tests/e2e-three-vm** | Multi-agent restricted tunnel access, group-based access across agents |
|
|
944
|
+
|
|
945
|
+
---
|
|
946
|
+
|
|
947
|
+
## Resolved Decisions
|
|
948
|
+
|
|
949
|
+
1. **Access request page hosting**: **Inline on the tunnel's own FQDN.** Gatekeeper returns the full HTML page as the 403 response body — no redirect. nginx uses `error_page 403 = /internal/lamaste/authz` pattern where gatekeeper serves the "request access" page directly. The user stays on `gitlab.example.com` (the URL they tried to visit), which is the most natural UX. No extra subdomain, no extra DNS record, no extra TLS cert.
|
|
950
|
+
|
|
951
|
+
2. **Grant auto-cleanup**: **90-day lazy cleanup for consumed grants**, consistent with the existing pattern. Configurable retention period via gatekeeper settings. Grants are small (JSON entries), but auto-cleanup prevents unbounded accumulation over years of operation.
|
|
952
|
+
|
|
953
|
+
3. **Plugin tunnel migration**: **Hard cut-over.** Installer upgrade replaces all plugin tunnel vhosts with gatekeeper-based ones, migrates grants from `user-plugin-access.json` to `access-grants.json`, and removes the Authelia access control rules that `syncAllAccessControl()` previously wrote. Running two authorization systems in parallel would be more complex and error-prone than switching cleanly. The installer already handles nginx vhost rewrites during upgrades.
|