Compare commits

..

5 Commits

Author SHA1 Message Date
amery 15f28fa7c8 zones: Machine.CreateFile() and Machine.CreateTruncFile()
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-23 20:13:20 +00:00
amery 6838d00a0a zones: introduce Machine.OpenFile()
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-23 20:12:39 +00:00
amery e723066f7b zones: introduce Machine.RemoveFile()
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-23 19:36:26 +00:00
amery 3c6dee543a zones: switch to using hackpadfs/os.FS as the standard os.FS is incomplete
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-23 19:14:56 +00:00
amery 75d7b4389a zones: move Machine.ReadFile to a dedicated machine_file.go
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-23 18:12:06 +00:00
18 changed files with 158 additions and 1048 deletions
-7
View File
@@ -1,7 +1,5 @@
package main package main
import "git.jpi.io/amery/jpictl/pkg/zones"
// Config describes the repository // Config describes the repository
type Config struct { type Config struct {
Base string Base string
@@ -12,8 +10,3 @@ var cfg = &Config{
Base: "./m", Base: "./m",
Domain: "m.jpi.cloud", Domain: "m.jpi.cloud",
} }
// LoadZones loads all zones and machines in the config directory
func (cfg *Config) LoadZones() (*zones.Zones, error) {
return zones.New(cfg.Base, cfg.Domain)
}
+5 -54
View File
@@ -2,76 +2,27 @@ package main
import ( import (
"bytes" "bytes"
"encoding/json"
"io"
"os" "os"
"github.com/burntSushi/toml" "github.com/burntSushi/toml"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/yaml.v3"
"git.jpi.io/amery/jpictl/pkg/zones"
) )
// Encoder represents an object that encodes another internally
type Encoder interface {
Encode(any) error
}
// Encoding represents a type of [Encoder]
type Encoding int
const (
// TOMLEncoding represents TOML encoding
TOMLEncoding Encoding = iota
// JSONEncoding represents JSON encoding
JSONEncoding
// YAMLEncoding represents YAML encoding
YAMLEncoding
)
// NewJSONEncoder returns a JSON [Encoder] to work on the given [io.Writer]
func NewJSONEncoder(w io.Writer) Encoder {
enc := json.NewEncoder(w)
enc.SetIndent(``, ` `)
return enc
}
// NewYAMLEncoder returns a YAML [Encoder] to work on the given [io.Writer]
func NewYAMLEncoder(w io.Writer) Encoder {
enc := yaml.NewEncoder(w)
enc.SetIndent(2)
return enc
}
// NewTOMLEncoder returns a TOML [Encoder] to work on the given [io.Writer]
func NewTOMLEncoder(w io.Writer) Encoder {
enc := toml.NewEncoder(w)
return enc
}
const encoding = YAMLEncoding
// Command // Command
var dumpCmd = &cobra.Command{ var dumpCmd = &cobra.Command{
Use: "dump", Use: "dump",
Short: "generates a text representation of the config", Short: "generates a toml representation of the config",
RunE: func(_ *cobra.Command, _ []string) error { RunE: func(_ *cobra.Command, _ []string) error {
var buf bytes.Buffer var buf bytes.Buffer
var enc Encoder
m, err := cfg.LoadZones() m, err := zones.New(cfg.Base, cfg.Domain)
if err != nil { if err != nil {
return err return err
} }
switch encoding { enc := toml.NewEncoder(&buf)
case JSONEncoding:
enc = NewJSONEncoder(&buf)
case YAMLEncoding:
enc = NewYAMLEncoder(&buf)
default:
enc = NewTOMLEncoder(&buf)
}
if err = enc.Encode(m); err != nil { if err = enc.Encode(m); err != nil {
return err return err
} }
+3 -1
View File
@@ -4,6 +4,8 @@ import (
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"git.jpi.io/amery/jpictl/pkg/zones"
) )
// Command // Command
@@ -11,7 +13,7 @@ var envCmd = &cobra.Command{
Use: "env", Use: "env",
Short: "generates environment variables for shell scripts", Short: "generates environment variables for shell scripts",
RunE: func(_ *cobra.Command, _ []string) error { RunE: func(_ *cobra.Command, _ []string) error {
m, err := cfg.LoadZones() m, err := zones.New(cfg.Base, cfg.Domain)
if err != nil { if err != nil {
return err return err
} }
-23
View File
@@ -1,23 +0,0 @@
package main
import (
"github.com/spf13/cobra"
)
// Command
var writeCmd = &cobra.Command{
Use: "write",
Short: "rewrites all config files",
RunE: func(_ *cobra.Command, _ []string) error {
m, err := cfg.LoadZones()
if err != nil {
return err
}
return m.SyncAll()
},
}
func init() {
rootCmd.AddCommand(writeCmd)
}
-1
View File
@@ -13,7 +13,6 @@ require (
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.7.0
golang.org/x/crypto v0.12.0 golang.org/x/crypto v0.12.0
gopkg.in/gcfg.v1 v1.2.3 gopkg.in/gcfg.v1 v1.2.3
gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
-1
View File
@@ -90,7 +90,6 @@ golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 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 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
-40
View File
@@ -1,44 +1,17 @@
package wireguard package wireguard
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net/netip" "net/netip"
"strconv" "strconv"
"strings" "strings"
"text/template"
"darvaza.org/core" "darvaza.org/core"
"gopkg.in/gcfg.v1" "gopkg.in/gcfg.v1"
) )
var configTemplate = template.Must(template.New("config").Funcs(template.FuncMap{
"PrefixJoin": func(a []netip.Prefix, sep string) string {
s := make([]string, len(a))
for i, p := range a {
s[i] = p.String()
}
return strings.Join(s, sep)
},
}).Parse(`[Interface]
{{if .Interface.Name}}# Name: {{.Interface.Name}}
{{end -}}
Address = {{.Interface.Address}}
PrivateKey = {{.Interface.PrivateKey}}
ListenPort = {{.Interface.ListenPort}}
{{- range .Peer }}
[Peer]
{{if .Name}}# Name: {{.Name}}
{{end -}}
PublicKey = {{.PublicKey}}
Endpoint = {{.Endpoint}}
AllowedIPs = {{ PrefixJoin .AllowedIPs ", "}}
{{- end }}
`))
// Config represents a wgN.conf file // Config represents a wgN.conf file
type Config struct { type Config struct {
Interface InterfaceConfig Interface InterfaceConfig
@@ -55,20 +28,8 @@ func (f *Config) Peers() int {
return len(f.Peer) return len(f.Peer)
} }
// WriteTo writes a Wireguard [Config] onto the provided [io.Writer]
func (f *Config) WriteTo(w io.Writer) (int64, error) {
var buf bytes.Buffer
if err := configTemplate.Execute(&buf, f); err != nil {
return 0, err
}
return buf.WriteTo(w)
}
// InterfaceConfig represents the [Interface] section // InterfaceConfig represents the [Interface] section
type InterfaceConfig struct { type InterfaceConfig struct {
Name string
Address netip.Addr Address netip.Addr
PrivateKey PrivateKey PrivateKey PrivateKey
ListenPort uint16 ListenPort uint16
@@ -76,7 +37,6 @@ type InterfaceConfig struct {
// PeerConfig represents a [Peer] section // PeerConfig represents a [Peer] section
type PeerConfig struct { type PeerConfig struct {
Name string
PublicKey PublicKey PublicKey PublicKey
Endpoint EndpointAddress Endpoint EndpointAddress
AllowedIPs []netip.Prefix AllowedIPs []netip.Prefix
+36 -83
View File
@@ -5,7 +5,6 @@ import (
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt"
"golang.org/x/crypto/curve25519" "golang.org/x/crypto/curve25519"
) )
@@ -28,108 +27,60 @@ var (
type ( type (
// PrivateKey is a binary Wireguard Private Key // PrivateKey is a binary Wireguard Private Key
PrivateKey [PrivateKeySize]byte PrivateKey []byte
// PublicKey is a binary Wireguard Public Key // PublicKey is a binary Wireguard Public Key
PublicKey [PublicKeySize]byte PublicKey []byte
) )
func (key PrivateKey) String() string { func (key PrivateKey) String() string {
switch { return encodeKey(key)
case key.IsZero():
return ""
default:
return base64.StdEncoding.EncodeToString(key[:])
}
} }
func (pub PublicKey) String() string { func (pub PublicKey) String() string {
switch { return encodeKey(pub)
case pub.IsZero():
return ""
default:
return base64.StdEncoding.EncodeToString(pub[:])
}
}
// MarshalJSON encodes the key for JSON, omitting empty.
func (key PrivateKey) MarshalJSON() ([]byte, error) {
return encodeKeyJSON(key.String())
}
// MarshalJSON encodes the key for JSON, omitting empty.
func (pub PublicKey) MarshalJSON() ([]byte, error) {
return encodeKeyJSON(pub.String())
}
func encodeKeyJSON(s string) ([]byte, error) {
var out []byte
if s != "" {
out = []byte(fmt.Sprintf("%q", s))
}
return out, nil
}
// MarshalYAML encodes the key for YAML, omitting empty.
func (key PrivateKey) MarshalYAML() (any, error) {
return encodeKeyYAML(key.String())
}
// MarshalYAML encodes the key for YAML, omitting empty.
func (pub PublicKey) MarshalYAML() (any, error) {
return encodeKeyYAML(pub.String())
}
func encodeKeyYAML(s string) (any, error) {
if s == "" {
return nil, nil
}
return s, nil
} }
// IsZero tells if the key hasn't been set // IsZero tells if the key hasn't been set
func (key PrivateKey) IsZero() bool { func (key PrivateKey) IsZero() bool {
var zero PrivateKey return len(key) == 0
return key.Equal(zero)
} }
// IsZero tells if the key hasn't been set // IsZero tells if the key hasn't been set
func (pub PublicKey) IsZero() bool { func (pub PublicKey) IsZero() bool {
var zero PublicKey return len(pub) == 0
return pub.Equal(zero)
} }
// Equal checks if two private keys are identical // Equal checks if two private keys are identical
func (key PrivateKey) Equal(alter PrivateKey) bool { func (key PrivateKey) Equal(alter PrivateKey) bool {
return bytes.Equal(key[:], alter[:]) return bytes.Equal(key, alter)
} }
// Equal checks if two public keys are identical // Equal checks if two public keys are identical
func (pub PublicKey) Equal(alter PublicKey) bool { func (pub PublicKey) Equal(alter PublicKey) bool {
return bytes.Equal(pub[:], alter[:]) return bytes.Equal(pub, alter)
} }
// PrivateKeyFromBase64 decodes a base64-based string into // PrivateKeyFromBase64 decodes a base64-based string into
// a [PrivateKey] // a [PrivateKey]
func PrivateKeyFromBase64(data string) (PrivateKey, error) { func PrivateKeyFromBase64(data string) (PrivateKey, error) {
b, err := decodeKey(data, PrivateKeySize) b, err := decodeKey(data, PrivateKeySize)
if err != nil { return b, err
var zero PrivateKey
return zero, err
}
return *(*[PrivateKeySize]byte)(b), nil
} }
// PublicKeyFromBase64 decodes a base64-based string into // PublicKeyFromBase64 decodes a base64-based string into
// a [PublicKey] // a [PublicKey]
func PublicKeyFromBase64(data string) (PublicKey, error) { func PublicKeyFromBase64(data string) (PublicKey, error) {
b, err := decodeKey(data, PublicKeySize) b, err := decodeKey(data, PublicKeySize)
if err != nil { return b, err
var zero PublicKey }
return zero, err
func encodeKey(b []byte) string {
switch {
case len(b) == 0:
return ""
default:
return base64.StdEncoding.EncodeToString(b)
} }
return *(*[PublicKeySize]byte)(b), nil
} }
func decodeKey(data string, size int) ([]byte, error) { func decodeKey(data string, size int) ([]byte, error) {
@@ -151,27 +102,27 @@ func NewPrivateKey() (PrivateKey, error) {
_, err := rand.Read(s[:]) _, err := rand.Read(s[:])
if err != nil { if err != nil {
var zero PrivateKey return []byte{}, err
return zero, err
} }
// apply same clamping as wireguard-go/device/noise-helpers.go // apply same clamping as wireguard-go/device/noise-helpers.go
s[0] &= 0xf8 s[0] &= 0xf8
s[31] = (s[31] & 0x7f) | 0x40 s[31] = (s[31] & 0x7f) | 0x40
return s, nil return s[:], nil
} }
// Public generates the corresponding PublicKey // Public generates the corresponding PublicKey
func (key PrivateKey) Public() PublicKey { func (key PrivateKey) Public() PublicKey {
var pub PublicKey if len(key) != PrivateKeySize {
if !key.IsZero() { return []byte{}
in := (*[PrivateKeySize]byte)(&key)
out := (*[PublicKeySize]byte)(&pub)
curve25519.ScalarBaseMult(out, in)
} }
return pub
out := [PublicKeySize]byte{}
in := (*[PrivateKeySize]byte)(key)
curve25519.ScalarBaseMult(&out, in)
return out[:]
} }
// KeyPair holds a Key pair // KeyPair holds a Key pair
@@ -207,13 +158,15 @@ func (kp *KeyPair) Validate() error {
} }
// NewKeyPair creates a new KeyPair for Wireguard // NewKeyPair creates a new KeyPair for Wireguard
func NewKeyPair() (KeyPair, error) { func NewKeyPair() (*KeyPair, error) {
var out KeyPair
key, err := NewPrivateKey() key, err := NewPrivateKey()
if err == nil { if err != nil {
out.PrivateKey = key return nil, err
out.PublicKey = key.Public() }
out := &KeyPair{
PrivateKey: key,
PublicKey: key.Public(),
} }
return out, nil return out, nil
} }
+19 -11
View File
@@ -32,14 +32,11 @@ func (m *Zones) writeEnvZone(w io.Writer, z *Zone) {
// ZONE{zoneID}_GW // ZONE{zoneID}_GW
gatewayID := getRingZeroGatewayID(z) gatewayID := getRingZeroGatewayID(z)
if gatewayID > 0 { m.writeEnvVar(w, fmt.Sprintf("%v", gatewayID), "ZONE%v_%s", zoneID, "GW")
m.writeEnvVar(w, fmt.Sprintf("%v", gatewayID), "ZONE%v_%s", zoneID, "GW")
// ZONE{zoneID}_IP // ZONE{zoneID}_IP
if ip, ok := RingZeroAddress(zoneID, gatewayID); ok { ip, _ := RingZeroAddress(zoneID, gatewayID)
m.writeEnvVar(w, ip.String(), "ZONE%v_%s", zoneID, "IP") m.writeEnvVar(w, ip.String(), "ZONE%v_%s", zoneID, "IP")
}
}
} }
func (m *Zones) writeEnvVarFn(w io.Writer, fn func(*Zones) string, name string, args ...any) { func (m *Zones) writeEnvVarFn(w io.Writer, fn func(*Zones) string, name string, args ...any) {
@@ -82,15 +79,26 @@ func genEnvZoneNodes(z *Zone) string {
} }
func getRingZeroGatewayID(z *Zone) int { func getRingZeroGatewayID(z *Zone) int {
var gatewayID int var firstNodeID, gatewayID int
z.ForEachMachine(func(p *Machine) bool { z.ForEachMachine(func(p *Machine) bool {
if p.IsGateway() { nodeID := p.ID()
gatewayID = p.ID
if firstNodeID == 0 {
firstNodeID = nodeID
}
if _, found := p.getRingInfo(0); found {
gatewayID = nodeID
} }
return gatewayID != 0 return gatewayID != 0
}) })
return gatewayID switch {
case gatewayID == 0:
return firstNodeID
default:
return gatewayID
}
} }
+29 -37
View File
@@ -2,27 +2,48 @@ package zones
import ( import (
"net/netip" "net/netip"
"strconv"
"strings" "strings"
"sync"
) )
// revive:disable:line-length-limit
// A Machine is a machine on a Zone // A Machine is a machine on a Zone
type Machine struct { type Machine struct {
mu sync.Mutex
zone *Zone zone *Zone
ID int `toml:"id"` id int
Name string `toml:"-" json:"-" yaml:"-"` Name string `toml:"name"`
PublicAddresses []netip.Addr `toml:"public,omitempty" json:"public,omitempty" yaml:"public,omitempty"` PublicAddresses []netip.Addr `toml:"public,omitempty"`
Rings []*RingInfo `toml:"rings,omitempty" json:"rings,omitempty" yaml:"rings,omitempty"` RingAddresses []*RingInfo `toml:"rings,omitempty"`
} }
// revive:enable:line-length-limit
func (m *Machine) String() string { func (m *Machine) String() string {
return m.Name return m.Name
} }
// ID return the index within the [Zone] associated to this [Machine]
func (m *Machine) ID() int {
m.mu.Lock()
defer m.mu.Unlock()
if m.id == 0 {
zoneName := m.zone.Name
s := m.Name[len(zoneName)+1:]
id, err := strconv.ParseInt(s, 10, 8)
if err != nil {
panic(err)
}
m.id = int(id)
}
return m.id
}
// FullName returns the Name of the machine including domain name // FullName returns the Name of the machine including domain name
func (m *Machine) FullName() string { func (m *Machine) FullName() string {
if domain := m.zone.zones.domain; domain != "" { if domain := m.zone.zones.domain; domain != "" {
@@ -37,35 +58,6 @@ func (m *Machine) FullName() string {
return m.Name return m.Name
} }
// IsGateway tells if the Machine is a ring0 gateway
func (m *Machine) IsGateway() bool {
_, ok := m.getRingInfo(0)
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
}
func (m *Machine) getPeerByName(name string) (*Machine, bool) { func (m *Machine) getPeerByName(name string) (*Machine, bool) {
return m.zone.zones.GetMachineByName(name) return m.zone.zones.GetMachineByName(name)
} }
+4 -28
View File
@@ -1,9 +1,7 @@
package zones package zones
import ( import (
"bytes"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath" "path/filepath"
@@ -19,22 +17,13 @@ func (m *Machine) OpenFile(name string, flags int, args ...any) (fs.File, error)
} }
// CreateTruncFile creates or truncates a file on the machine's config directory // CreateTruncFile creates or truncates a file on the machine's config directory
func (m *Machine) CreateTruncFile(name string, args ...any) (io.WriteCloser, error) { func (m *Machine) CreateTruncFile(name string, args ...any) (fs.File, error) {
return m.openWriter(name, os.O_CREATE|os.O_TRUNC, args...) return m.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, args...)
} }
// CreateFile creates a file on the machine's config directory // CreateFile creates a file on the machine's config directory
func (m *Machine) CreateFile(name string, args ...any) (io.WriteCloser, error) { func (m *Machine) CreateFile(name string, args ...any) (fs.File, error) {
return m.openWriter(name, os.O_CREATE, args...) return m.OpenFile(name, os.O_RDWR|os.O_CREATE, args...)
}
func (m *Machine) openWriter(name string, flags int, args ...any) (io.WriteCloser, error) {
f, err := m.OpenFile(name, os.O_WRONLY|flags, args...)
if err != nil {
return nil, err
}
return f.(io.WriteCloser), nil
} }
// RemoveFile deletes a file from the machine's config directory // RemoveFile deletes a file from the machine's config directory
@@ -59,19 +48,6 @@ func (m *Machine) ReadFile(name string, args ...any) ([]byte, error) {
return fs.ReadFile(base, fullName) return fs.ReadFile(base, fullName)
} }
// WriteStringFile writes the given content to a file on the machine's config directory
func (m *Machine) WriteStringFile(value string, name string, args ...any) error {
f, err := m.CreateTruncFile(name, args...)
if err != nil {
return err
}
defer f.Close()
buf := bytes.NewBufferString(value)
_, err = buf.WriteTo(f)
return err
}
func (m *Machine) getFilename(name string, args ...any) string { func (m *Machine) getFilename(name string, args ...any) string {
if len(args) > 0 { if len(args) > 0 {
name = fmt.Sprintf(name, args...) name = fmt.Sprintf(name, args...)
+10 -124
View File
@@ -10,90 +10,6 @@ import (
"git.jpi.io/amery/jpictl/pkg/wireguard" "git.jpi.io/amery/jpictl/pkg/wireguard"
) )
// GetWireguardKeys reads a wgN.key/wgN.pub files
func (m *Machine) GetWireguardKeys(ring int) (wireguard.KeyPair, error) {
var (
data []byte
err error
out wireguard.KeyPair
)
data, err = m.ReadFile("wg%v.key", ring)
if err != nil {
// failed to read
return out, err
}
out.PrivateKey, err = wireguard.PrivateKeyFromBase64(string(data))
if err != nil {
// bad key
err = core.Wrapf(err, "wg%v.key", ring)
return out, err
}
data, err = m.ReadFile("wg%v.pub", ring)
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.Wrapf(err, "wg%v.pub", ring)
return out, err
}
}
err = out.Validate()
return out, err
}
func (m *Machine) tryReadWireguardKeys(ring int) error {
kp, err := m.GetWireguardKeys(ring)
switch {
case os.IsNotExist(err):
// ignore
return nil
case err != nil:
// something went wrong
return err
default:
// import keys
ri := &RingInfo{
Ring: ring,
Keys: kp,
}
return m.applyRingInfo(ring, ri)
}
}
// RemoveWireguardKeys deletes wgN.key and wgN.pub from
// the machine's config directory
func (m *Machine) RemoveWireguardKeys(ring int) error {
var err error
err = m.RemoveFile("wg%v.pub", ring)
switch {
case os.IsNotExist(err):
// ignore
case err != nil:
return err
}
err = m.RemoveFile("wg%v.key", ring)
if os.IsNotExist(err) {
// ignore
err = nil
}
return err
}
// GetWireguardConfig reads a wgN.conf file // GetWireguardConfig reads a wgN.conf file
func (m *Machine) GetWireguardConfig(ring int) (*wireguard.Config, error) { func (m *Machine) GetWireguardConfig(ring int) (*wireguard.Config, error) {
data, err := m.ReadFile("wg%v.conf", ring) data, err := m.ReadFile("wg%v.conf", ring)
@@ -145,9 +61,9 @@ func (m *Machine) applyWireguardConfig(ring int, wg *wireguard.Config) error {
} }
func (m *Machine) getRingInfo(ring int) (*RingInfo, bool) { func (m *Machine) getRingInfo(ring int) (*RingInfo, bool) {
for _, ri := range m.Rings { for _, ri := range m.RingAddresses {
if ri.Ring == ring { if ri.Ring == ring {
return ri, ri.Enabled return ri, true
} }
} }
@@ -155,10 +71,10 @@ func (m *Machine) getRingInfo(ring int) (*RingInfo, bool) {
} }
func (m *Machine) applyRingInfo(ring int, new *RingInfo) error { func (m *Machine) applyRingInfo(ring int, new *RingInfo) error {
cur, _ := m.getRingInfo(ring) cur, found := m.getRingInfo(ring)
if cur == nil { if !found {
// first, append // first, append
m.Rings = append(m.Rings, new) m.RingAddresses = append(m.RingAddresses, new)
return nil return nil
} }
@@ -170,7 +86,8 @@ func (m *Machine) applyWireguardInterfaceConfig(ring int, data wireguard.Interfa
ri := &RingInfo{ ri := &RingInfo{
Ring: ring, Ring: ring,
Enabled: true, Enabled: true,
Keys: wireguard.KeyPair{ Address: data.Address,
Keys: &wireguard.KeyPair{
PrivateKey: data.PrivateKey, PrivateKey: data.PrivateKey,
}, },
} }
@@ -190,7 +107,7 @@ func (m *Machine) applyWireguardPeerConfig(ring int, pc wireguard.PeerConfig) er
ri := &RingInfo{ ri := &RingInfo{
Ring: ring, Ring: ring,
Enabled: true, Enabled: true,
Keys: wireguard.KeyPair{ Keys: &wireguard.KeyPair{
PublicKey: pc.PublicKey, PublicKey: pc.PublicKey,
}, },
} }
@@ -207,8 +124,8 @@ func (m *Machine) applyZoneNodeID(zoneID, nodeID int) error {
return fmt.Errorf("invalid %s", "zoneID") return fmt.Errorf("invalid %s", "zoneID")
case nodeID == 0: case nodeID == 0:
return fmt.Errorf("invalid %s", "nodeID") return fmt.Errorf("invalid %s", "nodeID")
case m.ID != nodeID: case m.ID() != nodeID:
return fmt.Errorf("invalid %s: %v ≠ %v", "zoneID", m.ID, nodeID) return fmt.Errorf("invalid %s: %v ≠ %v", "zoneID", m.ID(), nodeID)
case m.zone.ID != 0 && m.zone.ID != zoneID: case m.zone.ID != 0 && m.zone.ID != zoneID:
return fmt.Errorf("invalid %s: %v ≠ %v", "zoneID", m.zone.ID, zoneID) return fmt.Errorf("invalid %s: %v ≠ %v", "zoneID", m.zone.ID, zoneID)
case m.zone.ID == 0: case m.zone.ID == 0:
@@ -217,34 +134,3 @@ func (m *Machine) applyZoneNodeID(zoneID, nodeID int) error {
return nil return nil
} }
// RemoveWireguardConfig deletes wgN.conf from the machine's
// config directory.
func (m *Machine) RemoveWireguardConfig(ring int) error {
err := m.RemoveFile("wg%v.conf", ring)
if os.IsNotExist(err) {
err = nil
}
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
}
-27
View File
@@ -3,7 +3,6 @@ package zones
import ( import (
"context" "context"
"net/netip" "net/netip"
"strconv"
"time" "time"
) )
@@ -26,32 +25,6 @@ func (m *Machine) updatePublicAddresses() error {
return nil return nil
} }
func (m *Machine) init() error {
if err := m.setID(); err != nil {
return err
}
for i := 0; i < RingsCount; i++ {
if err := m.tryReadWireguardKeys(i); err != nil {
return err
}
}
return nil
}
func (m *Machine) setID() error {
zoneName := m.zone.Name
suffix := m.Name[len(zoneName)+1:]
id, err := strconv.ParseInt(suffix, 10, 8)
if err != nil {
return err
}
m.ID = int(id)
return nil
}
func (m *Machine) scan() error { func (m *Machine) scan() error {
for i := 0; i < RingsCount; i++ { for i := 0; i < RingsCount; i++ {
if err := m.tryApplyWireguardConfig(i); err != nil { if err := m.tryApplyWireguardConfig(i); err != nil {
+44 -198
View File
@@ -2,7 +2,6 @@ package zones
import ( import (
"fmt" "fmt"
"io/fs"
"net/netip" "net/netip"
"git.jpi.io/amery/jpictl/pkg/wireguard" "git.jpi.io/amery/jpictl/pkg/wireguard"
@@ -15,58 +14,75 @@ const (
MaxNodeID = 0xff - 1 MaxNodeID = 0xff - 1
// RingsCount indicates how many wireguard rings we have // RingsCount indicates how many wireguard rings we have
RingsCount = 2 RingsCount = 2
// RingZeroPort is the port wireguard uses for ring0
RingZeroPort = 51800
// RingOnePort is the port wireguard uses for ring1
RingOnePort = 51810
) )
// RingInfo contains represents the Wireguard endpoint details // RingInfo contains represents the Wireguard endpoint details
// for a Machine on a particular ring // for a Machine on a particular ring
type RingInfo struct { type RingInfo struct {
Ring int `toml:"ring"` Ring int `toml:"ring"`
Enabled bool `toml:"enabled,omitempty"` Enabled bool `toml:"enabled,omitempty"`
Keys wireguard.KeyPair `toml:"keys,omitempty"` Keys *wireguard.KeyPair `toml:"keys,omitempty"`
Address netip.Addr `toml:"address,omitempty"`
} }
// Merge attempts to combine two RingInfo structs // Merge attempts to combine two RingInfo structs
func (ri *RingInfo) Merge(alter *RingInfo) error { func (ri *RingInfo) Merge(alter *RingInfo) error {
switch { switch {
case alter == nil:
return nil
case ri.Ring != alter.Ring: case ri.Ring != alter.Ring:
// different ring // different ring
return fmt.Errorf("invalid %s: %v ≠ %v", "ring", ri.Ring, alter.Ring) return fmt.Errorf("invalid %s: %v ≠ %v", "ring", ri.Ring, alter.Ring)
case ri.Enabled && !alter.Enabled: case ri.Enabled != alter.Enabled:
// can't disable via Merge // different state
return fmt.Errorf("invalid %s: %v %v", "enabled", ri.Enabled, alter.Enabled) return fmt.Errorf("invalid %s: %v %v", "enabled", ri.Enabled, alter.Enabled)
case !canMergeAddress(ri.Address, alter.Address):
// different address
return fmt.Errorf("invalid %s: %v ≠ %v", "address", ri.Address, alter.Address)
case !canMergeKeyPairs(ri.Keys, alter.Keys): case !canMergeKeyPairs(ri.Keys, alter.Keys):
// incompatible keypairs // incompatible keypairs
return fmt.Errorf("invalid %s: %s ≠ %s", "keys", ri.Keys, alter.Keys) return fmt.Errorf("invalid %s: %s ≠ %s", "keys", ri.Keys, alter.Keys)
} }
return ri.unsafeMerge(alter) switch {
} case ri.Keys == nil:
// assign keypair
func (ri *RingInfo) unsafeMerge(alter *RingInfo) error { ri.Keys = alter.Keys
// enable via Merge case alter.Keys != nil:
if alter.Enabled { // fill the gaps on our keypair
ri.Enabled = true if ri.Keys.PrivateKey.IsZero() {
ri.Keys.PrivateKey = alter.Keys.PrivateKey
}
if ri.Keys.PublicKey.IsZero() {
ri.Keys.PublicKey = alter.Keys.PublicKey
}
} }
// fill the gaps on our keypair if addressEqual(ri.Address, netip.Addr{}) {
if ri.Keys.PrivateKey.IsZero() { // assign address
ri.Keys.PrivateKey = alter.Keys.PrivateKey ri.Address = alter.Address
}
if ri.Keys.PublicKey.IsZero() {
ri.Keys.PublicKey = alter.Keys.PublicKey
} }
return nil return nil
} }
func canMergeKeyPairs(p1, p2 wireguard.KeyPair) bool { func canMergeAddress(ip1, ip2 netip.Addr) bool {
var zero netip.Addr
switch { switch {
case addressEqual(ip1, zero) || addressEqual(ip2, zero) || addressEqual(ip1, ip2):
return true
default:
return false
}
}
func addressEqual(ip1, ip2 netip.Addr) bool {
return ip1.Compare(ip2) == 0
}
func canMergeKeyPairs(p1, p2 *wireguard.KeyPair) bool {
switch {
case p1 == nil || p2 == nil:
return true
case !p1.PrivateKey.IsZero() && !p2.PrivateKey.IsZero() && !p1.PrivateKey.Equal(p2.PrivateKey): case !p1.PrivateKey.IsZero() && !p2.PrivateKey.IsZero() && !p1.PrivateKey.Equal(p2.PrivateKey):
return false return false
case !p1.PublicKey.IsZero() && !p2.PublicKey.IsZero() && !p1.PublicKey.Equal(p2.PublicKey): case !p1.PublicKey.IsZero() && !p2.PublicKey.IsZero() && !p1.PublicKey.Equal(p2.PublicKey):
@@ -80,7 +96,6 @@ func canMergeKeyPairs(p1, p2 wireguard.KeyPair) bool {
// Wireguard ring // Wireguard ring
type RingAddressEncoder struct { type RingAddressEncoder struct {
ID int ID int
Port uint16
Encode func(zoneID, nodeID int) (netip.Addr, bool) Encode func(zoneID, nodeID int) (netip.Addr, bool)
Decode func(addr netip.Addr) (zoneID, nodeID int, ok bool) Decode func(addr netip.Addr) (zoneID, nodeID int, ok bool)
} }
@@ -89,14 +104,12 @@ var (
// RingZero is a wg0 address encoder/decoder // RingZero is a wg0 address encoder/decoder
RingZero = RingAddressEncoder{ RingZero = RingAddressEncoder{
ID: 0, ID: 0,
Port: RingZeroPort,
Decode: ParseRingZeroAddress, Decode: ParseRingZeroAddress,
Encode: RingZeroAddress, Encode: RingZeroAddress,
} }
// RingOne is a wg1 address encoder/decoder // RingOne is a wg1 address encoder/decoder
RingOne = RingAddressEncoder{ RingOne = RingAddressEncoder{
ID: 1, ID: 1,
Port: RingOnePort,
Decode: ParseRingOneAddress, Decode: ParseRingOneAddress,
Encode: RingOneAddress, Encode: RingOneAddress,
} }
@@ -176,174 +189,7 @@ func RingOneAddress(zoneID, nodeID int) (netip.Addr, bool) {
case !ValidZoneID(zoneID) || !ValidNodeID(nodeID): case !ValidZoneID(zoneID) || !ValidNodeID(nodeID):
return netip.Addr{}, false return netip.Addr{}, false
default: default:
a4 := [4]uint8{10, uint8(zoneID << 4), 0, uint8(nodeID)} a4 := [4]uint8{10, 0, uint8(zoneID << 4), uint8(nodeID)}
return netip.AddrFrom4(a4), true return netip.AddrFrom4(a4), true
} }
} }
var (
_ MachineIterator = (*Ring)(nil)
_ ZoneIterator = (*Ring)(nil)
)
// A Ring describes all peers on a ring
type Ring struct {
RingAddressEncoder
ZoneIterator
Peers []*RingPeer
}
// AddPeer adds a [Machine] to the ring
func (r *Ring) AddPeer(p *Machine) bool {
ri, ok := p.getRingInfo(r.ID)
if !ok {
return false
}
nodeID := p.ID
zoneID := p.Zone()
addr, _ := r.Encode(zoneID, nodeID)
rp := &RingPeer{
Node: p,
Address: addr,
PrivateKey: ri.Keys.PrivateKey,
PeerConfig: wireguard.PeerConfig{
Name: fmt.Sprintf("%s-%v", p.Name, r.ID),
PublicKey: ri.Keys.PublicKey,
Endpoint: wireguard.EndpointAddress{
Host: p.FullName(),
Port: r.Port,
},
},
}
switch {
case r.ID == 0:
r.setRingZeroAllowedIPs(rp)
case p.IsGateway():
r.setRingOneGatewayAllowedIPs(rp)
default:
r.setRingOneNodeAllowedIPs(rp)
}
r.Peers = append(r.Peers, rp)
return true
}
func (r *Ring) setRingZeroAllowedIPs(rp *RingPeer) {
zoneID, _, _ := r.Decode(rp.Address)
// everyone on ring0 is a gateway to ring1
addr, _ := RingOneAddress(zoneID, 0)
rp.AllowCIDR(addr, 12)
// peer
rp.AllowCIDR(rp.Address, 32)
}
func (r *Ring) setRingOneGatewayAllowedIPs(rp *RingPeer) {
zoneID, _, _ := r.Decode(rp.Address)
// peer
rp.AllowCIDR(rp.Address, 32)
// ring1 gateways connect to all other ring1 networks
r.ForEachZone(func(z *Zone) bool {
if z.ID != zoneID {
addr, _ := r.Encode(z.ID, 0)
rp.AllowCIDR(addr, 12)
}
return false
})
// ring1 gateways also connect to all ring0 addresses
r.ForEachZone(func(z *Zone) bool {
z.ForEachMachine(func(p *Machine) bool {
if p.IsGateway() {
addr, _ := RingZeroAddress(z.ID, p.ID)
rp.AllowCIDR(addr, 32)
}
return false
})
return false
})
}
func (*Ring) setRingOneNodeAllowedIPs(rp *RingPeer) {
// only to the peer itself
rp.AllowCIDR(rp.Address, 32)
}
// 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 builds a wgN.conf for the specified machine on the ring
func (r *Ring) ExportConfig(p *Machine) (*wireguard.Config, error) {
var found bool
out := &wireguard.Config{
Interface: wireguard.InterfaceConfig{
ListenPort: r.Port,
},
}
for _, pp := range r.Peers {
switch {
case pp.Node == p:
// current
found = true
out.Interface.Name = pp.PeerConfig.Name
out.Interface.Address = pp.Address
out.Interface.PrivateKey = pp.PrivateKey
default:
// peer
pc := pp.PeerConfig
out.Peer = append(out.Peer, pc)
}
}
if !found {
return nil, fs.ErrNotExist
}
return out, nil
}
// A RingPeer is a node on a [Ring]
type RingPeer struct {
Node *Machine
Address netip.Addr
PrivateKey wireguard.PrivateKey
PeerConfig wireguard.PeerConfig
}
// AllowCIDR allows an IP range via this peer
func (rp *RingPeer) AllowCIDR(addr netip.Addr, bits int) {
cidr := netip.PrefixFrom(addr, bits)
rp.PeerConfig.AllowedIPs = append(rp.PeerConfig.AllowedIPs, cidr)
}
// NewRing composes a new Ring for Wireguard setup
func NewRing(z ZoneIterator, m MachineIterator, ring int) (*Ring, error) {
r := &Ring{
RingAddressEncoder: Rings[ring],
ZoneIterator: z,
}
m.ForEachMachine(func(p *Machine) bool {
r.AddPeer(p)
return false
})
return r, nil
}
+5 -58
View File
@@ -11,7 +11,6 @@ func (m *Zones) scan() error {
m.scanMachines, m.scanMachines,
m.scanZoneIDs, m.scanZoneIDs,
m.scanSort, m.scanSort,
m.scanGateways,
} { } {
if err := fn(); err != nil { if err := fn(); err != nil {
return err return err
@@ -94,8 +93,8 @@ func (m *Zones) scanSort() error {
m.ForEachZone(func(z *Zone) bool { m.ForEachZone(func(z *Zone) bool {
sort.SliceStable(z.Machines, func(i, j int) bool { sort.SliceStable(z.Machines, func(i, j int) bool {
id1 := z.Machines[i].ID id1 := z.Machines[i].ID()
id2 := z.Machines[j].ID id2 := z.Machines[j].ID()
return id1 < id2 return id1 < id2
}) })
@@ -103,9 +102,9 @@ func (m *Zones) scanSort() error {
}) })
m.ForEachMachine(func(p *Machine) bool { m.ForEachMachine(func(p *Machine) bool {
sort.SliceStable(p.Rings, func(i, j int) bool { sort.SliceStable(p.RingAddresses, func(i, j int) bool {
ri1 := p.Rings[i] ri1 := p.RingAddresses[i]
ri2 := p.Rings[j] ri2 := p.RingAddresses[j]
return ri1.Ring < ri2.Ring return ri1.Ring < ri2.Ring
}) })
@@ -116,16 +115,6 @@ func (m *Zones) scanSort() error {
return nil return nil
} }
func (m *Zones) scanGateways() error {
var err error
m.ForEachZone(func(z *Zone) bool {
_, _, err = z.GetGateway()
return err != nil
})
return err
}
func (z *Zone) scan() error { func (z *Zone) scan() error {
// each directory is a machine // each directory is a machine
entries, err := fs.ReadDir(z.zones.dir, z.Name) entries, err := fs.ReadDir(z.zones.dir, z.Name)
@@ -140,51 +129,9 @@ func (z *Zone) scan() error {
Name: e.Name(), Name: e.Name(),
} }
if err := m.init(); err != nil {
return err
}
z.Machines = append(z.Machines, m) z.Machines = append(z.Machines, m)
} }
} }
return nil return nil
} }
// GetGateway returns the first gateway found, if none
// files will be created to enable the first [Machine] to
// be one
func (z *Zone) GetGateway() (*Machine, bool, error) {
var first *Machine
var gateway *Machine
z.zones.ForEachMachine(func(p *Machine) bool {
switch {
case p.IsGateway():
// found
gateway = p
case first == nil:
// remember
first = p
default:
// keep looking
}
return gateway != nil
})
switch {
case gateway != nil:
// found one
return gateway, false, nil
case first != nil:
// make one
if err := first.SetGateway(true); err != nil {
return first, false, err
}
return first, true, nil
default:
// Zone without nodes?
panic("unreachable")
}
}
-33
View File
@@ -1,33 +0,0 @@
package zones
// SyncAll updates all config files
func (m *Zones) SyncAll() error {
for _, fn := range []func() error{
m.SyncAllWireguard,
} {
if err := fn(); err != nil {
return err
}
}
return nil
}
// SyncAllWireguard updates all wireguard config files
func (m *Zones) SyncAllWireguard() error {
var err error
for ring := 0; ring < RingsCount; ring++ {
err = m.WriteWireguardKeys(ring)
if err != nil {
return err
}
err = m.SyncWireguardConfig(ring)
if err != nil {
return err
}
}
return nil
}
-272
View File
@@ -1,272 +0,0 @@
package zones
import (
"io/fs"
"os"
)
var (
_ WireguardConfigPruner = (*Zones)(nil)
_ WireguardConfigPruner = (*Zone)(nil)
_ WireguardConfigPruner = (*Machine)(nil)
_ WireguardConfigWriter = (*Zones)(nil)
_ WireguardConfigWriter = (*Zone)(nil)
_ WireguardConfigWriter = (*Machine)(nil)
_ WireguardConfigSyncer = (*Zones)(nil)
_ WireguardConfigSyncer = (*Zone)(nil)
_ WireguardConfigSyncer = (*Machine)(nil)
_ WireguardKeysWriter = (*Zones)(nil)
_ WireguardKeysWriter = (*Zone)(nil)
_ WireguardKeysWriter = (*Machine)(nil)
)
// A WireguardConfigPruner deletes wgN.conf on all machines under
// its scope with the specified ring disabled
type WireguardConfigPruner interface {
PruneWireguardConfig(ring int) error
}
// PruneWireguardConfig removes wgN.conf files of machines with
// the corresponding ring disabled on all zones
func (m *Zones) PruneWireguardConfig(ring int) error {
return pruneWireguardConfig(m, ring)
}
// PruneWireguardConfig removes wgN.conf files of machines with
// the corresponding ring disabled.
func (z *Zone) PruneWireguardConfig(ring int) error {
return pruneWireguardConfig(z, ring)
}
func pruneWireguardConfig(m MachineIterator, ring int) error {
var err error
m.ForEachMachine(func(p *Machine) bool {
err = p.PruneWireguardConfig(ring)
if os.IsNotExist(err) {
// ignore
err = nil
}
return err != nil
})
return err
}
// PruneWireguardConfig deletes the wgN.conf file if its
// presence on the ring is disabled
func (m *Machine) PruneWireguardConfig(ring int) error {
_, ok := m.getRingInfo(ring)
if !ok {
return m.RemoveWireguardConfig(ring)
}
return nil
}
// A WireguardConfigWriter rewrites all wgN.conf on all machines under
// its scope attached to that ring
type WireguardConfigWriter interface {
WriteWireguardConfig(ring int) error
}
// WriteWireguardConfig rewrites all wgN.conf on all machines
// attached to that ring
func (m *Zones) WriteWireguardConfig(ring int) error {
switch ring {
case 0:
return writeWireguardConfig(m, m, ring)
case 1:
var err error
m.ForEachZone(func(z *Zone) bool {
err = writeWireguardConfig(m, z, ring)
return err != nil
})
return err
default:
return fs.ErrInvalid
}
}
// WriteWireguardConfig rewrites all wgN.conf on all machines
// on the Zone attached to that ring
func (z *Zone) WriteWireguardConfig(ring int) error {
switch ring {
case 0:
return writeWireguardConfig(z.zones, z.zones, ring)
case 1:
return writeWireguardConfig(z.zones, z, ring)
default:
return fs.ErrInvalid
}
}
func writeWireguardConfig(z ZoneIterator, m MachineIterator, ring int) error {
r, err := NewRing(z, m, ring)
if err != nil {
return err
}
r.ForEachMachine(func(p *Machine) bool {
err = p.writeWireguardRingConfig(r)
return err != nil
})
return err
}
// WriteWireguardConfig rewrites the wgN.conf file of this Machine
// if enabled
func (m *Machine) WriteWireguardConfig(ring int) error {
r, err := NewRing(m.zone.zones, 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
}
// A WireguardConfigSyncer updates all wgN.conf on all machines under
// its scope reflecting the state of the ring
type WireguardConfigSyncer interface {
SyncWireguardConfig(ring int) error
}
// SyncWireguardConfig updates all wgN.conf files for the specified
// ring
func (m *Zones) SyncWireguardConfig(ring int) error {
switch ring {
case 0:
return syncWireguardConfig(m, m, ring)
case 1:
var err error
m.ForEachZone(func(z *Zone) bool {
err = syncWireguardConfig(m, z, ring)
return err != nil
})
return err
default:
return fs.ErrInvalid
}
}
// 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, z.zones, ring)
case 1:
return syncWireguardConfig(z.zones, z, ring)
default:
return fs.ErrInvalid
}
}
func syncWireguardConfig(z ZoneIterator, m MachineIterator, ring int) error {
r, err := NewRing(z, m, ring)
if err != nil {
return err
}
r.ForEachMachine(func(p *Machine) bool {
if _, ok := p.getRingInfo(ring); ok {
err = p.writeWireguardRingConfig(r)
} else {
err = p.RemoveWireguardConfig(ring)
}
return err != nil
})
return err
}
// SyncWireguardConfig updates all wgN.conf files for the specified
// ring
func (m *Machine) SyncWireguardConfig(ring int) error {
return m.zone.SyncWireguardConfig(ring)
}
// A WireguardKeysWriter writes the Wireguard Keys for all machines
// under its scope for the specified ring
type WireguardKeysWriter interface {
WriteWireguardKeys(ring int) error
}
// WriteWireguardKeys rewrites all wgN.{key,pub} files
func (m *Zones) WriteWireguardKeys(ring int) error {
return writeWireguardKeys(m, ring)
}
// WriteWireguardKeys rewrites all wgN.{key,pub} files on this zone
func (z *Zone) WriteWireguardKeys(ring int) error {
return writeWireguardKeys(z, ring)
}
func writeWireguardKeys(m MachineIterator, ring int) error {
var err error
m.ForEachMachine(func(p *Machine) bool {
err = p.WriteWireguardKeys(ring)
if os.IsNotExist(err) {
// ignore
err = nil
}
return err != nil
})
return err
}
// WriteWireguardKeys writes the wgN.key/wgN.pub files
func (m *Machine) WriteWireguardKeys(ring int) error {
var err error
var key, pub string
var ri *RingInfo
ri, _ = m.getRingInfo(ring)
if ri != nil {
key = ri.Keys.PrivateKey.String()
pub = ri.Keys.PublicKey.String()
}
switch {
case key == "":
return fs.ErrNotExist
case pub == "":
pub = ri.Keys.PrivateKey.Public().String()
}
err = m.WriteStringFile(key+"\n", "wg%v.key", ring)
if err != nil {
return err
}
err = m.WriteStringFile(pub+"\n", "wg%v.pub", ring)
if err != nil {
return err
}
return nil
}
+3 -50
View File
@@ -3,35 +3,18 @@ package zones
import ( import (
"io/fs" "io/fs"
"path/filepath"
"github.com/hack-pad/hackpadfs/os" "github.com/hack-pad/hackpadfs/os"
"darvaza.org/resolver" "darvaza.org/resolver"
) )
var (
_ MachineIterator = (*Zone)(nil)
_ MachineIterator = (*Zones)(nil)
_ ZoneIterator = (*Zones)(nil)
)
// A MachineIterator is a set of Machines we can iterate on
type MachineIterator interface {
ForEachMachine(func(*Machine) bool)
}
// A ZoneIterator is a set of Zones we can iterate on
type ZoneIterator interface {
ForEachZone(func(*Zone) bool)
}
// Zone represents one zone in a cluster // Zone represents one zone in a cluster
type Zone struct { type Zone struct {
zones *Zones zones *Zones
ID int `toml:"id"` ID int
Name string `toml:"name"` Name string
Machines []*Machine `toml:"machines"` Machines []*Machine `toml:"machines"`
} }
@@ -50,31 +33,6 @@ func (z *Zone) ForEachMachine(fn func(*Machine) bool) {
} }
} }
// SetGateway configures a machine to be the zone's ring0 gateway
func (z *Zone) SetGateway(gatewayID int, enabled bool) error {
var err error
var found bool
z.ForEachMachine(func(p *Machine) bool {
if p.ID == gatewayID {
found = true
err = p.SetGateway(enabled)
return true
}
return false
})
switch {
case err != nil:
return err
case !found:
return fs.ErrNotExist
default:
return nil
}
}
// Zones represents all zones in a cluster // Zones represents all zones in a cluster
type Zones struct { type Zones struct {
dir fs.FS dir fs.FS
@@ -147,12 +105,7 @@ func NewFS(dir fs.FS, domain string) (*Zones, error) {
// New builds a [Zones] tree using the given directory // New builds a [Zones] tree using the given directory
func New(dir, domain string) (*Zones, error) { func New(dir, domain string) (*Zones, error) {
dir, err := filepath.Abs(dir) base, err := os.NewFS().Sub(dir)
if err != nil {
return nil, err
}
base, err := os.NewFS().Sub(dir[1:])
if err != nil { if err != nil {
return nil, err return nil, err
} }