Compare commits

...

10 Commits

Author SHA1 Message Date
amery c81b782b26 zones: Machine.IsGateway()
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-24 21:07:55 +00:00
amery 0f62ee2e53 zones: rename Machine.RingAddresses to Machine.Rings
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-24 20:52:08 +00:00
amery 30a7bceda3 wireguard: make KeyPairs solid
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-24 19:49:59 +00:00
amery 60e2687d04 wireguard: make keys arrays instead of slices
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-24 19:35:11 +00:00
amery 1419e55d5b zones: remove useless RingInfo.Address
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-24 19:06:33 +00:00
amery ffdacb833b zones: add Port information to RingAddressEncoder
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-24 17:56:19 +00:00
amery aca0a5e834 zones: calculate Machine.ID on init
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-24 17:55:43 +00:00
amery 61374d4cc5 zones: load wireguard key pairs on Machine.init()
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-24 14:35:40 +00:00
amery 975e166da7 zones: allow RingInfo.Merge() to enable, but not disable
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-24 14:34:00 +00:00
amery b16c648f2c zones: introduce Machine.GetWireguardKeys()
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-23 21:03:03 +00:00
7 changed files with 192 additions and 126 deletions
+45 -36
View File
@@ -27,60 +27,71 @@ var (
type (
// PrivateKey is a binary Wireguard Private Key
PrivateKey []byte
PrivateKey [PrivateKeySize]byte
// PublicKey is a binary Wireguard Public Key
PublicKey []byte
PublicKey [PublicKeySize]byte
)
func (key PrivateKey) String() string {
return encodeKey(key)
switch {
case key.IsZero():
return ""
default:
return base64.StdEncoding.EncodeToString(key[:])
}
}
func (pub PublicKey) String() string {
return encodeKey(pub)
switch {
case pub.IsZero():
return ""
default:
return base64.StdEncoding.EncodeToString(pub[:])
}
}
// IsZero tells if the key hasn't been set
func (key PrivateKey) IsZero() bool {
return len(key) == 0
var zero PrivateKey
return key.Equal(zero)
}
// IsZero tells if the key hasn't been set
func (pub PublicKey) IsZero() bool {
return len(pub) == 0
var zero PublicKey
return pub.Equal(zero)
}
// Equal checks if two private keys are identical
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
func (pub PublicKey) Equal(alter PublicKey) bool {
return bytes.Equal(pub, alter)
return bytes.Equal(pub[:], alter[:])
}
// PrivateKeyFromBase64 decodes a base64-based string into
// a [PrivateKey]
func PrivateKeyFromBase64(data string) (PrivateKey, error) {
b, err := decodeKey(data, PrivateKeySize)
return b, err
if err != nil {
var zero PrivateKey
return zero, err
}
return *(*[PrivateKeySize]byte)(b), nil
}
// PublicKeyFromBase64 decodes a base64-based string into
// a [PublicKey]
func PublicKeyFromBase64(data string) (PublicKey, error) {
b, err := decodeKey(data, PublicKeySize)
return b, err
}
func encodeKey(b []byte) string {
switch {
case len(b) == 0:
return ""
default:
return base64.StdEncoding.EncodeToString(b)
if err != nil {
var zero PublicKey
return zero, err
}
return *(*[PublicKeySize]byte)(b), nil
}
func decodeKey(data string, size int) ([]byte, error) {
@@ -102,27 +113,27 @@ func NewPrivateKey() (PrivateKey, error) {
_, err := rand.Read(s[:])
if err != nil {
return []byte{}, err
var zero PrivateKey
return zero, err
}
// apply same clamping as wireguard-go/device/noise-helpers.go
s[0] &= 0xf8
s[31] = (s[31] & 0x7f) | 0x40
return s[:], nil
return s, nil
}
// Public generates the corresponding PublicKey
func (key PrivateKey) Public() PublicKey {
if len(key) != PrivateKeySize {
return []byte{}
var pub PublicKey
if !key.IsZero() {
in := (*[PrivateKeySize]byte)(&key)
out := (*[PublicKeySize]byte)(&pub)
curve25519.ScalarBaseMult(out, in)
}
out := [PublicKeySize]byte{}
in := (*[PrivateKeySize]byte)(key)
curve25519.ScalarBaseMult(&out, in)
return out[:]
return pub
}
// KeyPair holds a Key pair
@@ -158,15 +169,13 @@ func (kp *KeyPair) Validate() error {
}
// NewKeyPair creates a new KeyPair for Wireguard
func NewKeyPair() (*KeyPair, error) {
key, err := NewPrivateKey()
if err != nil {
return nil, err
}
func NewKeyPair() (KeyPair, error) {
var out KeyPair
out := &KeyPair{
PrivateKey: key,
PublicKey: key.Public(),
key, err := NewPrivateKey()
if err == nil {
out.PrivateKey = key
out.PublicKey = key.Public()
}
return out, nil
}
+3 -5
View File
@@ -82,14 +82,12 @@ func getRingZeroGatewayID(z *Zone) int {
var firstNodeID, gatewayID int
z.ForEachMachine(func(p *Machine) bool {
nodeID := p.ID()
if firstNodeID == 0 {
firstNodeID = nodeID
firstNodeID = p.ID
}
if _, found := p.getRingInfo(0); found {
gatewayID = nodeID
if p.IsGateway() {
gatewayID = p.ID
}
return gatewayID != 0
+8 -27
View File
@@ -5,48 +5,23 @@ import (
"io/fs"
"net/netip"
"path/filepath"
"strconv"
"strings"
"sync"
)
// A Machine is a machine on a Zone
type Machine struct {
mu sync.Mutex
zone *Zone
id int
ID int
Name string `toml:"name"`
PublicAddresses []netip.Addr `toml:"public,omitempty"`
RingAddresses []*RingInfo `toml:"rings,omitempty"`
Rings []*RingInfo `toml:"rings,omitempty"`
}
func (m *Machine) String() string {
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
func (m *Machine) FullName() string {
if domain := m.zone.zones.domain; domain != "" {
@@ -83,6 +58,12 @@ func (m *Machine) getFilename(name string, args ...any) string {
return filepath.Join(s...)
}
// IsGateway tells if the Machine is a ring0 gateway
func (m *Machine) IsGateway() bool {
_, ok := m.getRingInfo(0)
return ok
}
func (m *Machine) getPeerByName(name string) (*Machine, bool) {
return m.zone.zones.GetMachineByName(name)
}
+71 -10
View File
@@ -10,6 +10,68 @@ import (
"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)
}
}
// GetWireguardConfig reads a wgN.conf file
func (m *Machine) GetWireguardConfig(ring int) (*wireguard.Config, error) {
data, err := m.ReadFile("wg%v.conf", ring)
@@ -61,9 +123,9 @@ func (m *Machine) applyWireguardConfig(ring int, wg *wireguard.Config) error {
}
func (m *Machine) getRingInfo(ring int) (*RingInfo, bool) {
for _, ri := range m.RingAddresses {
for _, ri := range m.Rings {
if ri.Ring == ring {
return ri, true
return ri, ri.Enabled
}
}
@@ -71,10 +133,10 @@ func (m *Machine) getRingInfo(ring int) (*RingInfo, bool) {
}
func (m *Machine) applyRingInfo(ring int, new *RingInfo) error {
cur, found := m.getRingInfo(ring)
if !found {
cur, _ := m.getRingInfo(ring)
if cur == nil {
// first, append
m.RingAddresses = append(m.RingAddresses, new)
m.Rings = append(m.Rings, new)
return nil
}
@@ -86,8 +148,7 @@ func (m *Machine) applyWireguardInterfaceConfig(ring int, data wireguard.Interfa
ri := &RingInfo{
Ring: ring,
Enabled: true,
Address: data.Address,
Keys: &wireguard.KeyPair{
Keys: wireguard.KeyPair{
PrivateKey: data.PrivateKey,
},
}
@@ -107,7 +168,7 @@ func (m *Machine) applyWireguardPeerConfig(ring int, pc wireguard.PeerConfig) er
ri := &RingInfo{
Ring: ring,
Enabled: true,
Keys: &wireguard.KeyPair{
Keys: wireguard.KeyPair{
PublicKey: pc.PublicKey,
},
}
@@ -124,8 +185,8 @@ func (m *Machine) applyZoneNodeID(zoneID, nodeID int) error {
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.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:
+27
View File
@@ -3,6 +3,7 @@ package zones
import (
"context"
"net/netip"
"strconv"
"time"
)
@@ -25,6 +26,32 @@ func (m *Machine) updatePublicAddresses() error {
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 {
for i := 0; i < RingsCount; i++ {
if err := m.tryApplyWireguardConfig(i); err != nil {
+29 -43
View File
@@ -14,75 +14,58 @@ const (
MaxNodeID = 0xff - 1
// RingsCount indicates how many wireguard rings we have
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
// for a Machine on a particular ring
type RingInfo struct {
Ring int `toml:"ring"`
Enabled bool `toml:"enabled,omitempty"`
Keys *wireguard.KeyPair `toml:"keys,omitempty"`
Address netip.Addr `toml:"address,omitempty"`
Ring int `toml:"ring"`
Enabled bool `toml:"enabled,omitempty"`
Keys wireguard.KeyPair `toml:"keys,omitempty"`
}
// Merge attempts to combine two RingInfo structs
func (ri *RingInfo) Merge(alter *RingInfo) error {
switch {
case alter == nil:
return nil
case ri.Ring != alter.Ring:
// different ring
return fmt.Errorf("invalid %s: %v ≠ %v", "ring", ri.Ring, alter.Ring)
case ri.Enabled != alter.Enabled:
// different state
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 ri.Enabled && !alter.Enabled:
// can't disable via Merge
return fmt.Errorf("invalid %s: %v %v", "enabled", ri.Enabled, alter.Enabled)
case !canMergeKeyPairs(ri.Keys, alter.Keys):
// incompatible keypairs
return fmt.Errorf("invalid %s: %s ≠ %s", "keys", ri.Keys, alter.Keys)
}
switch {
case ri.Keys == nil:
// assign keypair
ri.Keys = alter.Keys
case alter.Keys != nil:
// fill the gaps on our keypair
if ri.Keys.PrivateKey.IsZero() {
ri.Keys.PrivateKey = alter.Keys.PrivateKey
}
if ri.Keys.PublicKey.IsZero() {
ri.Keys.PublicKey = alter.Keys.PublicKey
}
return ri.unsafeMerge(alter)
}
func (ri *RingInfo) unsafeMerge(alter *RingInfo) error {
// enable via Merge
if alter.Enabled {
ri.Enabled = true
}
if addressEqual(ri.Address, netip.Addr{}) {
// assign address
ri.Address = alter.Address
// fill the gaps on our keypair
if ri.Keys.PrivateKey.IsZero() {
ri.Keys.PrivateKey = alter.Keys.PrivateKey
}
if ri.Keys.PublicKey.IsZero() {
ri.Keys.PublicKey = alter.Keys.PublicKey
}
return nil
}
func canMergeAddress(ip1, ip2 netip.Addr) bool {
var zero netip.Addr
func canMergeKeyPairs(p1, p2 wireguard.KeyPair) bool {
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):
return false
case !p1.PublicKey.IsZero() && !p2.PublicKey.IsZero() && !p1.PublicKey.Equal(p2.PublicKey):
@@ -96,6 +79,7 @@ func canMergeKeyPairs(p1, p2 *wireguard.KeyPair) bool {
// Wireguard ring
type RingAddressEncoder struct {
ID int
Port uint16
Encode func(zoneID, nodeID int) (netip.Addr, bool)
Decode func(addr netip.Addr) (zoneID, nodeID int, ok bool)
}
@@ -104,12 +88,14 @@ var (
// RingZero is a wg0 address encoder/decoder
RingZero = RingAddressEncoder{
ID: 0,
Port: RingZeroPort,
Decode: ParseRingZeroAddress,
Encode: RingZeroAddress,
}
// RingOne is a wg1 address encoder/decoder
RingOne = RingAddressEncoder{
ID: 1,
Port: RingOnePort,
Decode: ParseRingOneAddress,
Encode: RingOneAddress,
}
+9 -5
View File
@@ -93,8 +93,8 @@ func (m *Zones) scanSort() error {
m.ForEachZone(func(z *Zone) bool {
sort.SliceStable(z.Machines, func(i, j int) bool {
id1 := z.Machines[i].ID()
id2 := z.Machines[j].ID()
id1 := z.Machines[i].ID
id2 := z.Machines[j].ID
return id1 < id2
})
@@ -102,9 +102,9 @@ func (m *Zones) scanSort() error {
})
m.ForEachMachine(func(p *Machine) bool {
sort.SliceStable(p.RingAddresses, func(i, j int) bool {
ri1 := p.RingAddresses[i]
ri2 := p.RingAddresses[j]
sort.SliceStable(p.Rings, func(i, j int) bool {
ri1 := p.Rings[i]
ri2 := p.Rings[j]
return ri1.Ring < ri2.Ring
})
@@ -129,6 +129,10 @@ func (z *Zone) scan() error {
Name: e.Name(),
}
if err := m.init(); err != nil {
return err
}
z.Machines = append(z.Machines, m)
}
}