Skip to content

Commit

Permalink
allow clients to multiple addresses (v4 and v6)
Browse files Browse the repository at this point in the history
  • Loading branch information
aep committed Aug 9, 2024
1 parent f7edd37 commit 5f1c2ed
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 66 deletions.
13 changes: 10 additions & 3 deletions charts/wga/crds/wga.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,14 @@ spec:
type: string
description: Last update time
format: date-time
addresses:
type: array
items:
type: string
description: Address of the "client" peer
address:
type: string
description: Address of the "client" peer
description: "Deprecated: Address of the client peer"
dns:
type: array
description: List of DNS servers
Expand Down Expand Up @@ -120,9 +125,11 @@ spec:
- spec
additionalPrinterColumns:
- name: Address
type: string
type: array
items:
type: string
description: Address of the "client" peer
jsonPath: .status.address
jsonPath: .status.addresses
- name: DNS
type: string
description: List of DNS servers
Expand Down
2 changes: 1 addition & 1 deletion format.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ var funcs = template.FuncMap{
var wgFileTemplate = template.Must(template.New("wg-file").Funcs(funcs).Parse(WgFile))

type ConfigFile struct {
Address *net.IPNet
Address string
DNS []string
wgtypes.Device
Name string
Expand Down
87 changes: 52 additions & 35 deletions operator/nft.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ func nftInit() {
}
}

//TODO: this doesnt scale and should be replaced with a map

func nftSync(ctx context.Context, log *slog.Logger, config *Config, deviceName string) {
ruleNameToDestinations := make(map[string][]net.IPNet)
for _, rr := range config.Rules {
Expand Down Expand Up @@ -96,49 +98,64 @@ func nftSync(ctx context.Context, log *slog.Logger, config *Config, deviceName s
continue
}

snet := net.IPNet{
IP: net.ParseIP(peer.Status.Address),
Mask: net.CIDRMask(128, 128),
if len(peer.Status.Addresses) == 0 {
peer.Status.Addresses = []string{peer.Status.Address}
}

for _, name := range peer.Spec.AccessRules {
for _, dnet := range ruleNameToDestinations[name] {
for _, addr := range peer.Status.Addresses {

ip := net.ParseIP(addr)
mask := net.CIDRMask(128, 128)
if ip.To4() == nil {
mask = net.CIDRMask(128, 128)
} else {
mask = net.CIDRMask(32, 32)
}

snet := net.IPNet{
IP: ip,
Mask: mask,
}

comment := "r" + strip(snet.String()+dnet.String())
for _, name := range peer.Spec.AccessRules {
for _, dnet := range ruleNameToDestinations[name] {

exists := false
for ud := range ruleMap {
if strings.Contains(ud, comment) {
delete(ruleMap, ud)
exists = true
comment := "r" + strip(snet.String()+dnet.String())

exists := false
for ud := range ruleMap {
if strings.Contains(ud, comment) {
delete(ruleMap, ud)
exists = true
}
}
if exists {
continue
}
}
if exists {
continue
}

if len(peer.Status.DNS) == 0 {
log.ErrorContext(ctx, "peer has no DNS", "peer", peer.Name)
continue
}
if len(peer.Status.DNS) == 0 {
log.ErrorContext(ctx, "peer has no DNS", "peer", peer.Name)
continue
}

err = routingRule(ctx, table, chain, snet, dnet, comment)
if err != nil {
log.ErrorContext(ctx, "failed to add routing rule", "peer", peer.Name, "err", err)
continue
}
err = dnsRule(ctx, table, chain, peer.Status.DNS[0], snet, comment)
if err != nil {
log.ErrorContext(ctx, "failed to add dns rule", "peer", peer.Name, "err", err)
continue
}
err = httpRule(ctx, table, chain, peer.Status.DNS[0], snet, comment)
if err != nil {
log.ErrorContext(ctx, "failed to add http rule", "peer", peer.Name, "err", err)
continue
}
err = routingRule(ctx, table, chain, snet, dnet, comment)
if err != nil {
log.ErrorContext(ctx, "failed to add routing rule", "peer", peer.Name, "err", err)
continue
}
err = dnsRule(ctx, table, chain, peer.Status.DNS[0], snet, comment)
if err != nil {
log.ErrorContext(ctx, "failed to add dns rule", "peer", peer.Name, "err", err)
continue
}
err = httpRule(ctx, table, chain, peer.Status.DNS[0], snet, comment)
if err != nil {
log.ErrorContext(ctx, "failed to add http rule", "peer", peer.Name, "err", err)
continue
}

log.Debug("rules added")
log.Debug("rules added")
}
}
}
}
Expand Down
94 changes: 81 additions & 13 deletions operator/wga.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,22 +159,66 @@ type PeerReconciler struct {
}

func (r *PeerReconciler) Reconcile(ctx context.Context, peer *v1beta.WireguardAccessPeer) (ctrl.Result, error) {
if peer.Status != nil && peer.Status.Address != "" {
if peer.Status != nil && len(peer.Status.Addresses) != 0 {

if peer.Status != nil && peer.Status.Address != "" {
r.log.Info("migrating peer status Address -> Addresses", "peer", peer.Name)

peer.Status.Addresses = []string{peer.Status.Address}

err := r.client.Update(ctx, peer)
if err != nil {
slog.Error(err.Error(), "peer", peer.Name)
return ctrl.Result{}, err
}
}

return ctrl.Result{}, nil
}

r.log.Info("setting peer status", "peer", peer.Name)

cnet := randPick(r.clientsNets)
sip, err := cidr.HostBig(&cnet, generateIndex(time.Now(), maskBits(cnet)))
if err != nil {
r.log.Error(err.Error(), "peer", peer.Name)
return ctrl.Result{}, err
clientNetsV4 := []net.IPNet{}
clientNetsV6 := []net.IPNet{}

for _, cnet := range r.clientsNets {
if cnet.IP.To4() != nil {
clientNetsV4 = append(clientNetsV4, cnet)
} else {
clientNetsV6 = append(clientNetsV6, cnet)
}
}

var addrs = []string{}

if len(clientNetsV6) != 0 {
cnet := randPick(clientNetsV6)
sip, err := cidr.HostBig(&cnet, generateIndex(time.Now(), maskBits(cnet)))
if err != nil {
r.log.Error(err.Error(), "peer", peer.Name)
return ctrl.Result{}, err
}
addrs = append(addrs, sip.String())
}

/* TODO this will conflict quickly
if len(clientNetsV4) != 0 {
cnet := randPick(clientNetsV4)
sip, err := cidr.HostBig(&cnet, generateIndex(time.Now(), maskBits(cnet)))
if err != nil {
r.log.Error(err.Error(), "peer", peer.Name)
return ctrl.Result{}, err
}
addrs = append(addrs, sip.String())
}
*/

peer.Status = &v1beta.WireguardAccessPeerStatus{
LastUpdated: metav1.Now(),
Address: sip.String(),
Address: addrs[0],
Addresses: addrs,
DNS: r.dnsServers,
Peers: []v1beta.WireguardAccessPeerStatusPeer{
{
Expand All @@ -185,7 +229,7 @@ func (r *PeerReconciler) Reconcile(ctx context.Context, peer *v1beta.WireguardAc
},
}

err = r.client.Update(ctx, peer)
err := r.client.Update(ctx, peer)
if err != nil {
slog.Error(err.Error(), "peer", peer.Name)
return ctrl.Result{}, err
Expand Down Expand Up @@ -332,10 +376,34 @@ func wgaSync(log *slog.Logger, config *Config) error {
continue
}

log.Info("syncing peer", "peer", peer.Name, "address", peer.Status.Address)
snet := net.IPNet{
IP: net.ParseIP(peer.Status.Address),
Mask: net.CIDRMask(128, 128),
if len(peer.Status.Addresses) == 0 {
peer.Status.Addresses = []string{peer.Status.Address}
}

log.Info("syncing peer", "peer", peer.Name, "address", peer.Status.Addresses)

var allowedIPs []net.IPNet
for _, addr := range peer.Status.Addresses {

ip := net.ParseIP(addr)
if ip == nil {
log.Error("invalid ip", "ip", addr, "peer", peer.Name)
continue
}

var mask net.IPMask
if ip.To4() == nil {
mask = net.CIDRMask(128, 128)
} else {
mask = net.CIDRMask(32, 32)
}

snet := net.IPNet{
IP: ip,
Mask: mask,
}

allowedIPs = append(allowedIPs, snet)
}

var psk wgtypes.Key
Expand All @@ -360,7 +428,7 @@ func wgaSync(log *slog.Logger, config *Config) error {
ReplaceAllowedIPs: true,
PresharedKey: &psk,
PublicKey: pub,
AllowedIPs: []net.IPNet{snet},
AllowedIPs: allowedIPs,
}

shouldPeers[pub.String()] = pc
Expand Down
7 changes: 5 additions & 2 deletions operator/wgc.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package operator

import (
"bytes"
"context"
"fmt"
"log/slog"
Expand Down Expand Up @@ -510,5 +509,9 @@ func wgcSync(log *slog.Logger, wgc []wgPeer) error {
}

func FullMask(ip net.IP) net.IPMask {
return net.IPMask(bytes.Repeat([]byte{0xff}, len(ip)))
if ip.To4() != nil {
return net.CIDRMask(32, 32)
} else {
return net.CIDRMask(128, 128)
}
}
24 changes: 16 additions & 8 deletions peers.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func peerCmd() *cobra.Command {
PrivateKey: v1beta.WireguardClusterClientNodePrivateKey{
Value: ptr(pk.String()),
},
Address: peer.Status.Address,
Address: strings.Join(peer.Status.Addresses, ","),
}
return nil
})
Expand Down Expand Up @@ -246,16 +246,24 @@ func FormatPeerIni(peer v1beta.WireguardAccessPeer, dns []string, pk, psk wgtype
})
}

ip := net.ParseIP(peer.Status.Address)
ips := ""
for _, ip := range peer.Status.Addresses {
ip := net.ParseIP(ip)
if len(ips) > 0 {
ips += ","
}
ipn := net.IPNet{
IP: ip,
Mask: operator.FullMask(ip),
}
ips += ipn.String()
}

oubuf := &strings.Builder{}
err := Format(oubuf, ConfigFile{
Name: peer.Name,
Address: &net.IPNet{
IP: ip,
Mask: operator.FullMask(ip),
},
DNS: dns,
Name: peer.Name,
Address: ips,
DNS: dns,
Device: wgtypes.Device{
Name: peer.Name,
PrivateKey: pk,
Expand Down
5 changes: 5 additions & 0 deletions pkgs/apis/v1beta/generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions pkgs/apis/v1beta/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ type WireguardAccessPeerSpec struct {

type WireguardAccessPeerStatus struct {
//+optional
LastUpdated metav1.Time `yaml:"lastUpdated,omitempty" json:"lastUpdated,omitempty"`
Address string `yaml:"address" json:"address"`
DNS []string `yaml:"dns" json:"dns"`
Peers []WireguardAccessPeerStatusPeer `yaml:"peers" json:"peers"`
LastUpdated metav1.Time `yaml:"lastUpdated,omitempty" json:"lastUpdated,omitempty"`
//+deprecated
Address string `yaml:"address" json:"address"`
Addresses []string `yaml:"addresses" json:"addresses"`
DNS []string `yaml:"dns" json:"dns"`
Peers []WireguardAccessPeerStatusPeer `yaml:"peers" json:"peers"`
}

type WireguardAccessPeerStatusPeer struct {
Expand Down

0 comments on commit 5f1c2ed

Please sign in to comment.