diff --git a/charts/wga/crds/wga.yaml b/charts/wga/crds/wga.yaml index 8b40811..8bc57bf 100644 --- a/charts/wga/crds/wga.yaml +++ b/charts/wga/crds/wga.yaml @@ -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 @@ -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 diff --git a/format.go b/format.go index 4fa522a..b85e452 100644 --- a/format.go +++ b/format.go @@ -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 diff --git a/operator/nft.go b/operator/nft.go index 7e0c70a..5aa4a1e 100644 --- a/operator/nft.go +++ b/operator/nft.go @@ -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 { @@ -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") + } } } } diff --git a/operator/wga.go b/operator/wga.go index d9d7997..c3a4050 100644 --- a/operator/wga.go +++ b/operator/wga.go @@ -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{ { @@ -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 @@ -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 @@ -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 diff --git a/operator/wgc.go b/operator/wgc.go index 3211baf..77103e0 100644 --- a/operator/wgc.go +++ b/operator/wgc.go @@ -1,7 +1,6 @@ package operator import ( - "bytes" "context" "fmt" "log/slog" @@ -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) + } } diff --git a/peers.go b/peers.go index 313f77b..9a5f297 100644 --- a/peers.go +++ b/peers.go @@ -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 }) @@ -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, diff --git a/pkgs/apis/v1beta/generated.deepcopy.go b/pkgs/apis/v1beta/generated.deepcopy.go index c1d0f5e..e9283b4 100644 --- a/pkgs/apis/v1beta/generated.deepcopy.go +++ b/pkgs/apis/v1beta/generated.deepcopy.go @@ -100,6 +100,11 @@ func (in *WireguardAccessPeerSpec) DeepCopy() *WireguardAccessPeerSpec { func (in *WireguardAccessPeerStatus) DeepCopyInto(out *WireguardAccessPeerStatus) { *out = *in in.LastUpdated.DeepCopyInto(&out.LastUpdated) + if in.Addresses != nil { + in, out := &in.Addresses, &out.Addresses + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.DNS != nil { in, out := &in.DNS, &out.DNS *out = make([]string, len(*in)) diff --git a/pkgs/apis/v1beta/types.go b/pkgs/apis/v1beta/types.go index c8b0340..f93705d 100644 --- a/pkgs/apis/v1beta/types.go +++ b/pkgs/apis/v1beta/types.go @@ -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 {