Operational Feature: Web UI for TLS certificate management
Problem:
- Server certificate (nginx HTTPS) managed manually via certs/ directory
- No visibility into cert expiry from UI
- Regeneration required command-line OpenSSL knowledge
- No warning when cert approaching expiration
- Agents using thumbprint pinning need new thumbprint after renewal
Solution:
- New admin-only settings page: Settings > Endpoints > Server Certificate
- View current certificate details (CN, SANs, expiry, thumbprint)
- Warning banner when cert expires within 30 days
- Generate self-signed certificate (configure CN, SANs, validity)
- Upload CA-signed certificate (PEM format)
- Automatic nginx restart after certificate changes
- Copy-to-clipboard for agent thumbprint pinning
Frontend:
- plugins/endpoints/frontend/views/ServerCertSettings.vue
- Certificate status display with expiry countdown
- Self-signed generation form (CN, SANs, days)
- CA certificate upload (cert + key files)
- Documentation collapsibles (requirements, pinning, air-gapped)
- plugins/endpoints/frontend/routes.js
- Route: /settings/endpoints/server-cert (admin-only)
Backend API:
- plugins/endpoints/api/routes.py
- GET /api/endpoints/settings/server-cert (view cert details)
- Uses openssl to extract CN, issuer, dates, thumbprint, SANs
- Calculates days until expiry (warns if < 30)
- Returns self-signed status
- POST /api/endpoints/settings/server-cert/generate
- Generates self-signed cert with custom CN/SANs/validity
- Backs up old certificate with timestamp
- Writes new cert/key to certs/ directory
- Restarts nginx (docker-compose restart nginx)
- Audit logged: endpoints.server_cert.generated
- POST /api/endpoints/settings/server-cert/upload
- Validates PEM format (cert + unencrypted key)
- Backs up old certificate
- Writes uploaded files to certs/
- Restarts nginx automatically
- Audit logged: endpoints.server_cert.uploaded
- All endpoints admin-only (@require_role('admin'))
Navigation:
- plugins/endpoints/plugin.py
- Added menu item: Settings > Endpoints > Server Certificate
- Icon: shield-check, Position: 29
Security:
- Admin-only access (certificate management is privileged operation)
- Private key validation (rejects encrypted keys)
- Certificate format validation (PEM-encoded)
- Audit logging for all cert operations
- Automatic backup before replacement (timestamped .backup files)
Operational Notes:
- Certificate changes restart nginx (brief HTTPS interruption)
- Agents using ServerCertThumbprint pinning must be updated after renewal
- Self-signed certs valid 1 year by default (configurable)
- Air-gapped sites: cert files in certs/ directory bundled automatically
Related:
- Complements certs/README.md and regeneration scripts (commit fd6240772e)
- Agents validate via Windows roots or thumbprint pinning (CertificateRenewal.cs)
- Client certificate renewal uses same nginx TLS surface (routes.py:3455)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
53 lines
1.9 KiB
JavaScript
53 lines
1.9 KiB
JavaScript
/**
|
|
* Endpoints plugin routes
|
|
*
|
|
* Groundwork for the fleet view. There is no endpoint-specific detail route
|
|
* yet: rows link into the existing PC detail page (/pcs/:id) so telemetry
|
|
* stays interoperable with the computers plugin instead of duplicating it.
|
|
*/
|
|
export default [
|
|
{
|
|
path: 'endpoints',
|
|
name: 'endpoints',
|
|
component: () => import('./views/EndpointsList.vue'),
|
|
meta: { plugin: 'endpoints' }
|
|
},
|
|
{
|
|
path: 'settings/endpoints/agentkey',
|
|
name: 'endpoints-agentkey-settings',
|
|
component: () => import('./views/AgentKeySettings.vue'),
|
|
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'endpoints' }
|
|
},
|
|
{
|
|
path: 'settings/endpoints/monitors',
|
|
name: 'endpoints-monitors-settings',
|
|
component: () => import('./views/MonitorsSettings.vue'),
|
|
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'endpoints' }
|
|
},
|
|
{
|
|
path: 'settings/endpoints/server-cert',
|
|
name: 'endpoints-server-cert-settings',
|
|
component: () => import('./views/ServerCertSettings.vue'),
|
|
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'endpoints' }
|
|
},
|
|
// requiresPermission, not requiresAdmin: endpoints.runscript is grantable to
|
|
// non-admin operators, and the sibling pages' admin gate would lock the
|
|
// holders of the grant out of the library they are trusted to manage. The
|
|
// API enforces the same permission, so this only keeps the menu honest.
|
|
{
|
|
path: 'settings/endpoints/scripts',
|
|
name: 'endpoints-scripts-settings',
|
|
component: () => import('./views/SystemScriptsSettings.vue'),
|
|
meta: {
|
|
requiresAuth: true,
|
|
requiresPermission: 'endpoints.runscript',
|
|
plugin: 'endpoints'
|
|
}
|
|
}
|
|
]
|
|
|
|
// No full-screen routes: every view in this plugin renders inside
|
|
// AppLayout. Declared so routes.gen.js can read `.toplevel` off the module
|
|
// without Rollup warning about a missing export.
|
|
export const toplevel = []
|