Compare commits

..

9 Commits

Author SHA1 Message Date
amery a3e3cde4c4 zones: fix scanMachines to run after all zones have been read
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-22 02:54:35 +00:00
amery a4a10d0226 zones: add helpers to compose and parse wg0/wg1 addresses
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-22 02:50:54 +00:00
amery 06e755ecd2 zones: scan for Machine details after they've all been added
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-22 02:01:00 +00:00
amery b15f394199 wireguard: add EndpointAddress.Name() removing the domain from Host
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-22 01:51:15 +00:00
amery f225e15b2c wireguard: rename EndpointAddress.Name to Host
and add String() method

Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-22 01:51:14 +00:00
amery 5d946e4e93 wireguard: adds wgN.conf parser
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-22 01:35:35 +00:00
amery 979324f151 zones: adds ForEachZone iterator
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-22 00:38:41 +00:00
amery be9da490ff zones: adds ForEachMachine() iterator
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-22 00:38:38 +00:00
amery 3599812072 zones: introduce Machine.ReadFile()
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-22 00:38:38 +00:00
9 changed files with 335 additions and 6 deletions
+3 -1
View File
@@ -3,16 +3,17 @@ module git.jpi.io/amery/jpictl
go 1.19
require (
darvaza.org/core v0.9.5
darvaza.org/resolver v0.5.2
darvaza.org/sidecar v0.0.0-20230721122716-b9c54b8adbaf
darvaza.org/slog v0.5.2
github.com/burntSushi/toml v0.3.1
github.com/mgechev/revive v1.3.2
github.com/spf13/cobra v1.7.0
gopkg.in/gcfg.v1 v1.2.3
)
require (
darvaza.org/core v0.9.5 // indirect
darvaza.org/slog/handlers/filter v0.4.4 // indirect
darvaza.org/slog/handlers/zerolog v0.4.4 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
@@ -36,4 +37,5 @@ require (
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/tools v0.12.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)
+4
View File
@@ -87,6 +87,10 @@ golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+221
View File
@@ -0,0 +1,221 @@
package wireguard
import (
"encoding/base64"
"errors"
"fmt"
"io"
"net/netip"
"strconv"
"strings"
"darvaza.org/core"
"gopkg.in/gcfg.v1"
)
// Config represents a wgN.conf file
type Config struct {
Interface InterfaceConfig
Peer []PeerConfig
}
// GetAddress is a shortcut to the interface's address
func (f *Config) GetAddress() netip.Addr {
return f.Interface.Address
}
// Peers tells how many peers are described
func (f *Config) Peers() int {
return len(f.Peer)
}
// InterfaceConfig represents the [Interface] section
type InterfaceConfig struct {
Address netip.Addr
PrivateKey []byte
ListenPort uint16
}
// PeerConfig represents a [Peer] section
type PeerConfig struct {
PublicKey []byte
Endpoint EndpointAddress
AllowedIPs []netip.Prefix
}
// EndpointAddress is a host:port pair to reach the Peer
type EndpointAddress struct {
Host string
Port uint16
}
// Name returns the first part of a hostname
func (ep EndpointAddress) Name() string {
before, _, _ := strings.Cut(ep.Host, ".")
return before
}
func (ep EndpointAddress) String() string {
switch {
case ep.Host == "":
return ""
case ep.Port == 0:
return ep.Host
case !strings.ContainsRune(ep.Host, ':'):
return fmt.Sprintf("%s:%v", ep.Host, ep.Port)
default:
return fmt.Sprintf("[%s]:%v", ep.Host, ep.Port)
}
}
// FromString sets the EndpointAddress from a given "[host]:port"
func (ep *EndpointAddress) FromString(s string) error {
host, port, err := core.SplitHostPort(s)
if err != nil {
return err
}
ep.Host = host
switch {
case port != "":
n, _ := strconv.ParseUint(port, 10, 16)
ep.Port = uint16(n)
default:
ep.Port = 0
}
return nil
}
type intermediateConfig struct {
Interface interfaceConfig
Peer peersConfig
}
func (v *intermediateConfig) Export() (*Config, error) {
var out Config
var err error
// Interface
out.Interface, err = v.Interface.Export()
if err != nil {
return nil, err
}
// Peers
peers, ok := v.PeersCount()
if !ok {
return nil, errors.New("inconsistent Peer data")
}
for i := 0; i < peers; i++ {
p, err := v.ExportPeer(i)
if err != nil {
err = core.Wrapf(err, "Peer[%v]:", i)
return nil, err
}
out.Peer = append(out.Peer, p)
}
return &out, nil
}
type interfaceConfig struct {
Address netip.Addr
PrivateKey string
ListenPort uint16
}
func (p interfaceConfig) Export() (InterfaceConfig, error) {
out := InterfaceConfig{
Address: p.Address,
ListenPort: p.ListenPort,
}
b, err := base64.StdEncoding.DecodeString(p.PrivateKey)
if err != nil {
err = core.Wrap(err, "PrivateKey")
return InterfaceConfig{}, err
}
out.PrivateKey = b
return out, nil
}
type peersConfig struct {
PublicKey []string
Endpoint []string
AllowedIPs []string
}
func (v *intermediateConfig) ExportPeer(i int) (PeerConfig, error) {
var out PeerConfig
// Endpoint
s := v.Peer.Endpoint[i]
err := out.Endpoint.FromString(s)
if err != nil {
err = core.Wrap(err, "Endpoint")
return out, err
}
// PublicKey
s = v.Peer.PublicKey[i]
out.PublicKey, err = base64.StdEncoding.DecodeString(s)
if err != nil {
err = core.Wrap(err, "PublicKey")
return out, err
}
// AllowedIPs
s = v.Peer.AllowedIPs[i]
out.AllowedIPs, err = parseAllowedIPs(s)
if err != nil {
err = core.Wrap(err, "AllowedIPs")
return out, err
}
return out, nil
}
func parseAllowedIPs(data string) ([]netip.Prefix, error) {
var out []netip.Prefix
for _, s := range strings.Split(data, ",") {
s = strings.TrimSpace(s)
p, err := netip.ParsePrefix(s)
if err != nil {
return out, err
}
out = append(out, p)
}
return out, nil
}
func (v *intermediateConfig) PeersCount() (int, bool) {
c0 := len(v.Peer.Endpoint)
c1 := len(v.Peer.PublicKey)
c2 := len(v.Peer.AllowedIPs)
if c0 != c1 || c1 != c2 {
return 0, false
}
return c0, true
}
// NewConfigFromReader parses a wgN.conf file
func NewConfigFromReader(r io.Reader) (*Config, error) {
temp := &intermediateConfig{}
if err := gcfg.ReadInto(temp, r); err != nil {
return nil, err
}
return temp.Export()
}
+2
View File
@@ -0,0 +1,2 @@
// Package wireguard deals with wireguard config
package wireguard
+49
View File
@@ -0,0 +1,49 @@
package zones
import "net/netip"
// ParseRingZeroAddress extracts zone and node ID from a wg0 [netip.Addr]
func ParseRingZeroAddress(addr netip.Addr) (zoneID int, nodeID int, ok bool) {
if addr.IsValid() {
a4 := addr.As4()
if a4[0] == 10 && a4[1] == 0 {
return int(a4[2]), int(a4[3]), true
}
}
return 0, 0, false
}
// RingZeroAddress returns a wg0 IP address
func RingZeroAddress(zoneID, nodeID int) netip.Addr {
c := zoneID
d := nodeID
return netip.AddrFrom4([4]byte{
10, 0, uint8(c), uint8(d),
})
}
// ParseRingOneAddress extracts zone and node ID from a wg1 [netip.Addr]
func ParseRingOneAddress(addr netip.Addr) (zoneID int, nodeID int, ok bool) {
if addr.IsValid() {
a4 := addr.As4()
if a4[0] == 10 && a4[2] == 0 {
zoneID = int(a4[1] >> 4)
nodeID = int(a4[3])
return zoneID, nodeID, true
}
}
return 0, 0, false
}
// RingOneAddress returns a wg1 IP address
func RingOneAddress(zoneID, nodeID int) netip.Addr {
b := zoneID << 4
d := nodeID
return netip.AddrFrom4([4]byte{
10, uint8(b), 0, uint8(d),
})
}
+25
View File
@@ -1,7 +1,10 @@
package zones
import (
"fmt"
"io/fs"
"net/netip"
"path/filepath"
"strconv"
"strings"
"sync"
@@ -56,3 +59,25 @@ func (m *Machine) FullName() string {
return m.Name
}
// ReadFile reads a file from the machine's config directory
func (m *Machine) ReadFile(name string, args ...any) ([]byte, error) {
base := m.zone.zones.dir
fullName := m.getFilename(name, args...)
return fs.ReadFile(base, fullName)
}
func (m *Machine) getFilename(name string, args ...any) string {
if len(args) > 0 {
name = fmt.Sprintf(name, args...)
}
s := []string{
m.zone.Name,
m.Name,
name,
}
return filepath.Join(s...)
}
+4
View File
@@ -24,3 +24,7 @@ func (m *Machine) updatePublicAddresses() error {
m.PublicAddresses = addrs
return nil
}
func (m *Machine) scan() error {
return m.updatePublicAddresses()
}
+11 -5
View File
@@ -26,7 +26,17 @@ func (m *Zones) scan() error {
}
}
return nil
return m.scanMachines()
}
func (m *Zones) scanMachines() error {
var err error
m.ForEachMachine(func(p *Machine) {
if err == nil {
err = p.scan()
}
})
return err
}
func (z *Zone) scan() error {
@@ -43,10 +53,6 @@ func (z *Zone) scan() error {
Name: e.Name(),
}
if err := m.updatePublicAddresses(); err != nil {
return err
}
z.Machines = append(z.Machines, m)
}
}
+16
View File
@@ -31,6 +31,22 @@ type Zones struct {
Zones []*Zone `toml:"zones"`
}
// ForEachMachine calls a function for each Machine in the cluster
func (m *Zones) ForEachMachine(fn func(*Machine)) {
for _, z := range m.Zones {
for _, p := range z.Machines {
fn(p)
}
}
}
// ForEachZone calls a function for each Zone in the cluster
func (m *Zones) ForEachZone(fn func(*Zone)) {
for _, p := range m.Zones {
fn(p)
}
}
// NewFS builds a [Zones] tree using the given directory
func NewFS(dir fs.FS, domain string) (*Zones, error) {
z := &Zones{