From ae4cc8b420d7e2d9fdc910fbf94e79e60246af3a Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Sat, 28 Mar 2026 19:02:15 -0700 Subject: [PATCH] Fix web UI download links for CA certs, SSH CA pubkey, and KRL Templates linked to /v1/ API server routes which don't exist on the web server (separate binary). Add web server handlers that fetch data via gRPC and serve the downloads directly. Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/webserver/routes.go | 22 +++++++++++++++++++++ internal/webserver/sshca.go | 38 ++++++++++++++++++++++++++++++++++++ web/templates/pki.html | 2 +- web/templates/sshca.html | 4 ++-- 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/internal/webserver/routes.go b/internal/webserver/routes.go index a0d31f0..4977243 100644 --- a/internal/webserver/routes.go +++ b/internal/webserver/routes.go @@ -50,6 +50,8 @@ func (ws *WebServer) registerRoutes(r chi.Router) { r.Route("/sshca", func(r chi.Router) { r.Get("/", ws.requireAuth(ws.handleSSHCA)) + r.Get("/ca", ws.requireAuth(ws.handleSSHCADownload)) + r.Get("/krl", ws.requireAuth(ws.handleSSHCAKRLDownload)) r.Post("/sign-user", ws.requireAuth(ws.handleSSHCASignUser)) r.Post("/sign-host", ws.requireAuth(ws.handleSSHCASignHost)) r.Get("/cert/{serial}", ws.requireAuth(ws.handleSSHCACertDetail)) @@ -91,6 +93,7 @@ func (ws *WebServer) registerRoutes(r chi.Router) { r.Route("/pki", func(r chi.Router) { r.Get("/", ws.requireAuth(ws.handlePKI)) + r.Get("/ca", ws.requireAuth(ws.handlePKIRootCA)) r.Post("/import-root", ws.requireAuth(ws.handleImportRoot)) r.Post("/create-issuer", ws.requireAuth(ws.handleCreateIssuer)) r.Post("/issue", ws.requireAuth(ws.handleIssueCert)) @@ -475,6 +478,25 @@ func (ws *WebServer) handleCreateIssuer(w http.ResponseWriter, r *http.Request) http.Redirect(w, r, "/pki", http.StatusFound) } +func (ws *WebServer) handlePKIRootCA(w http.ResponseWriter, r *http.Request) { + token := extractCookie(r) + mountName, err := ws.findCAMount(r, token) + if err != nil { + http.Error(w, "no CA engine mounted", http.StatusNotFound) + return + } + + certPEM, err := ws.vault.GetRootCert(r.Context(), mountName) + if err != nil || len(certPEM) == 0 { + http.Error(w, "root CA not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/x-pem-file") + w.Header().Set("Content-Disposition", "attachment; filename=root-ca.pem") + _, _ = w.Write(certPEM) //nolint:gosec +} + func (ws *WebServer) handlePKIIssuer(w http.ResponseWriter, r *http.Request) { token := extractCookie(r) mountName, err := ws.findCAMount(r, token) diff --git a/internal/webserver/sshca.go b/internal/webserver/sshca.go index c9065b9..b7e9ab4 100644 --- a/internal/webserver/sshca.go +++ b/internal/webserver/sshca.go @@ -40,6 +40,44 @@ func (ws *WebServer) handleSSHCA(w http.ResponseWriter, r *http.Request) { ws.renderTemplate(w, "sshca.html", data) } +func (ws *WebServer) handleSSHCADownload(w http.ResponseWriter, r *http.Request) { + token := extractCookie(r) + mountName, err := ws.findSSHCAMount(r, token) + if err != nil { + http.Error(w, "no SSH CA engine mounted", http.StatusNotFound) + return + } + + pubkey, err := ws.vault.GetSSHCAPublicKey(r.Context(), mountName) + if err != nil || pubkey == nil { + http.Error(w, "CA public key not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "text/plain") + w.Header().Set("Content-Disposition", "attachment; filename=ca.pub") + _, _ = w.Write([]byte(pubkey.PublicKey)) //nolint:gosec +} + +func (ws *WebServer) handleSSHCAKRLDownload(w http.ResponseWriter, r *http.Request) { + token := extractCookie(r) + mountName, err := ws.findSSHCAMount(r, token) + if err != nil { + http.Error(w, "no SSH CA engine mounted", http.StatusNotFound) + return + } + + krl, err := ws.vault.GetSSHCAKRL(r.Context(), mountName) + if err != nil { + http.Error(w, "KRL not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Disposition", "attachment; filename=krl.bin") + _, _ = w.Write(krl) //nolint:gosec +} + func (ws *WebServer) handleSSHCASignUser(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) diff --git a/web/templates/pki.html b/web/templates/pki.html index ebce4ac..9755d93 100644 --- a/web/templates/pki.html +++ b/web/templates/pki.html @@ -27,7 +27,7 @@

- Download Root CA (PEM) + Download Root CA (PEM)

{{else}}

No root CA configured.

diff --git a/web/templates/sshca.html b/web/templates/sshca.html index a08941c..9e60886 100644 --- a/web/templates/sshca.html +++ b/web/templates/sshca.html @@ -14,7 +14,7 @@ {{if .CAPublicKey}}

- Download CA Public Key + Download CA Public Key

{{else}}

CA public key not available.

@@ -210,7 +210,7 @@ {{if .IsAdmin}}
Key Revocation List
-

Download KRL

+

Download KRL

{{end}} {{end}}