package webserver import ( "net/http" "strconv" "strings" "github.com/go-chi/chi/v5" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) func (ws *WebServer) handleTransit(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) mountName, err := ws.findTransitMount(r, token) if err != nil { http.Redirect(w, r, "/dashboard", http.StatusFound) return } data := ws.baseData(r, info) data["MountName"] = mountName if keys, err := ws.vault.ListTransitKeys(r.Context(), token, mountName); err == nil { data["Keys"] = keys } ws.renderTemplate(w, "transit.html", data) } func (ws *WebServer) handleTransitKeyDetail(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) mountName, err := ws.findTransitMount(r, token) if err != nil { http.Error(w, "no transit engine mounted", http.StatusNotFound) return } name := chi.URLParam(r, "name") key, err := ws.vault.GetTransitKey(r.Context(), token, mountName, name) if err != nil { st, _ := status.FromError(err) if st.Code() == codes.NotFound { http.Error(w, "key not found", http.StatusNotFound) return } http.Error(w, grpcMessage(err), http.StatusInternalServerError) return } data := ws.baseData(r, info) data["MountName"] = mountName data["Key"] = key // Fetch public key for asymmetric signing keys. switch key.Type { case "ed25519", "ecdsa-p256", "ecdsa-p384": if pubkey, err := ws.vault.GetTransitPublicKey(r.Context(), token, mountName, name); err == nil { data["PublicKey"] = pubkey } } ws.renderTemplate(w, "transit_key_detail.html", data) } func (ws *WebServer) handleTransitCreateKey(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) if !info.IsAdmin { http.Error(w, "forbidden", http.StatusForbidden) return } token := extractCookie(r) mountName, err := ws.findTransitMount(r, token) if err != nil { http.Error(w, "no transit engine mounted", http.StatusNotFound) return } r.Body = http.MaxBytesReader(w, r.Body, 1<<20) _ = r.ParseForm() name := strings.TrimSpace(r.FormValue("name")) if name == "" { ws.renderTransitWithError(w, r, mountName, info, "Key name is required") return } keyType := r.FormValue("type") if keyType == "" { keyType = "aes256-gcm" } if err := ws.vault.CreateTransitKey(r.Context(), token, mountName, name, keyType); err != nil { ws.renderTransitWithError(w, r, mountName, info, grpcMessage(err)) return } http.Redirect(w, r, "/transit/key/"+name, http.StatusFound) } func (ws *WebServer) handleTransitRotateKey(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) if !info.IsAdmin { http.Error(w, "forbidden", http.StatusForbidden) return } token := extractCookie(r) mountName, err := ws.findTransitMount(r, token) if err != nil { http.Error(w, "no transit engine mounted", http.StatusNotFound) return } name := chi.URLParam(r, "name") if err := ws.vault.RotateTransitKey(r.Context(), token, mountName, name); err != nil { http.Error(w, grpcMessage(err), http.StatusInternalServerError) return } http.Redirect(w, r, "/transit/key/"+name, http.StatusSeeOther) } func (ws *WebServer) handleTransitUpdateConfig(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) if !info.IsAdmin { http.Error(w, "forbidden", http.StatusForbidden) return } token := extractCookie(r) mountName, err := ws.findTransitMount(r, token) if err != nil { http.Error(w, "no transit engine mounted", http.StatusNotFound) return } name := chi.URLParam(r, "name") r.Body = http.MaxBytesReader(w, r.Body, 1<<20) _ = r.ParseForm() minDecrypt := 0 if v := r.FormValue("min_decryption_version"); v != "" { if n, err := strconv.Atoi(v); err == nil { minDecrypt = n } } allowDeletion := r.FormValue("allow_deletion") == "on" if err := ws.vault.UpdateTransitKeyConfig(r.Context(), token, mountName, name, minDecrypt, allowDeletion); err != nil { http.Error(w, grpcMessage(err), http.StatusInternalServerError) return } http.Redirect(w, r, "/transit/key/"+name, http.StatusSeeOther) } func (ws *WebServer) handleTransitTrimKey(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) if !info.IsAdmin { http.Error(w, "forbidden", http.StatusForbidden) return } token := extractCookie(r) mountName, err := ws.findTransitMount(r, token) if err != nil { http.Error(w, "no transit engine mounted", http.StatusNotFound) return } name := chi.URLParam(r, "name") if _, err := ws.vault.TrimTransitKey(r.Context(), token, mountName, name); err != nil { http.Error(w, grpcMessage(err), http.StatusInternalServerError) return } http.Redirect(w, r, "/transit/key/"+name, http.StatusSeeOther) } func (ws *WebServer) handleTransitDeleteKey(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) if !info.IsAdmin { http.Error(w, "forbidden", http.StatusForbidden) return } token := extractCookie(r) mountName, err := ws.findTransitMount(r, token) if err != nil { http.Error(w, "no transit engine mounted", http.StatusNotFound) return } name := chi.URLParam(r, "name") if err := ws.vault.DeleteTransitKey(r.Context(), token, mountName, name); err != nil { http.Error(w, grpcMessage(err), http.StatusInternalServerError) return } http.Redirect(w, r, "/transit", http.StatusSeeOther) } func (ws *WebServer) handleTransitEncrypt(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) mountName, err := ws.findTransitMount(r, token) if err != nil { http.Error(w, "no transit engine mounted", http.StatusNotFound) return } r.Body = http.MaxBytesReader(w, r.Body, 1<<20) _ = r.ParseForm() key := r.FormValue("key") plaintext := r.FormValue("plaintext") transitCtx := r.FormValue("context") if key == "" || plaintext == "" { ws.renderTransitWithError(w, r, mountName, info, "Key and plaintext are required") return } ciphertext, err := ws.vault.TransitEncrypt(r.Context(), token, mountName, key, plaintext, transitCtx) if err != nil { ws.renderTransitWithError(w, r, mountName, info, grpcMessage(err)) return } ws.renderTransitWithResult(w, r, mountName, info, "EncryptResult", ciphertext) } func (ws *WebServer) handleTransitDecrypt(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) mountName, err := ws.findTransitMount(r, token) if err != nil { http.Error(w, "no transit engine mounted", http.StatusNotFound) return } r.Body = http.MaxBytesReader(w, r.Body, 1<<20) _ = r.ParseForm() key := r.FormValue("key") ciphertext := r.FormValue("ciphertext") transitCtx := r.FormValue("context") if key == "" || ciphertext == "" { ws.renderTransitWithError(w, r, mountName, info, "Key and ciphertext are required") return } plaintext, err := ws.vault.TransitDecrypt(r.Context(), token, mountName, key, ciphertext, transitCtx) if err != nil { ws.renderTransitWithError(w, r, mountName, info, grpcMessage(err)) return } ws.renderTransitWithResult(w, r, mountName, info, "DecryptResult", plaintext) } func (ws *WebServer) handleTransitRewrap(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) mountName, err := ws.findTransitMount(r, token) if err != nil { http.Error(w, "no transit engine mounted", http.StatusNotFound) return } r.Body = http.MaxBytesReader(w, r.Body, 1<<20) _ = r.ParseForm() key := r.FormValue("key") ciphertext := r.FormValue("ciphertext") transitCtx := r.FormValue("context") if key == "" || ciphertext == "" { ws.renderTransitWithError(w, r, mountName, info, "Key and ciphertext are required") return } newCiphertext, err := ws.vault.TransitRewrap(r.Context(), token, mountName, key, ciphertext, transitCtx) if err != nil { ws.renderTransitWithError(w, r, mountName, info, grpcMessage(err)) return } ws.renderTransitWithResult(w, r, mountName, info, "RewrapResult", newCiphertext) } func (ws *WebServer) handleTransitSign(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) mountName, err := ws.findTransitMount(r, token) if err != nil { http.Error(w, "no transit engine mounted", http.StatusNotFound) return } r.Body = http.MaxBytesReader(w, r.Body, 1<<20) _ = r.ParseForm() key := r.FormValue("key") input := r.FormValue("input") if key == "" || input == "" { ws.renderTransitWithError(w, r, mountName, info, "Key and input are required") return } signature, err := ws.vault.TransitSign(r.Context(), token, mountName, key, input) if err != nil { ws.renderTransitWithError(w, r, mountName, info, grpcMessage(err)) return } ws.renderTransitWithResult(w, r, mountName, info, "SignResult", signature) } func (ws *WebServer) handleTransitVerify(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) mountName, err := ws.findTransitMount(r, token) if err != nil { http.Error(w, "no transit engine mounted", http.StatusNotFound) return } r.Body = http.MaxBytesReader(w, r.Body, 1<<20) _ = r.ParseForm() key := r.FormValue("key") input := r.FormValue("input") signature := r.FormValue("signature") if key == "" || input == "" || signature == "" { ws.renderTransitWithError(w, r, mountName, info, "Key, input, and signature are required") return } valid, err := ws.vault.TransitVerify(r.Context(), token, mountName, key, input, signature) if err != nil { ws.renderTransitWithError(w, r, mountName, info, grpcMessage(err)) return } ws.renderTransitWithResult(w, r, mountName, info, "VerifyResult", valid) } func (ws *WebServer) handleTransitHMAC(w http.ResponseWriter, r *http.Request) { info := tokenInfoFromContext(r.Context()) token := extractCookie(r) mountName, err := ws.findTransitMount(r, token) if err != nil { http.Error(w, "no transit engine mounted", http.StatusNotFound) return } r.Body = http.MaxBytesReader(w, r.Body, 1<<20) _ = r.ParseForm() key := r.FormValue("key") input := r.FormValue("input") if key == "" || input == "" { ws.renderTransitWithError(w, r, mountName, info, "Key and input are required") return } hmac, err := ws.vault.TransitHMAC(r.Context(), token, mountName, key, input) if err != nil { ws.renderTransitWithError(w, r, mountName, info, grpcMessage(err)) return } ws.renderTransitWithResult(w, r, mountName, info, "HMACResult", hmac) } func (ws *WebServer) renderTransitWithError(w http.ResponseWriter, r *http.Request, mountName string, info *TokenInfo, errMsg string) { token := extractCookie(r) data := ws.baseData(r, info) data["MountName"] = mountName data["Error"] = errMsg if keys, err := ws.vault.ListTransitKeys(r.Context(), token, mountName); err == nil { data["Keys"] = keys } ws.renderTemplate(w, "transit.html", data) } func (ws *WebServer) renderTransitWithResult(w http.ResponseWriter, r *http.Request, mountName string, info *TokenInfo, resultKey string, result interface{}) { token := extractCookie(r) data := ws.baseData(r, info) data["MountName"] = mountName data[resultKey] = result if keys, err := ws.vault.ListTransitKeys(r.Context(), token, mountName); err == nil { data["Keys"] = keys } ws.renderTemplate(w, "transit.html", data) }