2023-02-12 11:58:26 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
2023-02-12 15:18:42 +01:00
|
|
|
"fmt"
|
2026-07-17 13:24:41 +02:00
|
|
|
"io"
|
2025-01-26 18:59:51 +01:00
|
|
|
"time"
|
2023-02-12 15:18:42 +01:00
|
|
|
|
2025-01-26 18:59:51 +01:00
|
|
|
"github.com/cloudflare/cfssl/helpers"
|
2023-02-12 15:18:42 +01:00
|
|
|
"github.com/cloudflare/cfssl/log"
|
2023-02-12 11:58:26 +01:00
|
|
|
restful "github.com/emicklei/go-restful"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var clusterCAs = newClusterSecretKV[CA]("CAs")
|
|
|
|
|
|
|
|
|
|
func wsClusterCAs(req *restful.Request, resp *restful.Response) {
|
|
|
|
|
clusterName := req.PathParameter("cluster-name")
|
|
|
|
|
clusterCAs.WsList(resp, clusterName+"/")
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-12 15:18:42 +01:00
|
|
|
func getUsableClusterCA(cluster, name string) (ca CA, err error) {
|
|
|
|
|
defer func() {
|
|
|
|
|
if err != nil {
|
|
|
|
|
err = fmt.Errorf("cluster %s CA %s: %w", cluster, name, err)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
key := cluster + "/" + name
|
|
|
|
|
|
|
|
|
|
ca, found, err := clusterCAs.Get(key)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !found {
|
|
|
|
|
log.Info("new CA in cluster ", cluster, ": ", name)
|
|
|
|
|
|
|
|
|
|
err = ca.Init()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = clusterCAs.Put(key, ca)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
checkErr := checkCertUsable(ca.Cert)
|
|
|
|
|
if checkErr != nil {
|
|
|
|
|
log.Infof("cluster %s: CA %s: regenerating certificate: %v", cluster, name, checkErr)
|
|
|
|
|
|
2026-07-17 13:24:41 +02:00
|
|
|
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")
|
|
|
|
|
}
|
2025-01-26 18:59:51 +01:00
|
|
|
|
2026-07-17 13:24:41 +02:00
|
|
|
prevCerts, _ := helpers.ParseCertificatesPEM(ca.Cert)
|
2023-02-12 15:18:42 +01:00
|
|
|
|
2026-07-17 13:24:41 +02:00
|
|
|
err = ca.RenewCert()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return ca, fmt.Errorf("renew: %w", err)
|
2025-01-26 18:59:51 +01:00
|
|
|
}
|
|
|
|
|
|
2026-07-17 13:24:41 +02:00
|
|
|
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
|
|
|
|
|
}
|
2023-02-12 15:18:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 08:43:43 +01:00
|
|
|
func caExtraCerts(cluster, name string) (extra string, err error) {
|
|
|
|
|
cfg, err := readConfig()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if cfg.ExtraCaCerts != nil {
|
|
|
|
|
extra = cfg.ExtraCaCerts[cluster+"/"+name]
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-12 11:58:26 +01:00
|
|
|
var clusterCASignedKeys = newClusterSecretKV[KeyCert]("CA-signed-keys")
|
|
|
|
|
|
|
|
|
|
func wsClusterCASignedKeys(req *restful.Request, resp *restful.Response) {
|
|
|
|
|
clusterName := req.PathParameter("cluster-name")
|
|
|
|
|
caName := req.PathParameter("ca-name")
|
|
|
|
|
clusterCASignedKeys.WsList(resp, clusterName+"/"+caName+"/")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func wsClusterCASignedKey(req *restful.Request, resp *restful.Response) {
|
|
|
|
|
clusterName := req.PathParameter("cluster-name")
|
|
|
|
|
caName := req.PathParameter("ca-name")
|
|
|
|
|
name := req.PathParameter("signed-name")
|
|
|
|
|
|
|
|
|
|
clusterCASignedKeys.WsGet(resp, clusterName+"/"+caName+"/"+name)
|
|
|
|
|
}
|
2026-07-17 13:24:41 +02:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|