Skip to content

Commit ab850c5

Browse files
committed
feat(wireguard): implement interface network validation and tests
1 parent f90d520 commit ab850c5

File tree

4 files changed

+79
-2
lines changed

4 files changed

+79
-2
lines changed

backend/wireguard/config.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"net"
8+
"strings"
89
"sync"
910

1011
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@@ -65,6 +66,27 @@ func NewConfig(config string) (*Config, error) {
6566
return &wgConfig, nil
6667
}
6768

69+
// InterfaceNetworks returns CIDR prefixes parsed from the node's core `address` list.
70+
// Used to restrict peer AllowedIPs to subnets this interface actually serves.
71+
func (c *Config) InterfaceNetworks() []*net.IPNet {
72+
if c == nil {
73+
return nil
74+
}
75+
var out []*net.IPNet
76+
for _, addr := range c.Address {
77+
addr = strings.TrimSpace(addr)
78+
if addr == "" {
79+
continue
80+
}
81+
_, ipNet, err := net.ParseCIDR(addr)
82+
if err != nil {
83+
continue
84+
}
85+
out = append(out, ipNet)
86+
}
87+
return out
88+
}
89+
6890
// GetPrivateKey returns the parsed WireGuard private key.
6991
// The parsed key is stored in memory and reused after first successful parse.
7092
func (c *Config) GetPrivateKey() (wgtypes.Key, error) {

backend/wireguard/config_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,29 @@
11
package wireguard
22

33
import (
4+
"net"
45
"testing"
56
)
67

8+
func TestConfigInterfaceNetworks(t *testing.T) {
9+
cfg := &Config{Address: []string{" 10.8.0.1/24 ", ""}}
10+
nets := cfg.InterfaceNetworks()
11+
if len(nets) != 1 {
12+
t.Fatalf("expected 1 network, got %d", len(nets))
13+
}
14+
if nets[0].String() != "10.8.0.0/24" {
15+
t.Fatalf("unexpected network: %s", nets[0].String())
16+
}
17+
_, ipn, _ := net.ParseCIDR("10.8.0.5/32")
18+
if !peerIPAllowedOnInterface(ipn, nets) {
19+
t.Fatal("expected 10.8.0.5/32 to be allowed under 10.8.0.0/24")
20+
}
21+
_, wrong, _ := net.ParseCIDR("10.0.0.2/32")
22+
if peerIPAllowedOnInterface(wrong, nets) {
23+
t.Fatal("expected 10.0.0.2/32 to be rejected under 10.8.0.0/24")
24+
}
25+
}
26+
727
func TestNewWireGuardConfig(t *testing.T) {
828
configJSON := `{
929
"interface_name": "wg0",

backend/wireguard/user_common.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,29 @@ func buildAddConfigFromPeerInfo(peer *PeerInfo, presharedKey *wgtypes.Key) (wgty
187187
return buildAddConfig(peer.PublicKey, peer.AllowedIPs, presharedKey), nil
188188
}
189189

190+
func peerIPAllowedOnInterface(peerNet *net.IPNet, ifaceNets []*net.IPNet) bool {
191+
if len(ifaceNets) == 0 {
192+
return true
193+
}
194+
for _, pool := range ifaceNets {
195+
if pool == nil || peerNet == nil {
196+
continue
197+
}
198+
if len(peerNet.IP) != len(pool.IP) {
199+
continue
200+
}
201+
if !pool.Contains(peerNet.IP) {
202+
continue
203+
}
204+
pPeer, _ := peerNet.Mask.Size()
205+
pPool, _ := pool.Mask.Size()
206+
if pPeer >= pPool {
207+
return true
208+
}
209+
}
210+
return false
211+
}
212+
190213
func (wg *WireGuard) collectDesiredPeers(users []*common.User) (map[string]*DesiredPeer, error) {
191214
desiredPeers := make(map[string]*DesiredPeer)
192215
seenIPs := make(map[string]string)
@@ -206,6 +229,8 @@ func (wg *WireGuard) collectDesiredPeers(users []*common.User) (map[string]*Desi
206229
continue
207230
}
208231

232+
ifaceNets := wg.config.InterfaceNetworks()
233+
209234
var allowedIPNets []net.IPNet
210235
var hasInvalidIP bool
211236

@@ -217,6 +242,16 @@ func (wg *WireGuard) collectDesiredPeers(users []*common.User) (map[string]*Desi
217242
break
218243
}
219244

245+
if !peerIPAllowedOnInterface(ipNet, ifaceNets) {
246+
log.Printf(
247+
"skipping wireguard peer IP %s for user %s on interface %s (outside core address ranges)",
248+
peerIp,
249+
email,
250+
wg.config.InterfaceName,
251+
)
252+
continue
253+
}
254+
220255
canonicalIP := ipNet.String()
221256
if existingEmail, exists := seenIPs[canonicalIP]; exists && existingEmail != email {
222257
return nil, fmt.Errorf("duplicate wireguard allowed IP %s assigned to users %s and %s", canonicalIP, existingEmail, email)

backend/wireguard/user_sync_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func TestSyncUserRejectsUnparsableStoredPeer(t *testing.T) {
6767
Inbounds: []string{"wg-test"},
6868
Proxies: &common.Proxy{
6969
Wireguard: &common.Wireguard{
70-
PublicKey: validKey, PeerIps: []string{"10.0.0.1/32"},
70+
PublicKey: validKey, PeerIps: []string{"10.61.0.10/32"},
7171
},
7272
},
7373
}
@@ -306,7 +306,7 @@ func TestSyncUsersReplaceAllRemovesStalePeersAndStats(t *testing.T) {
306306
Inbounds: []string{"wg-test"},
307307
Proxies: &common.Proxy{
308308
Wireguard: &common.Wireguard{
309-
PublicKey: newKey, PeerIps: []string{"10.0.0.4/32"},
309+
PublicKey: newKey, PeerIps: []string{"10.63.0.10/32"},
310310
},
311311
},
312312
},

0 commit comments

Comments
 (0)