Skip to content

Commit

Permalink
Add optional client isolation feature for blocking traffic between de…
Browse files Browse the repository at this point in the history
…vices

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.
  • Loading branch information
DasSkelett committed Mar 24, 2022
1 parent 04a37c7 commit aa88ab7
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 36 deletions.
17 changes: 14 additions & 3 deletions cmd/serve/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,13 @@ func Register(app *kingpin.Application) *servecmd {
cli.Flag("wireguard-interface", "Set the wireguard interface name").Default("wg0").Envar("WG_WIREGUARD_INTERFACE").StringVar(&cmd.AppConfig.WireGuard.Interface)
cli.Flag("wireguard-private-key", "Wireguard private key").Envar("WG_WIREGUARD_PRIVATE_KEY").StringVar(&cmd.AppConfig.WireGuard.PrivateKey)
cli.Flag("wireguard-port", "The port that the Wireguard server will listen on").Envar("WG_WIREGUARD_PORT").Default("51820").IntVar(&cmd.AppConfig.WireGuard.Port)
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)
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)
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)
cli.Flag("vpn-gateway-interface", "The gateway network interface (i.e. eth0)").Envar("WG_VPN_GATEWAY_INTERFACE").Default(detectDefaultInterface()).StringVar(&cmd.AppConfig.VPN.GatewayInterface)
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)
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)
cli.Flag("vpn-gateway-interface", "The gateway network interface (i.e. eth0)").Envar("WG_VPN_GATEWAY_INTERFACE").Default(detectDefaultInterface()).StringVar(&cmd.AppConfig.VPN.GatewayInterface)
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)
cli.Flag("vpn-client-isolation", "Block or allow traffic between client devices").Envar("WG_VPN_CLIENT_ISOLATION").Default("false").BoolVar(&cmd.AppConfig.VPN.ClientIsolation)
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)
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)
cli.Flag("dns-domain", "A domain to serve configured device names authoritatively").Envar("WG_DNS_DOMAIN").StringVar(&cmd.AppConfig.DNS.Domain)
Expand Down Expand Up @@ -141,7 +142,17 @@ func (cmd *servecmd) Run() {

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

if err := network.ConfigureForwarding(conf.VPN.GatewayInterface, conf.VPN.CIDR, conf.VPN.CIDRv6, conf.VPN.NAT44, conf.VPN.NAT66, conf.VPN.AllowedIPs); err != nil {
options := network.ForwardingOptions{
GatewayIface: conf.VPN.GatewayInterface,
CIDR: conf.VPN.CIDR,
CIDRv6: conf.VPN.CIDRv6,
NAT44: conf.VPN.NAT44,
NAT66: conf.VPN.NAT66,
ClientIsolation: conf.VPN.ClientIsolation,
AllowedIPs: conf.VPN.AllowedIPs,
}

if err := network.ConfigureForwarding(options); err != nil {
logrus.Error(err)
return
}
Expand Down
2 changes: 2 additions & 0 deletions docs/2-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Here's what you can configure:
| `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. |
| `WG_IPV4_NAT_ENABLED` | `--vpn-nat44-enabled` | `vpn.nat44` | | `true` | Disables NAT for IPv4 |
| `WG_IPV6_NAT_ENABLED` | `--vpn-nat66-enabled` | `vpn.nat66` | | `true` | Disables NAT for IPv6 |
| `WG_VPN_CLIENT_ISOLATION` | `--vpn-client-isolation` | `vpn.clientIsolation` | | `false` | BLock or allow traffic between client devices (client isolation) |
| `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. |
| `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. |
| `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. |
Expand All @@ -54,5 +55,6 @@ wireguard:
privateKey: "<some-key>"
dns:
upstream:
- "2001:4860:4860::8888"
- "8.8.8.8"
```
25 changes: 14 additions & 11 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ type AppConfig struct {
} `yaml:"wireguard"`
// Configure VPN related settings (networking)
VPN struct {
// The "AllowedIPs" for VPN clients.
// This value will be included in client config
// files and in server-side iptable rules
// to enforce network access.
// defaults to ["0.0.0.0/0", "::/0"]
AllowedIPs []string `yaml:"allowedIPs"`
// CIDR configures a network address space
// that client (WireGuard peers) will be allocated
// an IP address from
Expand All @@ -64,6 +70,11 @@ type AppConfig struct {
// an IP address from
// defaults to fd48:4c4:7aa9::/64
CIDRv6 string `yaml:"cidrv6"`
// GatewayInterface will be used in iptable forwarding
// rules that send VPN traffic from clients to this interface
// Most use-cases will want this interface to have access
// to the outside internet
GatewayInterface string `yaml:"gatewayInterface"`
// NAT44 configures whether IPv4 traffic leaving
// through the GatewayInterface should be masqueraded
// defaults to true
Expand All @@ -73,17 +84,9 @@ type AppConfig struct {
// masqueraded like IPv4 traffic
// defaults to true
NAT66 bool `yaml:"nat66"`
// GatewayInterface will be used in iptable forwarding
// rules that send VPN traffic from clients to this interface
// Most use-cases will want this interface to have access
// to the outside internet
GatewayInterface string `yaml:"gatewayInterface"`
// The "AllowedIPs" for VPN clients.
// This value will be included in client config
// files and in server-side iptable rules
// to enforce network access.
// defaults to ["0.0.0.0/0", "::/0"]
AllowedIPs []string `yaml:"allowedIPs"`
// ClientIsolation configures whether traffic between client devices will be blocked or allowed
// defaults to false
ClientIsolation bool `yaml:"clientIsolation"`
} `yaml:"vpn"`
// Configure the embedded DNS server
DNS struct {
Expand Down
70 changes: 48 additions & 22 deletions internal/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,25 @@ func SplitAddresses(addresses string) []string {
return split
}

func ConfigureForwarding(gatewayIface string, cidr, cidrv6 string, nat44, nat66 bool, allowedIPs []string) error {
// ForwardingOptions contains all options used for configuring the firewall rules
type ForwardingOptions struct {
GatewayIface string
CIDR, CIDRv6 string
NAT44, NAT66 bool
ClientIsolation bool
AllowedIPs []string
allowedIPv4s []string
allowedIPv6s []string
}

func ConfigureForwarding(options ForwardingOptions) error {
// Networking configuration (iptables) configuration
// to ensure that traffic from clients of the wireguard interface
// is sent to the provided network interface
allowedIPv4s := make([]string, 0, len(allowedIPs)/2)
allowedIPv6s := make([]string, 0, len(allowedIPs)/2)
allowedIPv4s := make([]string, 0, len(options.AllowedIPs)/2)
allowedIPv6s := make([]string, 0, len(options.AllowedIPs)/2)

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

if cidr != "" {
if err := configureForwardingv4(gatewayIface, cidr, nat44, allowedIPv4s); err != nil {
if options.CIDR != "" {
if err := configureForwardingv4(options); err != nil {
return err
}
}
if cidrv6 != "" {
if err := configureForwardingv6(gatewayIface, cidrv6, nat66, allowedIPv6s); err != nil {
if options.CIDRv6 != "" {
if err := configureForwardingv6(options); err != nil {
return err
}
}
return nil
}

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

for _, allowedCIDR := range allowedIPs {
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", cidr, "-d", allowedCIDR, "-j", "ACCEPT"); err != nil {
if options.ClientIsolation {
// Reject inter-device traffic
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", options.CIDR, "-d", options.CIDR, "-j", "REJECT"); err != nil {
return errors.Wrap(err, "failed to set ip tables rule")
}
}
// Accept client traffic for given allowed ips
for _, allowedCIDR := range options.allowedIPv4s {
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", options.CIDR, "-d", allowedCIDR, "-j", "ACCEPT"); err != nil {
return errors.Wrap(err, "failed to set ip tables rule")
}
}
// And reject everything else
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", cidr, "-j", "REJECT"); err != nil {
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", options.CIDR, "-j", "REJECT"); err != nil {
return errors.Wrap(err, "failed to set ip tables rule")
}

if gatewayIface != "" {
if nat44 {
if err := ipt.AppendUnique("nat", "WG_ACCESS_SERVER_POSTROUTING", "-s", cidr, "-o", gatewayIface, "-j", "MASQUERADE"); err != nil {
if options.GatewayIface != "" {
if options.NAT44 {
if err := ipt.AppendUnique("nat", "WG_ACCESS_SERVER_POSTROUTING", "-s", options.CIDR, "-o", options.GatewayIface, "-j", "MASQUERADE"); err != nil {
return errors.Wrap(err, "failed to set ip tables rule")
}
}
}
return nil
}

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

if options.ClientIsolation {
// Reject inter-device traffic
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", options.CIDRv6, "-d", options.CIDRv6, "-j", "REJECT"); err != nil {
return errors.Wrap(err, "failed to set ip tables rule")
}
}
// Accept client traffic for given allowed ips
for _, allowedCIDR := range allowedIPs {
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", cidrv6, "-d", allowedCIDR, "-j", "ACCEPT"); err != nil {
for _, allowedCIDR := range options.allowedIPv6s {
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", options.CIDRv6, "-d", allowedCIDR, "-j", "ACCEPT"); err != nil {
return errors.Wrap(err, "failed to set ip tables rule")
}
}
// And reject everything else
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", cidrv6, "-j", "REJECT"); err != nil {
if err := ipt.AppendUnique("filter", "WG_ACCESS_SERVER_FORWARD", "-s", options.CIDRv6, "-j", "REJECT"); err != nil {
return errors.Wrap(err, "failed to set ip tables rule")
}

if gatewayIface != "" {
if nat66 {
if err := ipt.AppendUnique("nat", "WG_ACCESS_SERVER_POSTROUTING", "-s", cidrv6, "-o", gatewayIface, "-j", "MASQUERADE"); err != nil {
if options.GatewayIface != "" {
if options.NAT66 {
if err := ipt.AppendUnique("nat", "WG_ACCESS_SERVER_POSTROUTING", "-s", options.CIDRv6, "-o", options.GatewayIface, "-j", "MASQUERADE"); err != nil {
return errors.Wrap(err, "failed to set ip tables rule")
}
}
Expand Down

0 comments on commit aa88ab7

Please sign in to comment.