From c421dd5ca8be7804b0673fd7628544ba5f3d4509 Mon Sep 17 00:00:00 2001 From: Alejandro Mery Date: Sat, 26 Aug 2023 16:09:48 +0100 Subject: [PATCH] WIP Signed-off-by: Alejandro Mery --- pkg/zones/machine.go | 18 ++++ pkg/zones/machine_rings.go | 52 +++++++++++ pkg/zones/rings.go | 172 +++++++++++++++++++++++++++++++++++++ pkg/zones/sync.go | 4 +- pkg/zones/zone_rings.go | 68 ++++++++++++++- 5 files changed, 311 insertions(+), 3 deletions(-) diff --git a/pkg/zones/machine.go b/pkg/zones/machine.go index 0a49ad3..1dd64a2 100644 --- a/pkg/zones/machine.go +++ b/pkg/zones/machine.go @@ -43,6 +43,24 @@ func (m *Machine) IsGateway() bool { return ok } +// SetGateway enables/disables a Machine ring0 integration +func (m *Machine) SetGateway(enabled bool) error { + ri, found := m.getRingInfo(0) + switch { + case !found && !enabled: + return nil + case !found: + var err error + + if ri, err = m.createRingInfo(0, false); err != nil { + return err + } + } + + ri.Enabled = enabled + return m.SyncWireguardConfig(0) +} + // Zone indicates the [Zone] this machine belongs to func (m *Machine) Zone() int { return m.zone.ID diff --git a/pkg/zones/machine_rings.go b/pkg/zones/machine_rings.go index bd67099..77ee240 100644 --- a/pkg/zones/machine_rings.go +++ b/pkg/zones/machine_rings.go @@ -261,3 +261,55 @@ func (m *Machine) RemoveWireguardConfig(ring int) error { return err } + +// SyncWireguardConfig updates all wgN.conf files for the specified +// ring +func (m *Machine) SyncWireguardConfig(ring int) error { + return m.zone.SyncWireguardConfig(ring) +} + +// WriteWireguardConfig ... +func (m *Machine) WriteWireguardConfig(ring int) error { + r, err := NewRing(m.zone, ring) + if err != nil { + return err + } + + return m.writeWireguardRingConfig(r) +} + +func (m *Machine) writeWireguardRingConfig(r *Ring) error { + wg, err := r.ExportConfig(m) + if err != nil { + return nil + } + + f, err := m.CreateTruncFile("wg%v.conf", r.ID) + if err != nil { + return err + } + defer f.Close() + + _, err = wg.WriteTo(f) + return err +} + +func (m *Machine) createRingInfo(ring int, enabled bool) (*RingInfo, error) { + keys, err := wireguard.NewKeyPair() + if err != nil { + return nil, err + } + + ri := &RingInfo{ + Ring: ring, + Enabled: enabled, + Keys: keys, + } + + err = m.applyRingInfo(ring, ri) + if err != nil { + return nil, err + } + + return ri, nil +} diff --git a/pkg/zones/rings.go b/pkg/zones/rings.go index 6ca0593..421f3b4 100644 --- a/pkg/zones/rings.go +++ b/pkg/zones/rings.go @@ -2,6 +2,7 @@ package zones import ( "fmt" + "io/fs" "net/netip" "git.jpi.io/amery/jpictl/pkg/wireguard" @@ -179,3 +180,174 @@ func RingOneAddress(zoneID, nodeID int) (netip.Addr, bool) { return netip.AddrFrom4(a4), true } } + +var ( + _ MachineIterator = (*Ring)(nil) +) + +// A Ring describes all peers on a ring +type Ring struct { + RingAddressEncoder + + Peers []*RingPeer +} + +// AddPeer adds a [Machine] to the ring +func (r *Ring) AddPeer(p *Machine) bool { + _, ok := p.getRingInfo(r.ID) + if !ok { + return false + } + + rp := r.newPeer(p) + r.Peers = append(r.Peers, rp) + return true +} + +func (r *Ring) newPeer(p *Machine) *RingPeer { + nodeID := p.ID + zoneID := p.Zone() + + ri, _ := p.getRingInfo(r.ID) + addr, _ := r.Encode(zoneID, nodeID) + + rp := &RingPeer{ + Node: p, + Address: addr, + PrivateKey: ri.Keys.PrivateKey, + PeerConfig: wireguard.PeerConfig{ + PublicKey: ri.Keys.PublicKey, + Endpoint: wireguard.EndpointAddress{ + Host: p.FullName(), + Port: r.Port, + }, + }, + } + + if r.ID == 0 { + rp.AllowRingOneNetwork(zoneID) + rp.AllowIP(addr) + } else { + rp.AllowIP(addr) + + if p.IsGateway() { + zones := p.zone.zones + rp.AllowOtherRingOneNetworks(zones, zoneID) + rp.AllowRingZeroGateways(zones) + } + } + + return rp +} + +// ForEachMachine calls a function for each Machine in the ring +// until instructed to terminate the loop +func (r *Ring) ForEachMachine(fn func(*Machine) bool) { + for _, pp := range r.Peers { + if fn(pp.Node) { + return + } + } +} + +// ExportConfig ... +func (r *Ring) ExportConfig(p *Machine) (*wireguard.Config, error) { + cur, ok := r.getMachine(p) + if !ok { + return nil, fs.ErrNotExist + } + + out := &wireguard.Config{ + Interface: wireguard.InterfaceConfig{ + Address: cur.Address, + PrivateKey: cur.PrivateKey, + ListenPort: r.Port, + }, + } + + for _, pp := range r.Peers { + if pp.Node != p { + pc := pp.PeerConfig + out.Peer = append(out.Peer, pc) + } + } + return out, nil +} + +func (r *Ring) getMachine(p *Machine) (*RingPeer, bool) { + for _, pp := range r.Peers { + if pp.Node == p { + return pp, true + } + } + + return nil, false +} + +// A RingPeer is a node on a [Ring] +type RingPeer struct { + Node *Machine + + Address netip.Addr + PrivateKey wireguard.PrivateKey + PeerConfig wireguard.PeerConfig +} + +// AllowIP ... +func (rp *RingPeer) AllowIP(addr netip.Addr) { + rp.PeerConfig.AllowedIPs = append(rp.PeerConfig.AllowedIPs, + netip.PrefixFrom(addr, 32), + ) +} + +// AllowNetwork ... +func (rp *RingPeer) AllowNetwork(addr netip.Addr, bits int) { + rp.PeerConfig.AllowedIPs = append(rp.PeerConfig.AllowedIPs, + netip.PrefixFrom(addr, bits), + ) +} + +// AllowRingOneNetwork ... +func (rp *RingPeer) AllowRingOneNetwork(zoneID int) { + addr, _ := RingOneAddress(zoneID, 0) + rp.AllowNetwork(addr, 12) +} + +// AllowOtherRingOneNetworks ... +func (rp *RingPeer) AllowOtherRingOneNetworks(m *Zones, zoneID int) { + m.ForEachZone(func(z *Zone) bool { + if z.ID != zoneID { + addr, _ := RingOneAddress(z.ID, 0) + rp.AllowNetwork(addr, 12) + } + return false + }) +} + +// AllowRingZeroGateways ... +func (rp *RingPeer) AllowRingZeroGateways(m *Zones) { + m.ForEachMachine(func(p *Machine) bool { + switch { + case !p.IsGateway(): + // skip regular nodes + default: + addr, _ := RingZeroAddress(p.Zone(), p.ID) + rp.AllowIP(addr) + } + return false + }) +} + +// NewRing ... +func NewRing(m MachineIterator, ring int) (*Ring, error) { + r := &Ring{ + RingAddressEncoder: Rings[ring], + } + + m.ForEachMachine(func(p *Machine) bool { + r.AddPeer(p) + return false + }) + + return r, nil +} diff --git a/pkg/zones/sync.go b/pkg/zones/sync.go index 76e3776..71b8951 100644 --- a/pkg/zones/sync.go +++ b/pkg/zones/sync.go @@ -18,12 +18,12 @@ func (m *Zones) SyncAllWireguard() error { var err error for ring := 0; ring < RingsCount; ring++ { - err = m.PruneWireguardConfig(ring) + err = m.WriteWireguardKeys(ring) if err != nil { return err } - err = m.WriteWireguardKeys(ring) + err = m.SyncWireguardConfig(ring) if err != nil { return err } diff --git a/pkg/zones/zone_rings.go b/pkg/zones/zone_rings.go index 25db1bc..866ddc5 100644 --- a/pkg/zones/zone_rings.go +++ b/pkg/zones/zone_rings.go @@ -1,6 +1,33 @@ package zones -import "os" +import ( + "io/fs" + "os" +) + +var ( + _ machineRinger = (*Zone)(nil) + _ machineRinger = (*Zones)(nil) +) + +type machineRinger interface { + MachineIterator + + PruneWireguardConfig(ring int) error +} + +// SyncWireguardConfig updates all wgN.conf files for the specified +// ring +func (z *Zone) SyncWireguardConfig(ring int) error { + switch ring { + case 0: + return syncWireguardConfig(z.zones, ring) + case 1: + return syncWireguardConfig(z, ring) + default: + return fs.ErrInvalid + } +} // PruneWireguardConfig removes wgN.conf files of machines with // the corresponding ring disabled. @@ -13,6 +40,24 @@ func (z *Zone) WriteWireguardKeys(ring int) error { return writeWireguardKeys(z, ring) } +// SyncWireguardConfig updates all wgN.conf files for the specified +// ring +func (m *Zones) SyncWireguardConfig(ring int) error { + switch ring { + case 0: + return syncWireguardConfig(m, ring) + case 1: + var err error + m.ForEachZone(func(z *Zone) bool { + err = syncWireguardConfig(z, ring) + return err != nil + }) + return err + default: + return fs.ErrInvalid + } +} + // PruneWireguardConfig removes wgN.conf files of machines with // the corresponding ring disabled on all zones func (m *Zones) PruneWireguardConfig(ring int) error { @@ -24,6 +69,27 @@ func (m *Zones) WriteWireguardKeys(ring int) error { return writeWireguardKeys(m, ring) } +func syncWireguardConfig(m machineRinger, ring int) error { + err := m.PruneWireguardConfig(ring) + if err != nil { + return err + } + + r, err := NewRing(m, ring) + if err != nil { + return err + } + + m.ForEachMachine(func(p *Machine) bool { + if _, ok := p.getRingInfo(ring); ok { + err = p.writeWireguardRingConfig(r) + } + return err != nil + }) + + return err +} + func pruneWireguardConfig(m MachineIterator, ring int) error { var err error