From d2aefba82e209a2f310149daf438fae9888c67f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Cluseau?= Date: Mon, 20 Jul 2026 12:20:47 +0200 Subject: [PATCH] kube CSR: preload UI fields --- cmd/dkl-local-server/ws-clusters.go | 38 +++++++++++++++++++ cmd/dkl-local-server/ws.go | 6 ++- ...c.js => ClusterAccess-1090c2ac32649bb9.js} | 16 ++++++++ html/ui/index.html | 2 +- ui/js/ClusterAccess.js | 16 ++++++++ 5 files changed, 76 insertions(+), 2 deletions(-) rename html/ui/{ClusterAccess-8b4165435ba4fcac.js => ClusterAccess-1090c2ac32649bb9.js} (91%) diff --git a/cmd/dkl-local-server/ws-clusters.go b/cmd/dkl-local-server/ws-clusters.go index a79793e..857c2f5 100644 --- a/cmd/dkl-local-server/ws-clusters.go +++ b/cmd/dkl-local-server/ws-clusters.go @@ -2,6 +2,8 @@ package main import ( "bytes" + "crypto/x509" + "encoding/pem" "errors" "fmt" "log" @@ -355,3 +357,39 @@ func parseCertDuration(d string, now time.Time) (t time.Time, err error) { return } + +type KubeCSRParseReq struct { + CSR string `json:"csr"` +} + +type KubeCSRParseResp struct { + User string `json:"user,omitempty"` + Group string `json:"group,omitempty"` +} + +func wsParseKubeCSR(req *restful.Request, resp *restful.Response) { + var r KubeCSRParseReq + if err := req.ReadEntity(&r); err != nil { + wsError(resp, err) + return + } + + block, _ := pem.Decode([]byte(r.CSR)) + if block == nil || block.Type != "CERTIFICATE REQUEST" { + wsError(resp, fmt.Errorf("invalid CSR PEM")) + return + } + + cr, err := x509.ParseCertificateRequest(block.Bytes) + if err != nil { + wsError(resp, fmt.Errorf("parse CSR: %w", err)) + return + } + + out := KubeCSRParseResp{User: cr.Subject.CommonName} + if len(cr.Subject.Organization) > 0 { + out.Group = cr.Subject.Organization[0] + } + + resp.WriteAsJson(out) +} diff --git a/cmd/dkl-local-server/ws.go b/cmd/dkl-local-server/ws.go index 5be5f71..b54b6e9 100644 --- a/cmd/dkl-local-server/ws.go +++ b/cmd/dkl-local-server/ws.go @@ -42,7 +42,11 @@ func registerWS(rest *restful.Container) { Param(ws.PathParameter("asset", "the requested asset")). Doc("Fetch an asset via a download token")). Route(ws.GET("/download-set").To(wsDownloadSet)). - Route(ws.GET("/download-set/{kind}/{name}/{asset}").To(wsDownloadSetAsset)) + Route(ws.GET("/download-set/{kind}/{name}/{asset}").To(wsDownloadSetAsset)). + Route(ws.POST("/parse-kube-csr").To(wsParseKubeCSR). + Reads(KubeCSRParseReq{}). + Writes(KubeCSRParseResp{}). + Doc("Parse a Kubernetes CSR PEM and return the subject fields")) rest.Add(ws) } diff --git a/html/ui/ClusterAccess-8b4165435ba4fcac.js b/html/ui/ClusterAccess-1090c2ac32649bb9.js similarity index 91% rename from html/ui/ClusterAccess-8b4165435ba4fcac.js rename to html/ui/ClusterAccess-1090c2ac32649bb9.js index f9c8421..b54a74e 100644 --- a/html/ui/ClusterAccess-8b4165435ba4fcac.js +++ b/html/ui/ClusterAccess-1090c2ac32649bb9.js @@ -21,6 +21,22 @@ const ClusterAccess = { msg: null, }; }, + watch: { + 'kubeSignReq.CSR'(v) { + if (v && v.includes('BEGIN CERTIFICATE REQUEST')) { + fetch(`/public/parse-kube-csr`, { + method: 'POST', + body: JSON.stringify({ csr: v }), + headers: { 'Content-Type': 'application/json' }, + }).then(r => { + if (r.ok) r.json().then(d => { + if (d.user) this.kubeSignReq.User = d.user; + if (d.group) this.kubeSignReq.Group = d.group; + }); + }).catch(() => {}); + } + }, + }, computed: { availableAssets() { return [ diff --git a/html/ui/index.html b/html/ui/index.html index 73e4d87..676f890 100644 --- a/html/ui/index.html +++ b/html/ui/index.html @@ -13,7 +13,7 @@ - + diff --git a/ui/js/ClusterAccess.js b/ui/js/ClusterAccess.js index f9c8421..b54a74e 100644 --- a/ui/js/ClusterAccess.js +++ b/ui/js/ClusterAccess.js @@ -21,6 +21,22 @@ const ClusterAccess = { msg: null, }; }, + watch: { + 'kubeSignReq.CSR'(v) { + if (v && v.includes('BEGIN CERTIFICATE REQUEST')) { + fetch(`/public/parse-kube-csr`, { + method: 'POST', + body: JSON.stringify({ csr: v }), + headers: { 'Content-Type': 'application/json' }, + }).then(r => { + if (r.ok) r.json().then(d => { + if (d.user) this.kubeSignReq.User = d.user; + if (d.group) this.kubeSignReq.Group = d.group; + }); + }).catch(() => {}); + } + }, + }, computed: { availableAssets() { return [