@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.
Files changed (270) hide show
  1. package/LICENSE.md +118 -0
  2. package/README.md +114 -0
  3. package/bin/create-lamaste.js +15 -0
  4. package/package.json +54 -0
  5. package/scripts/bundle-vendor.js +110 -0
  6. package/scripts/lamaste-cert-info +62 -0
  7. package/scripts/lamaste-certbot +96 -0
  8. package/scripts/lamaste-priv +693 -0
  9. package/src/index.js +646 -0
  10. package/src/lib/accounts.js +86 -0
  11. package/src/lib/binaries.js +137 -0
  12. package/src/lib/cert-help-page.js +160 -0
  13. package/src/lib/env.js +132 -0
  14. package/src/lib/http-redirect.js +38 -0
  15. package/src/lib/ownership.js +231 -0
  16. package/src/lib/relay-services.js +103 -0
  17. package/src/lib/secrets.js +19 -0
  18. package/src/lib/service-config.js +311 -0
  19. package/src/lib/summary.js +131 -0
  20. package/src/lib/wrappers.js +63 -0
  21. package/src/tasks/gatekeeper.js +233 -0
  22. package/src/tasks/harden.js +293 -0
  23. package/src/tasks/mtls.js +193 -0
  24. package/src/tasks/nginx.js +265 -0
  25. package/src/tasks/node.js +110 -0
  26. package/src/tasks/panel.js +408 -0
  27. package/src/tasks/redeploy.js +438 -0
  28. package/vendor/docs/00-introduction/how-it-works.md +447 -0
  29. package/vendor/docs/00-introduction/quickstart.md +472 -0
  30. package/vendor/docs/00-introduction/what-is-lamaste.md +112 -0
  31. package/vendor/docs/01-concepts/authentication.md +464 -0
  32. package/vendor/docs/01-concepts/authorization.md +242 -0
  33. package/vendor/docs/01-concepts/certificates.md +452 -0
  34. package/vendor/docs/01-concepts/dns-and-domains.md +343 -0
  35. package/vendor/docs/01-concepts/mtls.md +490 -0
  36. package/vendor/docs/01-concepts/nginx-reverse-proxy.md +540 -0
  37. package/vendor/docs/01-concepts/security-model.md +600 -0
  38. package/vendor/docs/01-concepts/tickets.md +414 -0
  39. package/vendor/docs/01-concepts/tunneling.md +367 -0
  40. package/vendor/docs/02-guides/agent-setup.md +194 -0
  41. package/vendor/docs/02-guides/certificate-management.md +341 -0
  42. package/vendor/docs/02-guides/cloud-provisioning.md +406 -0
  43. package/vendor/docs/02-guides/desktop-app-setup.md +226 -0
  44. package/vendor/docs/02-guides/disaster-recovery.md +464 -0
  45. package/vendor/docs/02-guides/first-tunnel.md +230 -0
  46. package/vendor/docs/02-guides/installation.md +393 -0
  47. package/vendor/docs/02-guides/managing-users.md +255 -0
  48. package/vendor/docs/02-guides/onboarding.md +244 -0
  49. package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +375 -0
  50. package/vendor/docs/02-guides/static-sites.md +364 -0
  51. package/vendor/docs/03-architecture/e2e-three-vm-sequences.md +457 -0
  52. package/vendor/docs/03-architecture/installer-flow.md +151 -0
  53. package/vendor/docs/03-architecture/installer.md +445 -0
  54. package/vendor/docs/03-architecture/management-flow.md +288 -0
  55. package/vendor/docs/03-architecture/nginx-configuration.md +830 -0
  56. package/vendor/docs/03-architecture/onboarding-flow.md +164 -0
  57. package/vendor/docs/03-architecture/overview.md +541 -0
  58. package/vendor/docs/03-architecture/panel-client.md +416 -0
  59. package/vendor/docs/03-architecture/panel-server.md +695 -0
  60. package/vendor/docs/03-architecture/state-management.md +529 -0
  61. package/vendor/docs/03-architecture/system-overview.md +167 -0
  62. package/vendor/docs/04-api-reference/certificates.md +1141 -0
  63. package/vendor/docs/04-api-reference/gatekeeper.md +592 -0
  64. package/vendor/docs/04-api-reference/onboarding.md +473 -0
  65. package/vendor/docs/04-api-reference/overview.md +404 -0
  66. package/vendor/docs/04-api-reference/services.md +267 -0
  67. package/vendor/docs/04-api-reference/settings.md +343 -0
  68. package/vendor/docs/04-api-reference/sites.md +716 -0
  69. package/vendor/docs/04-api-reference/system.md +268 -0
  70. package/vendor/docs/04-api-reference/tickets.md +618 -0
  71. package/vendor/docs/04-api-reference/tunnels.md +417 -0
  72. package/vendor/docs/04-api-reference/users.md +333 -0
  73. package/vendor/docs/05-operations/backup-and-restore.md +342 -0
  74. package/vendor/docs/05-operations/monitoring.md +285 -0
  75. package/vendor/docs/05-operations/uninstalling.md +344 -0
  76. package/vendor/docs/05-operations/upgrades.md +270 -0
  77. package/vendor/docs/06-reference/config-files.md +1080 -0
  78. package/vendor/docs/06-reference/glossary.md +528 -0
  79. package/vendor/docs/06-reference/installer-flags.md +260 -0
  80. package/vendor/docs/06-reference/ports-and-services.md +244 -0
  81. package/vendor/docs/06-reference/troubleshooting.md +665 -0
  82. package/vendor/docs/_index.json +233 -0
  83. package/vendor/docs/gatekeeper-design.md +953 -0
  84. package/vendor/docs/package.json +15 -0
  85. package/vendor/docs/todo/lib-cli-desktop-refactor.md +641 -0
  86. package/vendor/gatekeeper/dist/boot.d.ts +1 -0
  87. package/vendor/gatekeeper/dist/boot.d.ts.map +1 -0
  88. package/vendor/gatekeeper/dist/boot.js +17 -0
  89. package/vendor/gatekeeper/dist/boot.js.map +1 -0
  90. package/vendor/gatekeeper/dist/cli/commands/access.d.ts +2 -0
  91. package/vendor/gatekeeper/dist/cli/commands/access.d.ts.map +1 -0
  92. package/vendor/gatekeeper/dist/cli/commands/access.js +33 -0
  93. package/vendor/gatekeeper/dist/cli/commands/access.js.map +1 -0
  94. package/vendor/gatekeeper/dist/cli/commands/grants.d.ts +2 -0
  95. package/vendor/gatekeeper/dist/cli/commands/grants.d.ts.map +1 -0
  96. package/vendor/gatekeeper/dist/cli/commands/grants.js +110 -0
  97. package/vendor/gatekeeper/dist/cli/commands/grants.js.map +1 -0
  98. package/vendor/gatekeeper/dist/cli/commands/groups.d.ts +2 -0
  99. package/vendor/gatekeeper/dist/cli/commands/groups.d.ts.map +1 -0
  100. package/vendor/gatekeeper/dist/cli/commands/groups.js +98 -0
  101. package/vendor/gatekeeper/dist/cli/commands/groups.js.map +1 -0
  102. package/vendor/gatekeeper/dist/cli/index.d.ts +3 -0
  103. package/vendor/gatekeeper/dist/cli/index.d.ts.map +1 -0
  104. package/vendor/gatekeeper/dist/cli/index.js +45 -0
  105. package/vendor/gatekeeper/dist/cli/index.js.map +1 -0
  106. package/vendor/gatekeeper/dist/index.d.ts +9 -0
  107. package/vendor/gatekeeper/dist/index.d.ts.map +1 -0
  108. package/vendor/gatekeeper/dist/index.js +13 -0
  109. package/vendor/gatekeeper/dist/index.js.map +1 -0
  110. package/vendor/gatekeeper/dist/lib/authz.d.ts +16 -0
  111. package/vendor/gatekeeper/dist/lib/authz.d.ts.map +1 -0
  112. package/vendor/gatekeeper/dist/lib/authz.js +42 -0
  113. package/vendor/gatekeeper/dist/lib/authz.js.map +1 -0
  114. package/vendor/gatekeeper/dist/lib/cache-bust.d.ts +15 -0
  115. package/vendor/gatekeeper/dist/lib/cache-bust.d.ts.map +1 -0
  116. package/vendor/gatekeeper/dist/lib/cache-bust.js +19 -0
  117. package/vendor/gatekeeper/dist/lib/cache-bust.js.map +1 -0
  118. package/vendor/gatekeeper/dist/lib/constants.d.ts +82 -0
  119. package/vendor/gatekeeper/dist/lib/constants.d.ts.map +1 -0
  120. package/vendor/gatekeeper/dist/lib/constants.js +79 -0
  121. package/vendor/gatekeeper/dist/lib/constants.js.map +1 -0
  122. package/vendor/gatekeeper/dist/lib/grants.d.ts +32 -0
  123. package/vendor/gatekeeper/dist/lib/grants.d.ts.map +1 -0
  124. package/vendor/gatekeeper/dist/lib/grants.js +314 -0
  125. package/vendor/gatekeeper/dist/lib/grants.js.map +1 -0
  126. package/vendor/gatekeeper/dist/lib/groups.d.ts +10 -0
  127. package/vendor/gatekeeper/dist/lib/groups.d.ts.map +1 -0
  128. package/vendor/gatekeeper/dist/lib/groups.js +247 -0
  129. package/vendor/gatekeeper/dist/lib/groups.js.map +1 -0
  130. package/vendor/gatekeeper/dist/lib/migration.d.ts +17 -0
  131. package/vendor/gatekeeper/dist/lib/migration.d.ts.map +1 -0
  132. package/vendor/gatekeeper/dist/lib/migration.js +79 -0
  133. package/vendor/gatekeeper/dist/lib/migration.js.map +1 -0
  134. package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0001_groups.sql +7 -0
  135. package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0002_access_grants.sql +14 -0
  136. package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0003_access_request_log.sql +9 -0
  137. package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0001_groups.sql +7 -0
  138. package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0002_access_grants.sql +14 -0
  139. package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0003_access_request_log.sql +9 -0
  140. package/vendor/gatekeeper/dist/lib/state-db.d.ts +16 -0
  141. package/vendor/gatekeeper/dist/lib/state-db.d.ts.map +1 -0
  142. package/vendor/gatekeeper/dist/lib/state-db.js +154 -0
  143. package/vendor/gatekeeper/dist/lib/state-db.js.map +1 -0
  144. package/vendor/gatekeeper/dist/lib/templates.d.ts +21 -0
  145. package/vendor/gatekeeper/dist/lib/templates.d.ts.map +1 -0
  146. package/vendor/gatekeeper/dist/lib/templates.js +151 -0
  147. package/vendor/gatekeeper/dist/lib/templates.js.map +1 -0
  148. package/vendor/gatekeeper/dist/lib/types.d.ts +178 -0
  149. package/vendor/gatekeeper/dist/lib/types.d.ts.map +1 -0
  150. package/vendor/gatekeeper/dist/lib/types.js +2 -0
  151. package/vendor/gatekeeper/dist/lib/types.js.map +1 -0
  152. package/vendor/gatekeeper/dist/server/index.d.ts +15 -0
  153. package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -0
  154. package/vendor/gatekeeper/dist/server/index.js +251 -0
  155. package/vendor/gatekeeper/dist/server/index.js.map +1 -0
  156. package/vendor/gatekeeper/dist/server/routes/access-request.d.ts +7 -0
  157. package/vendor/gatekeeper/dist/server/routes/access-request.d.ts.map +1 -0
  158. package/vendor/gatekeeper/dist/server/routes/access-request.js +17 -0
  159. package/vendor/gatekeeper/dist/server/routes/access-request.js.map +1 -0
  160. package/vendor/gatekeeper/dist/server/routes/authz.d.ts +3 -0
  161. package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -0
  162. package/vendor/gatekeeper/dist/server/routes/authz.js +279 -0
  163. package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -0
  164. package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts +13 -0
  165. package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts.map +1 -0
  166. package/vendor/gatekeeper/dist/server/routes/diagnostic.js +249 -0
  167. package/vendor/gatekeeper/dist/server/routes/diagnostic.js.map +1 -0
  168. package/vendor/gatekeeper/dist/server/routes/grants.d.ts +3 -0
  169. package/vendor/gatekeeper/dist/server/routes/grants.d.ts.map +1 -0
  170. package/vendor/gatekeeper/dist/server/routes/grants.js +112 -0
  171. package/vendor/gatekeeper/dist/server/routes/grants.js.map +1 -0
  172. package/vendor/gatekeeper/dist/server/routes/groups.d.ts +3 -0
  173. package/vendor/gatekeeper/dist/server/routes/groups.d.ts.map +1 -0
  174. package/vendor/gatekeeper/dist/server/routes/groups.js +139 -0
  175. package/vendor/gatekeeper/dist/server/routes/groups.js.map +1 -0
  176. package/vendor/gatekeeper/package.json +49 -0
  177. package/vendor/server/bin/lamaste-server.js +204 -0
  178. package/vendor/server/package.json +31 -0
  179. package/vendor/server/src/commands/certs.js +224 -0
  180. package/vendor/server/src/commands/chisel.js +132 -0
  181. package/vendor/server/src/commands/logs.js +49 -0
  182. package/vendor/server/src/commands/plugins.js +321 -0
  183. package/vendor/server/src/commands/reset-admin.js +478 -0
  184. package/vendor/server/src/commands/restart.js +94 -0
  185. package/vendor/server/src/commands/sites.js +257 -0
  186. package/vendor/server/src/commands/status.js +158 -0
  187. package/vendor/server/src/commands/tunnels.js +365 -0
  188. package/vendor/server/src/commands/uninstall.js +326 -0
  189. package/vendor/server/src/config.js +38 -0
  190. package/vendor/server/src/exec.js +26 -0
  191. package/vendor/server/src/logger.js +39 -0
  192. package/vendor/server/src/ndjson.js +56 -0
  193. package/vendor/server/src/panel-api.js +85 -0
  194. package/vendor/server/src/state.js +45 -0
  195. package/vendor/server-ui/dist/assets/index-B0nWuUyL.js +257 -0
  196. package/vendor/server-ui/dist/assets/index-BqWWFlTU.css +1 -0
  197. package/vendor/server-ui/dist/index.html +13 -0
  198. package/vendor/serverd/package.json +44 -0
  199. package/vendor/serverd/src/boot.js +13 -0
  200. package/vendor/serverd/src/index.js +191 -0
  201. package/vendor/serverd/src/lib/app-error.js +14 -0
  202. package/vendor/serverd/src/lib/authelia.js +85 -0
  203. package/vendor/serverd/src/lib/certbot.js +74 -0
  204. package/vendor/serverd/src/lib/chisel-args.js +6 -0
  205. package/vendor/serverd/src/lib/chisel-reconcile.js +232 -0
  206. package/vendor/serverd/src/lib/chisel-runtime.js +46 -0
  207. package/vendor/serverd/src/lib/chisel-users.js +109 -0
  208. package/vendor/serverd/src/lib/chisel.js +81 -0
  209. package/vendor/serverd/src/lib/config.js +190 -0
  210. package/vendor/serverd/src/lib/constants.js +8 -0
  211. package/vendor/serverd/src/lib/csr-signing.js +331 -0
  212. package/vendor/serverd/src/lib/enrollment.js +302 -0
  213. package/vendor/serverd/src/lib/files.js +19 -0
  214. package/vendor/serverd/src/lib/gatekeeper-client.js +135 -0
  215. package/vendor/serverd/src/lib/invite-page.js +281 -0
  216. package/vendor/serverd/src/lib/ip-utils.js +79 -0
  217. package/vendor/serverd/src/lib/migrations/state/0001_enrollment_tokens.sql +17 -0
  218. package/vendor/serverd/src/lib/migrations/state/0002_tickets.sql +70 -0
  219. package/vendor/serverd/src/lib/migrations/state/0003_plugins.sql +15 -0
  220. package/vendor/serverd/src/lib/migrations/state/0004_agents.sql +19 -0
  221. package/vendor/serverd/src/lib/mtls.js +795 -0
  222. package/vendor/serverd/src/lib/nginx.js +1090 -0
  223. package/vendor/serverd/src/lib/pki-paths.js +41 -0
  224. package/vendor/serverd/src/lib/pki-sign.js +103 -0
  225. package/vendor/serverd/src/lib/plugins.js +538 -0
  226. package/vendor/serverd/src/lib/push-install.js +390 -0
  227. package/vendor/serverd/src/lib/revocation.js +112 -0
  228. package/vendor/serverd/src/lib/services.js +131 -0
  229. package/vendor/serverd/src/lib/session.js +151 -0
  230. package/vendor/serverd/src/lib/state-db.js +130 -0
  231. package/vendor/serverd/src/lib/state.js +140 -0
  232. package/vendor/serverd/src/lib/storage.js +63 -0
  233. package/vendor/serverd/src/lib/system-stats.js +58 -0
  234. package/vendor/serverd/src/lib/tickets.js +1556 -0
  235. package/vendor/serverd/src/lib/totp.js +76 -0
  236. package/vendor/serverd/src/lib/tunnel-deps.js +41 -0
  237. package/vendor/serverd/src/lib/user-access-session.js +142 -0
  238. package/vendor/serverd/src/lib/user-access.js +52 -0
  239. package/vendor/serverd/src/middleware/errors.js +63 -0
  240. package/vendor/serverd/src/middleware/mtls.js +120 -0
  241. package/vendor/serverd/src/middleware/onboarding-guard.js +30 -0
  242. package/vendor/serverd/src/middleware/role-guard.js +58 -0
  243. package/vendor/serverd/src/middleware/twofa-session.js +65 -0
  244. package/vendor/serverd/src/middleware/user-access-session.js +52 -0
  245. package/vendor/serverd/src/routes/enrollment.js +120 -0
  246. package/vendor/serverd/src/routes/health.js +22 -0
  247. package/vendor/serverd/src/routes/invite.js +163 -0
  248. package/vendor/serverd/src/routes/management/agents.js +182 -0
  249. package/vendor/serverd/src/routes/management/certs.js +911 -0
  250. package/vendor/serverd/src/routes/management/gatekeeper-proxy.js +160 -0
  251. package/vendor/serverd/src/routes/management/identity.js +107 -0
  252. package/vendor/serverd/src/routes/management/invitations.js +166 -0
  253. package/vendor/serverd/src/routes/management/logs.js +218 -0
  254. package/vendor/serverd/src/routes/management/plugins.js +560 -0
  255. package/vendor/serverd/src/routes/management/services.js +63 -0
  256. package/vendor/serverd/src/routes/management/settings.js +325 -0
  257. package/vendor/serverd/src/routes/management/sites.js +522 -0
  258. package/vendor/serverd/src/routes/management/storage.js +192 -0
  259. package/vendor/serverd/src/routes/management/system.js +201 -0
  260. package/vendor/serverd/src/routes/management/tickets.js +457 -0
  261. package/vendor/serverd/src/routes/management/tunnels.js +709 -0
  262. package/vendor/serverd/src/routes/management/users.js +377 -0
  263. package/vendor/serverd/src/routes/management.js +36 -0
  264. package/vendor/serverd/src/routes/onboarding/dns.js +101 -0
  265. package/vendor/serverd/src/routes/onboarding/domain.js +42 -0
  266. package/vendor/serverd/src/routes/onboarding/index.js +18 -0
  267. package/vendor/serverd/src/routes/onboarding/provision.js +327 -0
  268. package/vendor/serverd/src/routes/onboarding/status.js +12 -0
  269. package/vendor/serverd/src/routes/plugin-router.js +79 -0
  270. package/vendor/serverd/src/routes/user-access.js +389 -0
@@ -0,0 +1,452 @@
1
+ # Certificates
2
+
3
+ > Lamaste manages three types of TLS certificates: Let's Encrypt certificates for public domains, self-signed certificates for IP access, and mTLS client certificates for admin authentication.
4
+
5
+ ## In Plain English
6
+
7
+ When you visit a website over HTTPS, your browser checks the site's certificate to verify two things: the connection is encrypted, and the site is who it claims to be. Without a certificate, your browser shows a warning and the connection is insecure.
8
+
9
+ Lamaste needs certificates for every domain it serves — `panel.example.com`, `auth.example.com`, `myapp.example.com`, and so on. Buying certificates would be expensive and tedious, so Lamaste uses Let's Encrypt, a free certificate authority that issues certificates automatically.
10
+
11
+ Think of Let's Encrypt like a free notary service. Lamaste asks "I need a certificate for myapp.example.com," and Let's Encrypt says "prove you control that domain." Lamaste proves it by serving a specific file on that domain (an HTTP challenge). Let's Encrypt verifies the file is there and issues the certificate. The whole process takes a few seconds and happens without any human intervention.
12
+
13
+ Let's Encrypt certificates expire every 90 days. Lamaste sets up automatic renewal so you never need to think about it. A system timer checks twice daily whether any certificates need renewing and handles it silently.
14
+
15
+ For the IP-based admin panel (`https://<ip>:9292`), there is no domain to verify, so Lamaste uses a self-signed certificate instead. Your browser shows a security warning for self-signed certificates, but the connection is still encrypted. The real authentication for the admin panel comes from the [mTLS client certificate](mtls.md), not from the server certificate.
16
+
17
+ ## For Users
18
+
19
+ ### Certificate types in Lamaste
20
+
21
+ | Type | Where used | Issued by | Validity | Auto-renew |
22
+ | ----------------- | ------------------------------------------------------------------ | ----------------- | -------- | ------------------- |
23
+ | Let's Encrypt | Domain-based vhosts (`*.example.com`) | Let's Encrypt CA | 90 days | Yes (systemd timer) |
24
+ | Self-signed TLS | IP-based panel (`https://IP:9292`) | Lamaste installer | 10 years | No (long validity) |
25
+ | mTLS CA | nginx client cert verification | Lamaste installer | 10 years | No (long validity) |
26
+ | mTLS client | Browser authentication | Lamaste CA | 2 years | Manual rotation |
27
+ | Agent cert | Agent-to-panel mTLS (`CN=agent:<label>`) | Lamaste CA | 2 years | Manual rotation |
28
+ | Plugin-agent cert | Delegated plugin agent mTLS (`CN=plugin-agent:<delegator>:<name>`) | Lamaste CA | 2 years | Manual rotation |
29
+
30
+ ### When certificates are issued
31
+
32
+ - **Self-signed + mTLS certs** — created during `npx @lamalibre/create-lamaste@4.0.0` installation
33
+ - **Core Let's Encrypt certs** — issued during onboarding for `panel`, `auth`, and `tunnel` subdomains
34
+ - **App Let's Encrypt certs** — issued when you create a new tunnel or a managed static site
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)
36
+
37
+ ### Viewing certificates
38
+
39
+ The Certificates page in the management panel shows all certificates with:
40
+
41
+ - Domain name
42
+ - Issuer (Let's Encrypt or self-signed)
43
+ - Expiry date
44
+ - Days remaining
45
+ - Status (valid, expiring soon, expired)
46
+
47
+ ### Certificate renewal
48
+
49
+ Let's Encrypt certificates are automatically renewed by a systemd timer (`certbot.timer`). The timer runs twice daily and renews any certificate expiring within 30 days.
50
+
51
+ You can also manually trigger renewal from the Certificates page:
52
+
53
+ 1. Find the certificate in the list
54
+ 2. Click "Renew"
55
+ 3. Lamaste runs `certbot renew --force-renewal` for that certificate — without the random delay of up to about 8 minutes certbot otherwise adds before a non-interactive renewal (that delay is meant for `certbot.timer`, not for an operator waiting on the page)
56
+ 4. nginx reloads to pick up the new certificate
57
+
58
+ ### mTLS client certificate rotation
59
+
60
+ Your admin client certificate expires after 2 years. The Certificates page shows when it expires. To rotate it:
61
+
62
+ 1. On the server, run `sudo lamaste-server reset-admin` (the panel runs unprivileged and does not issue admin certificates)
63
+ 2. Lamaste generates a new key pair and certificate, signed by the same CA, and revokes the old one
64
+ 3. A new `.p12` file is generated with a new random password, which the command prints
65
+ 4. Download the new `.p12` file (Certificates page, or `scp`) and import it into your browser
66
+ 5. Remove the old certificate from your browser/keychain
67
+
68
+ **Important:** after rotation, the old certificate is immediately invalid.
69
+
70
+ ### What happens when certificates expire
71
+
72
+ | Certificate | If it expires | Impact | Recovery |
73
+ | --------------- | -------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------- |
74
+ | Let's Encrypt | Browsers show security warning | Visitors see "not secure" for that domain | Renew via panel or `certbot renew` |
75
+ | Self-signed TLS | Browser warning changes | Minimal — browsers already show a warning for self-signed certs | Re-run installer |
76
+ | mTLS CA | Client certs can no longer be verified | Admin panel becomes inaccessible | Re-provision PKI (requires SSH) |
77
+ | mTLS client | nginx rejects the certificate | Admin panel inaccessible from that browser | Rotate from panel (if another device still works) |
78
+
79
+ The most critical scenario is the mTLS CA expiring, but it has a 10-year validity, making this a distant concern.
80
+
81
+ ## For Developers
82
+
83
+ ### Let's Encrypt implementation
84
+
85
+ Lamaste uses certbot with the nginx plugin for certificate issuance. The implementation lives in `@lamalibre/lamaste@4.0.0/server` (`packages/core/lib/src/server/certbot.ts`), wrapped by `packages/server/daemon/src/lib/certbot.js`.
86
+
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
+
89
+ | Wrapper | Commands |
90
+ | ----------------------------------- | ---------------------------------------------------------------------------------------------- |
91
+ | `/usr/local/sbin/lamaste-certbot` | `issue <email> <cert-name> <domain>...`, `renew <cert-name> [force]`, `renew-all`, `list` |
92
+ | `/usr/local/sbin/lamaste-cert-info` | `<lineage> enddate` (expiry), `<lineage> checkend` (valid 24 h more?), `<lineage> san` (names) |
93
+
94
+ Each wrapper validates every argument — lowercase DNS hostnames only (no wildcards, no path characters, at most 100 per certificate), a syntactically valid email — and executes certbot or openssl with a fixed argument vector. They replace sudoers rules such as `certbot renew --cert-name * --non-interactive` and `openssl x509 -enddate -noout -in /etc/letsencrypt/live/*`: a sudoers `*` also matches spaces, so those rules accepted extra flags like `--deploy-hook <command>` or `-engine <library>` — root code execution for the `lamaste` user. The wrappers ship in `packages/provisioners/server/scripts/` and are installed (`root:root`, 0755) by the installer and by every redeploy.
95
+
96
+ The mTLS PKI needs no wrapper. `/etc/lamalibre/lamaste/pki` belongs to the `lamaste` user — the panel is the CA — so agent certificates and CSRs (enrollment, hardware-bound rotation) are signed by the panel itself with `openssl x509 -req` (`packages/server/daemon/src/lib/pki-sign.js`). `signAgentCsr` refuses any CN that is not an `agent:<label>` or `plugin-agent:<label>` label, and the callers check the CN is exactly the label they expect; the panel never issues `CN=admin`. Admin certificates come only from `sudo lamaste-server reset-admin` on the server. (Earlier versions signed through a root wrapper, `lamaste-sign-csr`, and renamed PKI files through `lamaste-pki-rename`; both are removed on upgrade.)
97
+
98
+ #### Issuing a certificate
99
+
100
+ ```javascript
101
+ export async function issueCert(fqdn, email, exec) {
102
+ try {
103
+ // lamaste-certbot runs: certbot certonly --nginx --cert-name <fqdn> -d <fqdn>
104
+ // --email <email> --agree-tos --non-interactive
105
+ await exec('sudo', ['/usr/local/sbin/lamaste-certbot', 'issue', email, fqdn, fqdn]);
106
+ } catch (err) {
107
+ const stderr = err.stderr || err.message;
108
+
109
+ if (stderr.includes('too many certificates') || stderr.includes('rate limit')) {
110
+ throw new Error(`Let's Encrypt rate limit reached for ${fqdn}...`);
111
+ }
112
+
113
+ if (stderr.includes('DNS problem') || stderr.includes('NXDOMAIN')) {
114
+ throw new Error(`DNS is not pointing ${fqdn} to this server...`);
115
+ }
116
+
117
+ throw new Error(`Failed to issue certificate for ${fqdn}: ${stderr}`);
118
+ }
119
+
120
+ return {
121
+ issued: true,
122
+ domain: fqdn,
123
+ certPath: `/etc/letsencrypt/live/${fqdn}/fullchain.pem`,
124
+ keyPath: `/etc/letsencrypt/live/${fqdn}/privkey.pem`,
125
+ };
126
+ }
127
+ ```
128
+
129
+ The error handling distinguishes between rate limits, DNS problems, and nginx configuration issues, providing specific guidance for each failure mode.
130
+
131
+ #### HTTP-01 challenge
132
+
133
+ The `--nginx` plugin uses the HTTP-01 challenge method:
134
+
135
+ ```
136
+ 1. Certbot creates a challenge response for /.well-known/acme-challenge/<token>
137
+ 2. Certbot temporarily adds a port-80 server block for the name to nginx
138
+ (cloned from the lamalibre-lamaste-http-redirect default server)
139
+ 3. Let's Encrypt's servers request http://fqdn/.well-known/acme-challenge/<token>
140
+ 4. If the response matches, the domain is verified
141
+ 5. Let's Encrypt issues the certificate
142
+ 6. Certbot restores the nginx configuration
143
+ ```
144
+
145
+ This requires that the FQDN resolves to the VPS IP and that port 80 is reachable from the internet — for the first issuance and for every renewal. The installer's port-80 catch-all, which redirects every other plain-HTTP request to HTTPS, does not interfere: certbot answers the challenge path ahead of the redirect and restores the file afterwards. See [DNS and Domains](dns-and-domains.md) for DNS configuration details.
146
+
147
+ #### Core certificate issuance
148
+
149
+ During onboarding, certificates are issued sequentially for the three core subdomains:
150
+
151
+ ```javascript
152
+ export async function issueCoreCerts(domain, email) {
153
+ const subdomains = ['panel', 'auth', 'tunnel'];
154
+ const results = [];
155
+
156
+ for (const sub of subdomains) {
157
+ const fqdn = `${sub}.${domain}`;
158
+ const result = await issueCert(fqdn, email);
159
+ results.push(result);
160
+ }
161
+
162
+ return results;
163
+ }
164
+ ```
165
+
166
+ Sequential issuance avoids triggering Let's Encrypt rate limits from parallel requests.
167
+
168
+ #### Tunnel certificate issuance
169
+
170
+ When creating a tunnel, the system checks for existing coverage before issuing a new certificate:
171
+
172
+ ```javascript
173
+ export async function issueTunnelCert(fqdn, email) {
174
+ // 1. Check for wildcard cert
175
+ const baseDomain = fqdn.split('.').slice(1).join('.');
176
+ if (await hasWildcardCert(baseDomain)) {
177
+ return { skipped: true, reason: 'wildcard', certPath: `.../${baseDomain}/` };
178
+ }
179
+
180
+ // 2. Check for existing valid individual cert
181
+ const existing = await isCertValid(fqdn);
182
+ if (existing.valid) {
183
+ return { skipped: true, reason: 'exists', certPath: `.../${fqdn}/` };
184
+ }
185
+
186
+ // 3. Issue new certificate
187
+ await issueCert(fqdn, email);
188
+ return { skipped: false, certPath: `.../${fqdn}/` };
189
+ }
190
+ ```
191
+
192
+ This three-step check prevents unnecessary certificate issuance and respects rate limits.
193
+
194
+ #### Custom-domain site certificate issuance
195
+
196
+ A custom-domain static site may have redirect aliases (e.g. `www.myblog.net`). `issueSiteCert(fqdn, aliases, email)` in `@lamalibre/lamaste@4.0.0/server` keeps them on **one** lineage named after the primary domain:
197
+
198
+ ```bash
199
+ sudo /usr/local/sbin/lamaste-certbot issue admin@example.com myblog.net myblog.net www.myblog.net
200
+ # runs: certbot certonly --nginx --cert-name myblog.net -d myblog.net -d www.myblog.net \
201
+ # --email admin@example.com --agree-tos --non-interactive
202
+ ```
203
+
204
+ `--cert-name` pins the lineage directory to `/etc/letsencrypt/live/myblog.net/`, so re-issuing for a changed alias set never drifts to `myblog.net-0001`. Before issuing, it reads the current certificate's names (`lamaste-cert-info myblog.net san`) and skips issuance when the certificate already covers every name — or, with `{ match: 'exact' }`, when it holds exactly those names.
205
+
206
+ When the aliases of a live site change, the certificate is first issued for the **union** of the old and new names (valid whichever vhost nginx serves during the switch), the vhost is rewritten and the site saved, and then the certificate is re-issued for **exactly** the new names (`match: 'exact'`), so a renewal never has to validate a name the site no longer serves. If that last step fails, `PATCH /api/sites/:id` still succeeds and returns a `warning` — see the [Sites API](../04-api-reference/sites.md).
207
+
208
+ #### Input validation
209
+
210
+ The `issueTunnelCert` function validates both the FQDN and email before proceeding:
211
+
212
+ ```javascript
213
+ if (!/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/.test(fqdn)) {
214
+ throw new Error(`Invalid FQDN: ${fqdn}`);
215
+ }
216
+
217
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
218
+ throw new Error(`Invalid email: ${email}`);
219
+ }
220
+ ```
221
+
222
+ These checks prevent command injection through the certbot CLI arguments. The `lamaste-certbot` wrapper repeats them as root, so a compromised panel process cannot pass anything else either.
223
+
224
+ ### Certificate validity checks
225
+
226
+ The `isCertValid` function uses OpenSSL to check if a certificate exists and is valid for at least 24 more hours:
227
+
228
+ ```javascript
229
+ export async function isCertValid(fqdn, exec) {
230
+ const certPath = `/etc/letsencrypt/live/${fqdn}/fullchain.pem`;
231
+ if (!HOSTNAME_RE.test(fqdn)) {
232
+ return { valid: false, certPath: null, expiryDate: null };
233
+ }
234
+
235
+ try {
236
+ // lamaste-cert-info runs: openssl x509 -checkend 86400 -noout -in <lineage>/fullchain.pem
237
+ await exec('sudo', ['/usr/local/sbin/lamaste-cert-info', fqdn, 'checkend']);
238
+ } catch (err) {
239
+ // Exit 1: the certificate exists but expires within a day.
240
+ // Anything else (3: no certificate, 2: refused) means there is none to use.
241
+ if (err.exitCode === 1) {
242
+ return { valid: false, certPath, expiryDate: null };
243
+ }
244
+ return { valid: false, certPath: null, expiryDate: null };
245
+ }
246
+
247
+ const expiry = await readLetsEncryptExpiry(fqdn, exec); // lamaste-cert-info <fqdn> enddate
248
+ return { valid: true, certPath, expiryDate: expiry?.expiresAt ?? null };
249
+ }
250
+ ```
251
+
252
+ `openssl x509 -checkend 86400` exits with code 0 if the certificate is valid for at least 86400 seconds (24 hours) and code 1 otherwise; the wrapper passes that through and uses exit code 3 for a lineage with no certificate. This provides a simple binary check without parsing dates.
253
+
254
+ ### Listing certificates
255
+
256
+ The `listCerts` function parses the output of `certbot certificates` (`lamaste-certbot list`) to build a structured list:
257
+
258
+ ```javascript
259
+ export async function listCerts(exec) {
260
+ const { stdout } = await exec('sudo', ['/usr/local/sbin/lamaste-certbot', 'list']);
261
+
262
+ if (stdout.includes('No certificates found')) return [];
263
+
264
+ const blocks = stdout.split('Certificate Name:').slice(1);
265
+ return blocks.map((block) => {
266
+ // Parse: name, domains, expiry, cert path, key path, validity
267
+ // ...
268
+ return { name, domains, expiryDate, daysRemaining, certPath, keyPath, isValid };
269
+ });
270
+ }
271
+ ```
272
+
273
+ This list is combined with mTLS certificate information from `packages/lamaste-serverd/src/lib/mtls.js` to present a unified certificate view in the panel.
274
+
275
+ ### Auto-renewal setup
276
+
277
+ During onboarding provisioning, the certbot systemd timer is enabled:
278
+
279
+ ```javascript
280
+ export async function setupAutoRenew() {
281
+ await execa('sudo', ['systemctl', 'enable', 'certbot.timer']);
282
+ await execa('sudo', ['systemctl', 'start', 'certbot.timer']);
283
+
284
+ const { stdout } = await execa('systemctl', ['is-active', 'certbot.timer']);
285
+ if (stdout.trim() !== 'active') {
286
+ throw new Error('Certbot timer is not active after enabling.');
287
+ }
288
+ return { enabled: true };
289
+ }
290
+ ```
291
+
292
+ The certbot timer runs twice daily (standard certbot behavior). It only renews certificates that expire within 30 days, so most runs are no-ops.
293
+
294
+ After renewal, certbot automatically reloads nginx via a deploy hook, ensuring the new certificate is served immediately.
295
+
296
+ ### Self-signed certificate for IP access
297
+
298
+ The installer generates a self-signed certificate during nginx setup:
299
+
300
+ ```bash
301
+ openssl req -x509 -nodes \
302
+ -days 3650 \
303
+ -newkey rsa:2048 \
304
+ -keyout /etc/lamalibre/lamaste/pki/self-signed-key.pem \
305
+ -out /etc/lamalibre/lamaste/pki/self-signed.pem \
306
+ -subj "/CN=203.0.113.42/O=Lamaste" \
307
+ -addext "subjectAltName=IP:203.0.113.42"
308
+ ```
309
+
310
+ The `-addext subjectAltName=IP:...` flag embeds the IP address in the Subject Alternative Name field. Without this, modern browsers reject the certificate even for IP-based access.
311
+
312
+ The self-signed certificate uses a 2048-bit key (smaller than the 4096-bit mTLS keys) because it serves a less critical role — it is only for IP-based access where the browser already shows a warning.
313
+
314
+ ### Certificate file layout
315
+
316
+ ```
317
+ /etc/letsencrypt/live/
318
+ ├── panel.example.com/
319
+ │ ├── fullchain.pem Let's Encrypt cert chain
320
+ │ ├── privkey.pem Private key
321
+ │ ├── cert.pem Server certificate only
322
+ │ └── chain.pem Intermediate CA chain
323
+ ├── auth.example.com/
324
+ │ └── (same structure)
325
+ ├── tunnel.example.com/
326
+ │ └── (same structure)
327
+ └── myapp.example.com/
328
+ └── (same structure)
329
+
330
+ /etc/lamalibre/lamaste/pki/
331
+ ├── self-signed.pem Self-signed TLS cert (IP access)
332
+ ├── self-signed-key.pem Self-signed TLS key
333
+ ├── ca.crt mTLS CA certificate
334
+ ├── ca.key mTLS CA private key
335
+ ├── client.crt mTLS client certificate
336
+ ├── client.key mTLS client private key
337
+ ├── client.p12 PKCS12 bundle for browser import
338
+ ├── .p12-password Password for the .p12 file
339
+ ├── revoked.json Revoked certificate serials
340
+ ├── enrollment-tokens.json Pending hardware-bound enrollment tokens
341
+ └── agents/
342
+ ├── registry.json Agent certificate registry
343
+ └── <label>/
344
+ └── client.p12 Agent PKCS12 bundle
345
+ ```
346
+
347
+ ### mTLS certificate expiry monitoring
348
+
349
+ The panel server reads mTLS certificate expiry dates using OpenSSL:
350
+
351
+ ```javascript
352
+ export async function readCertExpiry(certPath) {
353
+ // No sudo: the PKI directory belongs to the panel's user.
354
+ const { stdout } = await execa('openssl', ['x509', '-in', certPath, '-enddate', '-noout']);
355
+ const match = stdout.match(/notAfter=(.+)/);
356
+ const expiryDate = new Date(match[1]);
357
+ const daysUntilExpiry = Math.floor((expiryDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
358
+ return { expiresAt: expiryDate.toISOString(), daysUntilExpiry };
359
+ }
360
+ ```
361
+
362
+ Certificates with 30 or fewer days remaining are flagged as `expiringSoon` in the API response.
363
+
364
+ ### Source files
365
+
366
+ | File | Purpose |
367
+ | --------------------------------------------------------- | ------------------------------------------------------- |
368
+ | `packages/lamaste-serverd/src/lib/certbot.js` | Let's Encrypt issuance, renewal, listing |
369
+ | `packages/lamaste-serverd/src/lib/mtls.js` | mTLS cert expiry, rotation, download |
370
+ | `packages/lamaste-serverd/src/routes/management/certs.js` | Certificate management API |
371
+ | `packages/create-lamaste/src/tasks/nginx.js` | Self-signed cert generation |
372
+ | `packages/create-lamaste/src/tasks/mtls.js` | mTLS CA and client cert generation |
373
+ | `packages/lamaste-serverd/src/lib/csr-signing.js` | CSR signing for hardware-bound and delegated enrollment |
374
+ | `packages/lamaste-serverd/src/lib/enrollment.js` | Enrollment token management (regular and delegated) |
375
+ | `packages/lamaste-serverd/src/routes/enrollment.js` | Public enrollment route |
376
+
377
+ ## Quick Reference
378
+
379
+ ### Certificate types
380
+
381
+ | Type | Location | Issued by | Validity | Key size |
382
+ | --------------- | -------------------------------------------- | ---------------- | -------- | ------------ |
383
+ | Let's Encrypt | `/etc/letsencrypt/live/<fqdn>/` | Let's Encrypt CA | 90 days | 2048-bit RSA |
384
+ | Self-signed TLS | `/etc/lamalibre/lamaste/pki/self-signed.pem` | Self | 10 years | 2048-bit RSA |
385
+ | mTLS CA | `/etc/lamalibre/lamaste/pki/ca.crt` | Self | 10 years | 4096-bit RSA |
386
+ | mTLS client | `/etc/lamalibre/lamaste/pki/client.crt` | Lamaste CA | 2 years | 4096-bit RSA |
387
+
388
+ ### certbot commands
389
+
390
+ ```bash
391
+ # List all certificates
392
+ sudo certbot certificates
393
+
394
+ # Renew a specific certificate
395
+ sudo certbot renew --cert-name panel.example.com
396
+
397
+ # Renew all certificates due for renewal
398
+ sudo certbot renew
399
+
400
+ # Issue a new certificate
401
+ sudo certbot certonly --nginx -d myapp.example.com --email admin@example.com --agree-tos --non-interactive
402
+
403
+ # Check auto-renewal timer
404
+ systemctl status certbot.timer
405
+ ```
406
+
407
+ ### OpenSSL commands
408
+
409
+ ```bash
410
+ # View certificate details
411
+ openssl x509 -in /etc/letsencrypt/live/panel.example.com/fullchain.pem -text -noout
412
+
413
+ # Check certificate expiry date
414
+ openssl x509 -in /etc/letsencrypt/live/panel.example.com/fullchain.pem -enddate -noout
415
+
416
+ # Check if cert is valid for 30+ days
417
+ openssl x509 -checkend 2592000 -noout -in /path/to/cert.pem
418
+
419
+ # Verify certificate chain
420
+ openssl verify -CAfile /etc/letsencrypt/live/panel.example.com/chain.pem \
421
+ /etc/letsencrypt/live/panel.example.com/cert.pem
422
+ ```
423
+
424
+ ### API endpoints
425
+
426
+ | Method | Path | Description |
427
+ | ------ | -------------------------------------------- | -------------------------------------------- |
428
+ | GET | `/api/certs` | List all certificates (Let's Encrypt + mTLS) |
429
+ | POST | `/api/certs/:domain/renew` | Force renewal of a specific certificate |
430
+ | POST | `/api/certs/mtls/rotate` | Rotate the mTLS client certificate |
431
+ | GET | `/api/certs/mtls/download` | Download the current `.p12` bundle |
432
+ | POST | `/api/certs/agent/enroll` | Generate enrollment token (admin) |
433
+ | POST | `/api/certs/agent/enroll-delegated` | Delegated enrollment token (agent) |
434
+ | POST | `/api/enroll` | Enroll agent with token (public) |
435
+ | POST | `/api/certs/admin/upgrade-to-hardware-bound` | Upgrade admin auth (admin) |
436
+ | GET | `/api/certs/admin/auth-mode` | Get admin auth mode (admin) |
437
+
438
+ ### Let's Encrypt rate limits
439
+
440
+ | Limit | Value | Notes |
441
+ | ---------------------------------- | ------------------- | --------------------------------------- |
442
+ | Certificates per registered domain | 50/week | Covers all subdomains of `example.com` |
443
+ | Duplicate certificates | 5/week | Same set of domains |
444
+ | Failed validations | 5/hour | Per account per hostname |
445
+ | Certificate renewals | Exempt from 50/week | Renewals do not count against the limit |
446
+
447
+ ### Related documentation
448
+
449
+ - [mTLS](mtls.md) — client certificate details and PKCS12 bundles
450
+ - [DNS and Domains](dns-and-domains.md) — DNS must be correct for Let's Encrypt
451
+ - [nginx Reverse Proxy](nginx-reverse-proxy.md) — nginx uses these certificates
452
+ - [Security Model](security-model.md) — TLS as part of defense-in-depth