diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index d0159ecdfbcffff1027c609f39e4fbd98f091d03..4370b0a0a2a1e549384a048bd06075a073054df6 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -5,7 +5,6 @@ package cluster import ( "net" - "forge.tedomum.net/acides/hepto/hepto/pkg/pki" "forge.tedomum.net/acides/hepto/hepto/pkg/sml" "forge.tedomum.net/acides/hepto/hepto/pkg/wg" "github.com/sirupsen/logrus" @@ -13,7 +12,7 @@ import ( type Cluster struct { settings *ClusterSettings - ml *sml.Memberlist[HeptoMeta, *HeptoMeta] + ml *sml.Memberlist[HeptoMeta, HeptoState, *HeptoMeta, *HeptoState] vpn *wg.Wireguard networking *ClusterNetworking node *NodeSettings @@ -28,7 +27,7 @@ func New(settings *ClusterSettings, node *NodeSettings) (*Cluster, error) { networking: NewClusterNetworking(settings.Name, node.Name), } // Prepare memberlist - c.ml = sml.New[HeptoMeta](node.Name, node.IP, node.Port, node.Anchors, settings.Key) + c.ml = sml.New[HeptoMeta, HeptoState](node.Name, node.IP, node.Port, node.Anchors, settings.Key) // Prepare wireguard vpn, err := wg.New("wg0", 7124, c.networking.NodeAddress.IPNet()) if err != nil { @@ -42,18 +41,21 @@ func New(settings *ClusterSettings, node *NodeSettings) (*Cluster, error) { return nil, err } c.pki = pki + } else { + c.pki = &ClusterPKI{} } certs, err := NewNodeCerts("certs", node.Name) if err != nil { return nil, err } c.certs = certs - // Initialize cluster meta - meta := c.ml.Meta - meta.VpnKey = vpn.PubKey.String() - meta.Role = node.Role - meta.Certs = make(map[string]NodePKIBytes) - meta.CSR.APIClient = certs.APIClient.CSR + // Initialize node meta + c.ml.Meta.VpnKey = vpn.PubKey.String() + c.ml.Meta.Role = node.Role + // Initialize cluster state + c.ml.State.PKI = c.pki + c.ml.State.Certificates = make(map[string]*NodeCerts) + c.ml.State.Certificates[node.Name] = certs return c, nil } @@ -69,7 +71,9 @@ func (c *Cluster) Run() error { for { select { case <-events: - c.handlePKI() + if c.node.Role == "master" { + c.handlePKI() + } c.updateVPN() case <-instrUpdates: c.networking.MTU = instr.MinMTU() @@ -79,45 +83,8 @@ func (c *Cluster) Run() error { } func (c *Cluster) handlePKI() { - logrus.Debug("handling possible PKI messages") - for _, node := range c.ml.Nodes() { - meta := node.NodeMeta - if meta.Role == "master" { - logrus.Debug("found master node, checking certs") - logrus.Debug(string(node.Meta)) - if certs, ok := meta.Certs[c.node.Name]; ok { - if len(certs.APIClient) > 0 { - logrus.Debug("certificate is signed") - err := c.certs.APIClient.SetCert(certs.APIClient) - if err != nil { - logrus.Warn("cannot load certificate") - } - } - } - } - } - if c.pki != nil { - logrus.Debug("this is the master, generating required certificates") - for _, node := range c.ml.Nodes() { - meta := node.NodeMeta - if len(meta.CSR.APIClient) > 0 { - template := pki.NewClientTemplate(node.Name, "") - cert, err := c.pki.APIServer.Sign(meta.CSR.APIClient, template) - if err != nil { - logrus.Warn("cannot sign certificate") - continue - } - logrus.Debug("signed for ", node.Name, cert) - this := c.ml.Meta - var certs NodePKIBytes - if certs, ok := this.Certs[node.Name]; !ok { - this.Certs[node.Name] = certs - } - certs.APIClient = cert - this.Certs[node.Name] = certs - c.ml.Update() - } - } + for name, certs := range c.ml.State.Certificates { + c.pki.SignNodeCerts(name, certs) } } @@ -131,12 +98,12 @@ func (c *Cluster) updateVPN() { peerAddr := c.networking.NodeNet.DeriveAddress(node.Name).IP peer, err := c.vpn.MakePeer(node.Addr, meta.VpnKey, peerAddr, []net.IPNet{}) if err != nil { - logrus.Debug("Cannot setup VPN with node ", node.Name) + logrus.Debug("cannot setup VPN with node ", node.Name) logrus.Debug(err) continue } peers = append(peers, peer) } - logrus.Debugf("Updating the VPN mesh with %d peers, MTU %d", len(peers), c.networking.MTU) + logrus.Debugf("updating VPN mesh, %d peers, MTU %d", len(peers), c.networking.MTU) c.vpn.Update(peers, c.networking.MTU) } diff --git a/pkg/cluster/meta.go b/pkg/cluster/meta.go index 2cfc4f60cc66633ca9700731eb362032ecd8ac67..b3dcb3b889b228196112d2745492ee23fce92153 100644 --- a/pkg/cluster/meta.go +++ b/pkg/cluster/meta.go @@ -2,34 +2,82 @@ package cluster import ( "encoding/json" + "fmt" + "strings" ) -// Represents a node CSRs and returned certs -type NodePKIBytes struct { - // APIclient certificate - APIClient []byte `json:"apiclient"` -} - // Represents a node metadata type HeptoMeta struct { // Public key for the wireguard mesh VPN VpnKey string `json:"vpnKey"` // Node role inside the cluster Role string `json:"role"` - // Certificate CSRs - CSR NodePKIBytes `json:"csr"` - // Only on master, signed certificates - Certs map[string]NodePKIBytes `json:"certs"` +} + +// Represents the cluster state +type HeptoState struct { + // Cluster CAs public certificates + PKI *ClusterPKI `json:"ca"` + // Certificate per node, this should only + // be updated by the node itself + Certificates map[string]*NodeCerts `json:"nodes"` } func (m *HeptoMeta) Encode() ([]byte, error) { - return json.Marshal(&m) + return json.Marshal(m) } func (m *HeptoMeta) Decode(b []byte) error { - return json.Unmarshal(b, &m) + return json.Unmarshal(b, m) } func (m *HeptoMeta) String() string { return m.Role } + +func (s *HeptoState) Encode() ([]byte, error) { + return json.Marshal(s) +} + +func (s *HeptoState) Decode(b []byte) error { + return json.Unmarshal(b, s) +} + +func (s *HeptoState) String() string { + var res string + if s.PKI != nil { + var ca []string + if s.PKI.API != nil { + ca = append(ca, "api") + } + if s.PKI.Kubelet != nil { + ca = append(ca, "kubelet") + } + if s.PKI.Services != nil { + ca = append(ca, "services") + } + res += fmt.Sprintf("ca{%s}", strings.Join(ca, ", ")) + } + return res +} + +func (s *HeptoState) Merge(b []byte) error { + remote := new(HeptoState) + err := json.Unmarshal(b, remote) + if err != nil { + return err + } + if remote.PKI == nil { + return nil + } + s.PKI.Merge(remote.PKI) + for name, remoteCerts := range remote.Certificates { + _, ok := s.Certificates[name] + if ok { + s.Certificates[name].Merge(remoteCerts) + } else { + s.Certificates[name] = remoteCerts + } + } + return nil +} diff --git a/pkg/cluster/pki.go b/pkg/cluster/pki.go index b4988c8deef6404f2012fd68b0ae1ed6d3e3118b..37eafba55e1f7b416e0f6cde8009c988f2f21a40 100644 --- a/pkg/cluster/pki.go +++ b/pkg/cluster/pki.go @@ -1,16 +1,62 @@ package cluster import ( + "crypto/x509" "os" "path/filepath" "forge.tedomum.net/acides/hepto/hepto/pkg/pki" + "github.com/sirupsen/logrus" ) +// Cluster PKI is made of three different PKIs type ClusterPKI struct { - Services *pki.CA - Kubelet *pki.CA - APIServer *pki.CA + // Signs services exposed over the cluster + Services *pki.PKI `json:"services"` + // Signs kubelet client certificates (master) + Kubelet *pki.PKI `json:"kubelet"` + // Signs apiserver client certificates (nodes and controller) + API *pki.PKI `json:"api"` +} + +// Node certs +type NodeCerts struct { + // Certificate for exposing the kubelet service + Service *pki.Certificate `json:"service"` + // Node certificate for accessing the apiserver + API *pki.Certificate `json:"api"` +} + +// Merge PKI +func (n *ClusterPKI) Merge(other *ClusterPKI) { + if other.API != nil { + n.API = other.API + } + if other.Services != nil { + n.Services = other.Services + } + if other.Kubelet != nil { + n.Kubelet = other.Kubelet + } +} + +// Merge a single node certificate +func mergeNodeCert(local *pki.Certificate, remote *pki.Certificate) { + // Import CSR to master for signing + if remote.CSR != nil { + local.CSR = remote.CSR + } + // Import and save cert back to node + if remote.Cert != nil { + local.Cert = remote.Cert + local.Save() + } +} + +// Merge node certificates +func (n *NodeCerts) Merge(other *NodeCerts) { + mergeNodeCert(n.Service, other.Service) + mergeNodeCert(n.API, other.API) } func NewClusterPKI(path string) (*ClusterPKI, error) { @@ -18,35 +64,61 @@ func NewClusterPKI(path string) (*ClusterPKI, error) { if err != nil { return nil, err } - servicesCA, err := pki.GetCA(filepath.Join(path, "services")) + servicesCA, err := pki.GetPKI(filepath.Join(path, "services")) if err != nil { return nil, err } - kubeletCA, err := pki.GetCA(filepath.Join(path, "kubelet")) + kubeletCA, err := pki.GetPKI(filepath.Join(path, "kubelet")) if err != nil { return nil, err } - apiserverCA, err := pki.GetCA(filepath.Join(path, "apiserver")) + apiserverCA, err := pki.GetPKI(filepath.Join(path, "api")) if err != nil { return nil, err } return &ClusterPKI{servicesCA, kubeletCA, apiserverCA}, nil } -type NodeCerts struct { - APIClient *pki.CertKey -} - func NewNodeCerts(path string, nodeName string) (*NodeCerts, error) { err := os.MkdirAll(path, 0755) if err != nil { return nil, err } - apiClientCert, err := pki.LoadWithCSR(filepath.Join(path, "apiclient"), pki.NewClientTemplate(nodeName, "")) + // Service certificate + serviceCert, err := pki.GetCertificate(filepath.Join(path, "service")) + if err != nil { + return nil, err + } + err = serviceCert.MakeCSR(pki.NewServerTemplate([]string{nodeName})) + if err != nil { + return nil, err + } + // API certificate + apiClientCert, err := pki.GetCertificate(filepath.Join(path, "api")) + if err != nil { + return nil, err + } + err = apiClientCert.MakeCSR(pki.NewClientTemplate(nodeName, "")) if err != nil { return nil, err } return &NodeCerts{ - APIClient: apiClientCert, + Service: serviceCert, + API: apiClientCert, }, nil } + +func signCert(p *pki.PKI, c *pki.Certificate, template *x509.Certificate) { + if c.CSR != nil && c.Cert == nil { + logrus.Info("signing certificate ", c.CSR.Subject.String()) + err := p.Sign(c, template) + if err != nil { + logrus.Warnf("cannot sign API certificate for %s: %s", c.CSR.Subject.String(), err) + } + } +} + +func (p *ClusterPKI) SignNodeCerts(name string, n *NodeCerts) { + signCert(p.Services, n.Service, pki.NewServerTemplate([]string{name})) + signCert(p.API, n.API, pki.NewClientTemplate(name, "")) +}