From e7be7d73e5a4e42bce2e7e939d7cbceabadc66f8 Mon Sep 17 00:00:00 2001
From: kaiyou <dev@kaiyou.fr>
Date: Sun, 17 Dec 2023 20:53:52 +0100
Subject: [PATCH] General rewrite of the certificates code to support renewing
 certificates

---
 services/certs.go | 15 +++++++--
 services/meta.go  | 83 ++++++++++++++++++++++++++++-------------------
 2 files changed, 61 insertions(+), 37 deletions(-)

diff --git a/services/certs.go b/services/certs.go
index 5902790..19425b0 100644
--- a/services/certs.go
+++ b/services/certs.go
@@ -105,10 +105,16 @@ var pkiManager = &Unit{
 	},
 	Wake: func(u *Unit, c *Cluster) error {
 		for name, certs := range c.state.Certificates {
-			if certs.TLS.Cert == nil || certs.API.Cert == nil {
+			if certs == nil || certs.TLS == nil || certs.API == nil {
+				continue
+			}
+			if certs.TLS.Cert == nil && certs.TLS.CSR != nil {
 				c.pki.TLS.Sign(certs.TLS, pekahi.NewServerTemplate([]string{name}, certs.TLS.CSR.IPAddresses))
+				u.Manager.Logger.Info("signed node tls cert", "node", name)
+			}
+			if certs.API.Cert == nil && certs.API.CSR != nil {
 				c.pki.API.Sign(certs.API, pekahi.NewClientTemplate("system:node:"+name, "system:nodes"))
-				u.Manager.Logger.Info("signed node cert", "node", name)
+				u.Manager.Logger.Info("signed node api cert", "node", name)
 			}
 		}
 		return nil
@@ -223,6 +229,9 @@ var pkiNode = &Unit{
 		return nil
 	},
 	Ready: func(u *Unit, c *Cluster) bool {
-		return c.certs.TLS.Cert != nil && c.certs.API.Cert != nil
+		return (c.certs.TLS.Cert != nil &&
+			c.certs.API.Cert != nil &&
+			c.certs.TLS.Key.PublicKey.Equal(c.certs.TLS.Cert.PublicKey)) &&
+			c.certs.API.Key.PublicKey.Equal(c.certs.API.Cert.PublicKey)
 	},
 }
diff --git a/services/meta.go b/services/meta.go
index 3f98dba..7566de0 100644
--- a/services/meta.go
+++ b/services/meta.go
@@ -1,6 +1,7 @@
 package services
 
 import (
+	"bytes"
 	"encoding/json"
 	"net/netip"
 
@@ -79,47 +80,61 @@ func (s *HeptoState) String() string {
 func (s *HeptoState) Merge(b []byte) (bool, error) {
 	remote := new(HeptoState)
 	err := json.Unmarshal(b, remote)
+	// If state cannot decode or no pki is shared in state, bail
 	if err != nil {
 		return false, err
 	}
-	if s.PKI == nil || remote.PKI == nil {
-		return false, nil
+	// Track changes
+	changed := false
+	if s.PKI != nil && remote.PKI != nil {
+		// Grab CA Certificates
+		updateCA := func(local, remote *pekahi.Certificate) {
+			if local == nil || remote == nil {
+				return
+			}
+			if local.Cert == nil && remote.Cert != nil {
+				local.Cert = remote.Cert
+				local.Save()
+				changed = true
+			}
+		}
+		updateCA(s.PKI.TLS, remote.PKI.TLS)
+		updateCA(s.PKI.API, remote.PKI.API)
+		updateCA(s.PKI.Kubelet, remote.PKI.Kubelet)
+		updateCA(s.PKI.Proxy, remote.PKI.Proxy)
 	}
-	change := false
-	change = mergeCert(s.PKI.TLS, remote.PKI.TLS) || change
-	change = mergeCert(s.PKI.Kubelet, remote.PKI.Kubelet) || change
-	change = mergeCert(s.PKI.API, remote.PKI.API) || change
-	change = mergeCert(s.PKI.Proxy, remote.PKI.Proxy) || change
 	for name, remoteCerts := range remote.Certificates {
+		if remoteCerts == nil {
+			continue
+		}
 		certs, ok := s.Certificates[name]
-		if ok {
-			change = mergeCert(certs.TLS, remoteCerts.TLS) || change
-			change = mergeCert(certs.API, remoteCerts.API) || change
-		} else {
+		if !ok || certs == nil {
 			s.Certificates[name] = remoteCerts
-			change = true
+			changed = true
+			continue
 		}
+		// Grab a node CSR or our certificates if available
+		updateCSROrCert := func(local, remote *pekahi.Certificate) {
+			if local == nil || remote == nil {
+				return
+			}
+			// If this is not our own data, remote is providing a CSR we do not know about yet, save it
+			if local.Key == nil && remote.CSR != nil && (local.CSR == nil || !bytes.Equal(local.CSR.Raw, remote.CSR.Raw)) {
+				local.Cert = remote.Cert
+				local.CSR = remote.CSR
+				local.Save()
+				changed = true
+			}
+			// If remote is providing a cert that matches our key and CSR, save it
+			if local.Key != nil && local.CSR != nil && local.Cert == nil && remote.Cert != nil && local.Key.PublicKey.Equal(remote.Cert.PublicKey) {
+				local.Cert = remote.Cert
+				local.Save()
+				changed = true
+			}
+		}
+		updateCSROrCert(certs.API, remoteCerts.API)
+		updateCSROrCert(certs.TLS, remoteCerts.TLS)
 	}
-	return change, nil
-}
-
-// Merge a single node or master certificate
-func mergeCert(local *pekahi.Certificate, remote *pekahi.Certificate) bool {
-	change := false
-	// Do not merge nothing
-	if remote == nil {
-		return change
-	}
-	// Import CSR to master for signing
-	if local.CSR == nil && remote.CSR != nil {
-		local.CSR = remote.CSR
-		change = true
-	}
-	// Import and save cert back to node
-	if local.Cert == nil && remote.Cert != nil {
-		local.Cert = remote.Cert
-		local.Save()
-		change = true
-	}
-	return change
+	// Grab signed ceritificates for the kubelet of current node
+	return changed, nil
 }
-- 
GitLab