Skip to content
Snippets Groups Projects
Commit bea82082 authored by kaiyou's avatar kaiyou
Browse files

Use the new PKI API to generate node certificates

parent 7f3b4654
No related branches found
No related tags found
No related merge requests found
Pipeline #21052 passed
......@@ -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)
}
......@@ -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
}
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, ""))
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment