package cluster import ( "bytes" "errors" "fmt" "os" "darvaza.org/core" "git.jpi.io/amery/jpictl/pkg/rings" "git.jpi.io/amery/jpictl/pkg/wireguard" ) // GetWireguardKeys reads a wgN.key/wgN.pub files func (m *Machine) GetWireguardKeys(ringID rings.RingID) (wireguard.KeyPair, error) { var ( data []byte out wireguard.KeyPair ) ring, err := AsWireguardInterfaceID(ringID) if err != nil { // invalid ring return out, err } keyFile, pubFile, _ := ring.Files() data, err = m.ReadFile(keyFile) if err != nil { // failed to read return out, err } out.PrivateKey, err = wireguard.PrivateKeyFromBase64(string(data)) if err != nil { // bad key err = core.Wrap(err, keyFile) return out, err } data, err = m.ReadFile(pubFile) switch { case os.IsNotExist(err): // no wgN.pub is fine case err != nil: // failed to read return out, err default: // good read out.PublicKey, err = wireguard.PublicKeyFromBase64(string(data)) if err != nil { // bad key err = core.Wrap(err, pubFile) return out, err } } err = out.Validate() return out, err } func (m *Machine) tryReadWireguardKeys(ringID rings.RingID) error { kp, err := m.GetWireguardKeys(ringID) switch { case os.IsNotExist(err): // ignore return nil case err != nil: // something went wrong return err default: // import keys ri := &RingInfo{ Ring: MustWireguardInterfaceID(ringID), Keys: kp, } return m.applyRingInfo(ringID, ri) } } // RemoveWireguardKeys deletes wgN.key and wgN.pub from // the machine's config directory func (m *Machine) RemoveWireguardKeys(ringID rings.RingID) error { ring, err := AsWireguardInterfaceID(ringID) if err != nil { return err } keyFile, pubFile, _ := ring.Files() err = m.RemoveFile(pubFile) switch { case os.IsNotExist(err): // ignore case err != nil: return err } err = m.RemoveFile(keyFile) if os.IsNotExist(err) { // ignore err = nil } return err } // GetWireguardConfig reads a wgN.conf file func (m *Machine) GetWireguardConfig(ringID rings.RingID) (*wireguard.Config, error) { ring, err := AsWireguardInterfaceID(ringID) if err != nil { return nil, err } data, err := m.ReadFile(ring.ConfFile()) if err != nil { return nil, err } r := bytes.NewReader(data) return wireguard.NewConfigFromReader(r) } func (m *Machine) tryApplyWireguardConfig(ring rings.RingID) error { wg, err := m.GetWireguardConfig(ring) switch { case os.IsNotExist(err): return nil case err != nil: return err default: return m.applyWireguardConfig(ring, wg) } } func (m *Machine) applyWireguardConfigNode(ring rings.RingID, wg *wireguard.Config) error { addr := wg.GetAddress() if !core.IsZero(addr) { zoneID, nodeID, ok := Rings[ring].Decode(addr) if !ok { return fmt.Errorf("%s: invalid address", addr) } if err := m.applyZoneNodeID(zoneID, nodeID); err != nil { return core.Wrap(err, "%s: invalid address", addr) } } if err := m.applyWireguardInterfaceConfig(ring, wg.Interface); err != nil { return core.Wrap(err, "interface") } return nil } func (m *Machine) applyWireguardConfig(ring rings.RingID, wg *wireguard.Config) error { if err := m.applyWireguardConfigNode(ring, wg); err != nil { return err } for _, peer := range wg.Peer { err := m.applyWireguardPeerConfig(ring, peer) switch { case errors.Is(err, ErrUnknownNode): // ignore unknown peers m.warn(nil). WithField("subsystem", "wireguard"). WithField("node", m.Name). WithField("peer", peer.Endpoint.Host). WithField("ring", MustWireguardInterfaceID(ring)). Print("ignoring unknown endpoint") case err != nil: return core.Wrap(err, "peer") } } return nil } func (m *Machine) getRingInfo(ring rings.RingID) (*RingInfo, bool) { for _, ri := range m.Rings { if ri.RingID() == ring { return ri, ri.Enabled } } return nil, false } func (m *Machine) applyRingInfo(ring rings.RingID, new *RingInfo) error { cur, _ := m.getRingInfo(ring) if cur == nil { // first, append m.debug(). WithField("node", m.Name). WithField("ring", MustWireguardInterfaceID(ring)). Print("found") m.Rings = append(m.Rings, new) return nil } // extra, merge return cur.Merge(new) } func (m *Machine) applyWireguardInterfaceConfig(ring rings.RingID, data wireguard.InterfaceConfig) error { // ri := &RingInfo{ Ring: MustWireguardInterfaceID(ring), Enabled: true, Keys: wireguard.KeyPair{ PrivateKey: data.PrivateKey, }, } return m.applyRingInfo(ring, ri) } func (m *Machine) applyWireguardPeerConfig(ring rings.RingID, pc wireguard.PeerConfig) error { peer, found := m.getPeerByName(pc.Endpoint.Name()) switch { case !found: // unknown return core.Wrap(ErrUnknownNode, pc.Endpoint.Host) case ring == 1 && m.zone != peer.zone: // invalid zone return core.Wrap(ErrInvalidNode, peer.Name) default: // apply RingInfo ri := &RingInfo{ Ring: MustWireguardInterfaceID(ring), Enabled: true, Keys: wireguard.KeyPair{ PublicKey: pc.PublicKey, }, } return peer.applyRingInfo(ring, ri) } } func (m *Machine) applyZoneNodeID(zoneID rings.ZoneID, nodeID rings.NodeID) error { switch { case zoneID == 0: return fmt.Errorf("invalid %s", "zoneID") case nodeID == 0: return fmt.Errorf("invalid %s", "nodeID") case m.ID != nodeID: return fmt.Errorf("invalid %s: %v ≠ %v", "zoneID", m.ID, nodeID) case m.zone.ID != 0 && m.zone.ID != zoneID: return fmt.Errorf("invalid %s: %v ≠ %v", "zoneID", m.zone.ID, zoneID) case m.zone.ID == 0: m.zone.ID = zoneID } return nil } func (m *Machine) setRingDefaults(ri *RingInfo) error { if ri.Keys.PrivateKey.IsZero() { m.info(). WithField("subsystem", "wireguard"). WithField("node", m.Name). WithField("ring", ri.Ring). Print("generating key pair") kp, err := wireguard.NewKeyPair() if err != nil { return err } ri.Keys = kp } return nil } // RemoveWireguardConfig deletes wgN.conf from the machine's // config directory. func (m *Machine) RemoveWireguardConfig(ringID rings.RingID) error { ring, err := AsWireguardInterfaceID(ringID) if err != nil { return err } err = m.RemoveFile(ring.ConfFile()) if os.IsNotExist(err) { err = nil } return err } func (m *Machine) createRingInfo(ringID rings.RingID, enabled bool) (*RingInfo, error) { ring, err := AsWireguardInterfaceID(ringID) if err != nil { return nil, err } keys, err := wireguard.NewKeyPair() if err != nil { return nil, err } ri := &RingInfo{ Ring: ring, Enabled: enabled, Keys: keys, } err = m.applyRingInfo(ringID, ri) if err != nil { return nil, err } return ri, nil }