Compare commits
9 Commits
v0.0.1
..
a3e3cde4c4
| Author | SHA1 | Date | |
|---|---|---|---|
| a3e3cde4c4 | |||
| a4a10d0226 | |||
| 06e755ecd2 | |||
| b15f394199 | |||
| f225e15b2c | |||
| 5d946e4e93 | |||
| 979324f151 | |||
| be9da490ff | |||
| 3599812072 |
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Package wireguard deals with wireguard config
|
||||
package wireguard
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
@@ -24,3 +24,7 @@ func (m *Machine) updatePublicAddresses() error {
|
||||
m.PublicAddresses = addrs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Machine) scan() error {
|
||||
return m.updatePublicAddresses()
|
||||
}
|
||||
|
||||
+11
-5
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user