@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,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
|