Skip to content

Commit aa88ab7

Browse files
committed
Add optional client isolation feature for blocking traffic between devices
Until now there was absolutely no firewall between all online devices. While this is not problem (and often even wanted behaviour) for single-user setups, it can be useful for multi-user situations.
1 parent 04a37c7 commit aa88ab7

File tree

4 files changed

+78
-36
lines changed

4 files changed

+78
-36
lines changed

cmd/serve/main.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,13 @@ func Register(app *kingpin.Application) *servecmd {
4949
cli.Flag("wireguard-interface", "Set the wireguard interface name").Default("wg0").Envar("WG_WIREGUARD_INTERFACE").StringVar(&cmd.AppConfig.WireGuard.Interface)
5050
cli.Flag("wireguard-private-key", "Wireguard private key").Envar("WG_WIREGUARD_PRIVATE_KEY").StringVar(&cmd.AppConfig.WireGuard.PrivateKey)
5151
cli.Flag("wireguard-port", "The port that the Wireguard server will listen on").Envar("WG_WIREGUARD_PORT").Default("51820").IntVar(&cmd.AppConfig.WireGuard.Port)
52+
cli.Flag("vpn-allowed-ips", "A list of networks that VPN clients will be allowed to connect to via the VPN").Envar("WG_VPN_ALLOWED_IPS").Default("0.0.0.0/0", "::/0").StringsVar(&cmd.AppConfig.VPN.AllowedIPs)
5253
cli.Flag("vpn-cidr", "The network CIDR for the VPN").Envar("WG_VPN_CIDR").Default("10.44.0.0/24").StringVar(&cmd.AppConfig.VPN.CIDR)
5354
cli.Flag("vpn-cidrv6", "The IPv6 network CIDR for the VPN").Envar("WG_VPN_CIDRV6").Default("fd48:4c4:7aa9::/64").StringVar(&cmd.AppConfig.VPN.CIDRv6)
55+
cli.Flag("vpn-gateway-interface", "The gateway network interface (i.e. eth0)").Envar("WG_VPN_GATEWAY_INTERFACE").Default(detectDefaultInterface()).StringVar(&cmd.AppConfig.VPN.GatewayInterface)
5456
cli.Flag("vpn-nat44-enabled", "Enable or disable NAT of IPv6 traffic leaving through the gateway").Envar("WG_IPV4_NAT_ENABLED").Default("true").BoolVar(&cmd.AppConfig.VPN.NAT44)
5557
cli.Flag("vpn-nat66-enabled", "Enable or disable NAT of IPv6 traffic leaving through the gateway").Envar("WG_IPV6_NAT_ENABLED").Default("true").BoolVar(&cmd.AppConfig.VPN.NAT66)
56-
cli.Flag("vpn-gateway-interface", "The gateway network interface (i.e. eth0)").Envar("WG_VPN_GATEWAY_INTERFACE").Default(detectDefaultInterface()).StringVar(&cmd.AppConfig.VPN.GatewayInterface)
57-
cli.Flag("vpn-allowed-ips", "A list of networks that VPN clients will be allowed to connect to via the VPN").Envar("WG_VPN_ALLOWED_IPS").Default("0.0.0.0/0", "::/0").StringsVar(&cmd.AppConfig.VPN.AllowedIPs)
58+
cli.Flag("vpn-client-isolation", "Block or allow traffic between client devices").Envar("WG_VPN_CLIENT_ISOLATION").Default("false").BoolVar(&cmd.AppConfig.VPN.ClientIsolation)
5859
cli.Flag("dns-enabled", "Enable or disable the embedded dns proxy server (useful for development)").Envar("WG_DNS_ENABLED").Default("true").BoolVar(&cmd.AppConfig.DNS.Enabled)
5960
cli.Flag("dns-upstream", "An upstream DNS server to proxy DNS traffic to. Defaults to resolvconf with Cloudflare DNS as fallback").Envar("WG_DNS_UPSTREAM").StringsVar(&cmd.AppConfig.DNS.Upstream)
6061
cli.Flag("dns-domain", "A domain to serve configured device names authoritatively").Envar("WG_DNS_DOMAIN").StringVar(&cmd.AppConfig.DNS.Domain)
@@ -141,7 +142,17 @@ func (cmd *servecmd) Run() {
141142

142143
logrus.Infof("wireguard VPN network is %s", network.StringJoinIPNets(vpnip, vpnipv6))
143144

144-
if err := network.ConfigureForwarding(conf.VPN.GatewayInterface, conf.VPN.CIDR, conf.VPN.CIDRv6, conf.VPN.NAT44, conf.VPN.NAT66, conf.VPN.AllowedIPs); err != nil {
145+
options := network.ForwardingOptions{
146+
GatewayIface: conf.VPN.GatewayInterface,
147+
CIDR: conf.VPN.CIDR,
148+
CIDRv6: conf.VPN.CIDRv6,
149+
NAT44: conf.VPN.NAT44,
150+
NAT66: conf.VPN.NAT66,
151+
ClientIsolation: conf.VPN.ClientIsolation,
152+
AllowedIPs: conf.VPN.AllowedIPs,
153+
}
154+
155+
if err := network.ConfigureForwarding(options); err != nil {
145156
logrus.Error(err)
146157
return
147158
}

docs/2-configuration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Here's what you can configure:
3636
| `WG_VPN_CIDR` | `--vpn-cidr` | `vpn.cidr` | | `10.44.0.0/24` | The VPN IPv4 network range. VPN clients will be assigned IP addresses in this range. Set to `0` to disable IPv4. |
3737
| `WG_IPV4_NAT_ENABLED` | `--vpn-nat44-enabled` | `vpn.nat44` | | `true` | Disables NAT for IPv4 |
3838
| `WG_IPV6_NAT_ENABLED` | `--vpn-nat66-enabled` | `vpn.nat66` | | `true` | Disables NAT for IPv6 |
39+
| `WG_VPN_CLIENT_ISOLATION` | `--vpn-client-isolation` | `vpn.clientIsolation` | | `false` | BLock or allow traffic between client devices (client isolation) |
3940
| `WG_VPN_CIDRV6` | `--vpn-cidrv6` | `vpn.cidrv6` | | `fd48:4c4:7aa9::/64` | The VPN IPv6 network range. VPN clients will be assigned IP addresses in this range. Set to `0` to disable IPv6. |
4041
| `WG_VPN_GATEWAY_INTERFACE` | `--vpn-gateway-interface` | `vpn.gatewayInterface` | | _default gateway interface (e.g. eth0)_ | The VPN gateway interface. VPN client traffic will be forwarded to this interface. |
4142
| `WG_VPN_ALLOWED_IPS` | `--vpn-allowed-ips` | `vpn.allowedIPs` | | `0.0.0.0/0, ::/0` | Allowed IPs that clients may route through this VPN. This will be set in the client's WireGuard connection file and routing is also enforced by the server using iptables. |
@@ -54,5 +55,6 @@ wireguard:
5455
privateKey: "<some-key>"
5556
dns:
5657
upstream:
58+
- "2001:4860:4860::8888"
5759
- "8.8.8.8"
5860
```

internal/config/config.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ type AppConfig struct {
5454
} `yaml:"wireguard"`
5555
// Configure VPN related settings (networking)
5656
VPN struct {
57+
// The "AllowedIPs" for VPN clients.
58+
// This value will be included in client config
59+
// files and in server-side iptable rules
60+
// to enforce network access.
61+
// defaults to ["0.0.0.0/0", "::/0"]
62+
AllowedIPs []string `yaml:"allowedIPs"`
5763
// CIDR configures a network address space
5864
// that client (WireGuard peers) will be allocated
5965
// an IP address from
@@ -64,6 +70,11 @@ type AppConfig struct {
6470
// an IP address from
6571
// defaults to fd48:4c4:7aa9::/64
6672
CIDRv6 string `yaml:"cidrv6"`
73+
// GatewayInterface will be used in iptable forwarding
74+
// rules that send VPN traffic from clients to this interface
75+
// Most use-cases will want this interface to have access
76+
// to the outside internet
77+
GatewayInterface string `yaml:"gatewayInterface"`
6778
// NAT44 configures whether IPv4 traffic leaving
6879
// through the GatewayInterface should be masqueraded
6980
// defaults to true
@@ -73,17 +84,9 @@ type AppConfig struct {
7384
// masqueraded like IPv4 traffic
7485
// defaults to true
7586
NAT66 bool `yaml:"nat66"`
76-
// GatewayInterface will be used in iptable forwarding
77-
// rules that send VPN traffic from clients to this interface
78-
// Most use-cases will want this interface to have access
79-
// to the outside internet
80-
GatewayInterface string `yaml:"gatewayInterface"`
81-
// The "AllowedIPs" for VPN clients.
82-
// This value will be included in client config
83-
// files and in server-side iptable rules
84-
// to enforce network access.
85-
// defaults to ["0.0.0.0/0", "::/0"]
86-
AllowedIPs []string `yaml:"allowedIPs"`
87+
// ClientIsolation configures whether traffic between client devices will be blocked or allowed
88+
// defaults to false
89+
ClientIsolation bool `yaml:"clientIsolation"`
8790
} `yaml:"vpn"`
8891
// Configure the embedded DNS server
8992
DNS struct {

internal/network/network.go

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,25 @@ func SplitAddresses(addresses string) []string {
6565
return split
6666
}
6767

68-
func ConfigureForwarding(gatewayIface string, cidr, cidrv6 string, nat44, nat66 bool, allowedIPs []string) error {
68+
// ForwardingOptions contains all options used for configuring the firewall rules
69+
type ForwardingOptions struct {
70+
GatewayIface string
71+
CIDR, CIDRv6 string
72+
NAT44, NAT66 bool
73+
ClientIsolation bool
74+
AllowedIPs []string
75+
allowedIPv4s []string
76+
allowedIPv6s []string
77+
}
78+
79+
func ConfigureForwarding(options ForwardingOptions) error {
6980
// Networking configuration (iptables) configuration
7081
// to ensure that traffic from clients of the wireguard interface
7182
// is sent to the provided network interface
72-
allowedIPv4s := make([]string, 0, len(allowedIPs)/2)
73-
allowedIPv6s := make([]string, 0, len(allowedIPs)/2)
83+
allowedIPv4s := make([]string, 0, len(options.AllowedIPs)/2)
84+
allowedIPv6s := make([]string, 0, len(options.AllowedIPs)/2)
7485

75-
for _, allowedCIDR := range allowedIPs {
86+
for _, allowedCIDR := range options.AllowedIPs {
7687
parsedAddress, parsedNetwork, err := net.ParseCIDR(allowedCIDR)
7788
if err != nil {
7889
return errors.Wrap(err, "invalid cidr in AllowedIPs")
@@ -86,21 +97,23 @@ func ConfigureForwarding(gatewayIface string, cidr, cidrv6 string, nat44, nat66
8697
allowedIPv6s = append(allowedIPv6s, parsedNetwork.String())
8798
}
8899
}
100+
options.allowedIPv4s = allowedIPv4s
101+
options.allowedIPv6s = allowedIPv6s
89102

90-
if cidr != "" {
91-
if err := configureForwardingv4(gatewayIface, cidr, nat44, allowedIPv4s); err != nil {
103+
if options.CIDR != "" {
104+
if err := configureForwardingv4(options); err != nil {
92105
return err
93106
}
94107
}
95-
if cidrv6 != "" {
96-
if err := configureForwardingv6(gatewayIface, cidrv6, nat66, allowedIPv6s); err != nil {
108+
if options.CIDRv6 != "" {
109+
if err := configureForwardingv6(options); err != nil {
97110
return err
98111
}
99112
}
100113
return nil
101114
}
102115

103-
func configureForwardingv4(gatewayIface string, cidr string, nat44 bool, allowedIPs []string) error {
116+
func configureForwardingv4(options ForwardingOptions) error {
104117
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
105118
if err != nil {
106119
return errors.Wrap(err, "failed to init iptables")
@@ -128,27 +141,34 @@ func configureForwardingv4(gatewayIface string, cidr string, nat44 bool, allowed
128141
return errors.Wrap(err, "failed to append POSTROUTING rule to nat chain")
129142
}
130143

131-
for _, allowedCIDR := range allowedIPs {
132-
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", cidr, "-d", allowedCIDR, "-j", "ACCEPT"); err != nil {
144+
if options.ClientIsolation {
145+
// Reject inter-device traffic
146+
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", options.CIDR, "-d", options.CIDR, "-j", "REJECT"); err != nil {
147+
return errors.Wrap(err, "failed to set ip tables rule")
148+
}
149+
}
150+
// Accept client traffic for given allowed ips
151+
for _, allowedCIDR := range options.allowedIPv4s {
152+
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", options.CIDR, "-d", allowedCIDR, "-j", "ACCEPT"); err != nil {
133153
return errors.Wrap(err, "failed to set ip tables rule")
134154
}
135155
}
136156
// And reject everything else
137-
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", cidr, "-j", "REJECT"); err != nil {
157+
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", options.CIDR, "-j", "REJECT"); err != nil {
138158
return errors.Wrap(err, "failed to set ip tables rule")
139159
}
140160

141-
if gatewayIface != "" {
142-
if nat44 {
143-
if err := ipt.AppendUnique("nat", "WG_ACCESS_SERVER_POSTROUTING", "-s", cidr, "-o", gatewayIface, "-j", "MASQUERADE"); err != nil {
161+
if options.GatewayIface != "" {
162+
if options.NAT44 {
163+
if err := ipt.AppendUnique("nat", "WG_ACCESS_SERVER_POSTROUTING", "-s", options.CIDR, "-o", options.GatewayIface, "-j", "MASQUERADE"); err != nil {
144164
return errors.Wrap(err, "failed to set ip tables rule")
145165
}
146166
}
147167
}
148168
return nil
149169
}
150170

151-
func configureForwardingv6(gatewayIface string, cidrv6 string, nat66 bool, allowedIPs []string) error {
171+
func configureForwardingv6(options ForwardingOptions) error {
152172
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
153173
if err != nil {
154174
return errors.Wrap(err, "failed to init ip6tables")
@@ -174,20 +194,26 @@ func configureForwardingv6(gatewayIface string, cidrv6 string, nat66 bool, allow
174194
return errors.Wrap(err, "failed to append POSTROUTING rule to nat chain")
175195
}
176196

197+
if options.ClientIsolation {
198+
// Reject inter-device traffic
199+
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", options.CIDRv6, "-d", options.CIDRv6, "-j", "REJECT"); err != nil {
200+
return errors.Wrap(err, "failed to set ip tables rule")
201+
}
202+
}
177203
// Accept client traffic for given allowed ips
178-
for _, allowedCIDR := range allowedIPs {
179-
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", cidrv6, "-d", allowedCIDR, "-j", "ACCEPT"); err != nil {
204+
for _, allowedCIDR := range options.allowedIPv6s {
205+
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", options.CIDRv6, "-d", allowedCIDR, "-j", "ACCEPT"); err != nil {
180206
return errors.Wrap(err, "failed to set ip tables rule")
181207
}
182208
}
183209
// And reject everything else
184-
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", cidrv6, "-j", "REJECT"); err != nil {
210+
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", options.CIDRv6, "-j", "REJECT"); err != nil {
185211
return errors.Wrap(err, "failed to set ip tables rule")
186212
}
187213

188-
if gatewayIface != "" {
189-
if nat66 {
190-
if err := ipt.AppendUnique("nat", "WG_ACCESS_SERVER_POSTROUTING", "-s", cidrv6, "-o", gatewayIface, "-j", "MASQUERADE"); err != nil {
214+
if options.GatewayIface != "" {
215+
if options.NAT66 {
216+
if err := ipt.AppendUnique("nat", "WG_ACCESS_SERVER_POSTROUTING", "-s", options.CIDRv6, "-o", options.GatewayIface, "-j", "MASQUERADE"); err != nil {
191217
return errors.Wrap(err, "failed to set ip tables rule")
192218
}
193219
}

0 commit comments

Comments
 (0)