From 4d1deeb773bd4d06ff4645293f8fcced234f76a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Cluseau?= Date: Fri, 17 Jul 2026 13:24:41 +0200 Subject: [PATCH] ca.crt restore endpoint --- cmd/dkl-local-server/secrets.go | 9 ++- cmd/dkl-local-server/tls-ca.go | 7 +- cmd/dkl-local-server/ws-cluster-cas.go | 93 +++++++++++++++++++++----- cmd/dkl-local-server/ws.go | 3 + 4 files changed, 94 insertions(+), 18 deletions(-) diff --git a/cmd/dkl-local-server/secrets.go b/cmd/dkl-local-server/secrets.go index c868a21..958da95 100644 --- a/cmd/dkl-local-server/secrets.go +++ b/cmd/dkl-local-server/secrets.go @@ -2,13 +2,14 @@ package main import ( "crypto/ed25519" + "crypto/x509" "encoding/json" + "encoding/pem" "errors" "os" "path/filepath" "time" - "github.com/cloudflare/cfssl/certinfo" "github.com/cloudflare/cfssl/config" "github.com/cloudflare/cfssl/helpers/derhelpers" "github.com/cloudflare/cfssl/log" @@ -61,7 +62,11 @@ func loadSecretData(config *config.Config) (sd *SecretData, err error) { } func checkCertUsable(certPEM []byte) error { - cert, err := certinfo.ParseCertificatePEM(certPEM) + block, _ := pem.Decode(certPEM) + if block == nil { + return errors.New("no PEM block found") + } + cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return err } diff --git a/cmd/dkl-local-server/tls-ca.go b/cmd/dkl-local-server/tls-ca.go index aa48a74..005d57c 100644 --- a/cmd/dkl-local-server/tls-ca.go +++ b/cmd/dkl-local-server/tls-ca.go @@ -76,7 +76,12 @@ func (ca CA) ParseKey() (key crypto.Signer, err error) { return helpers.ParsePrivateKeyPEM(ca.Key) } func (ca CA) ParseCert() (cert *x509.Certificate, err error) { - return helpers.ParseCertificatePEM(ca.Cert) + block, _ := pem.Decode(ca.Cert) + if block == nil { + err = errors.New("no PEM block found") + return + } + return x509.ParseCertificate(block.Bytes) } func (ca CA) Signer(policy *config.Signing) (result *local.Signer, err error) { diff --git a/cmd/dkl-local-server/ws-cluster-cas.go b/cmd/dkl-local-server/ws-cluster-cas.go index ff768b8..361e203 100644 --- a/cmd/dkl-local-server/ws-cluster-cas.go +++ b/cmd/dkl-local-server/ws-cluster-cas.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "io" "time" "github.com/cloudflare/cfssl/helpers" @@ -50,23 +51,37 @@ func getUsableClusterCA(cluster, name string) (ca CA, err error) { if checkErr != nil { log.Infof("cluster %s: CA %s: regenerating certificate: %v", cluster, name, checkErr) - prevCerts, _ := helpers.ParseCertificatesPEM(ca.Cert) - - err = ca.RenewCert() - if err != nil { - err = fmt.Errorf("renew: %w", err) - } - - now := time.Now() - for _, cert := range prevCerts { - if cert.NotAfter.After(now) { - continue + ca, err = opMutex("renew-ca:"+key, func() (CA, error) { + ca, found, err := clusterCAs.Get(key) + if err != nil { + return ca, err + } + if !found { + return ca, fmt.Errorf("CA not found") } - certPEM := helpers.EncodeCertificatePEM(cert) - ca.Cert = append(ca.Cert, certPEM...) - } - err = clusterCAs.Put(key, ca) + prevCerts, _ := helpers.ParseCertificatesPEM(ca.Cert) + + err = ca.RenewCert() + if err != nil { + return ca, fmt.Errorf("renew: %w", err) + } + + now := time.Now() + for _, cert := range prevCerts { + if cert.NotAfter.Before(now) { + continue + } + certPEM := helpers.EncodeCertificatePEM(cert) + ca.Cert = append(ca.Cert, certPEM...) + } + + err = clusterCAs.Put(key, ca) + return ca, err + }) + if err != nil { + return ca, err + } } return @@ -98,3 +113,51 @@ func wsClusterCASignedKey(req *restful.Request, resp *restful.Response) { clusterCASignedKeys.WsGet(resp, clusterName+"/"+caName+"/"+name) } + +func wsClusterCACertPut(req *restful.Request, resp *restful.Response) { + clusterName := req.PathParameter("cluster-name") + caName := req.PathParameter("ca-name") + key := clusterName + "/" + caName + + body, err := io.ReadAll(req.Request.Body) + if err != nil { + wsError(resp, fmt.Errorf("read body: %w", err)) + return + } + + certs, err := helpers.ParseCertificatesPEM(body) + if err != nil { + wsError(resp, fmt.Errorf("invalid certificate(s): %w", err)) + return + } + + ca, found, err := clusterCAs.Get(key) + if err != nil { + wsError(resp, err) + return + } + if !found { + wsNotFound(resp) + return + } + + caCert, err := ca.ParseCert() + if err != nil { + wsError(resp, fmt.Errorf("parse CA cert: %w", err)) + return + } + + for _, c := range certs { + if err := c.CheckSignatureFrom(caCert); err != nil { + wsError(resp, fmt.Errorf("certificate not signed by this CA: %w", err)) + return + } + } + + ca.Cert = body + + if err := clusterCAs.Put(key, ca); err != nil { + wsError(resp, err) + return + } +} diff --git a/cmd/dkl-local-server/ws.go b/cmd/dkl-local-server/ws.go index 6d3a911..5be5f71 100644 --- a/cmd/dkl-local-server/ws.go +++ b/cmd/dkl-local-server/ws.go @@ -132,6 +132,9 @@ func registerWS(rest *restful.Container) { cluster(GET, "/CAs/{ca-name}/certificate").To(wsClusterCACert). Produces(mime.CACERT). Doc("Get cluster CA's certificate"), + cluster(PUT, "/CAs/{ca-name}/certificate").To(wsClusterCACertPut). + Consumes(mime.CACERT). + Doc("Set cluster CA's certificate(s)"), cluster(GET, "/CAs/{ca-name}/signed").To(wsClusterSignedCert). Produces(mime.CERT). Param(ws.QueryParameter("name", "signed reference name").Required(true)).