Skip to content

Instantly share code, notes, and snippets.

@qdm12
Last active February 20, 2024 22:35
Star You must be signed in to star a gist
Save qdm12/4e0e4f9d1a34db9cf63ebb0997827d0d to your computer and use it in GitHub Desktop.
Wireguard and iptables restrictions for multiple users

Wireguard and iptables restrictions for multiple users

If you don't know what Wireguard is, well, you should. It's fast, easy to setup and highly configurable. We will configure Wireguard for multiple users with various restrictions using iptables.

Assumptions

This should fit most setups (not mine though 😉)

  • LAN network: 192.168.1.0/24 (192.168.1.1 => 192.168.1.254)
  • LAN DNS server address: 192.168.1.1
  • Wireguard is installed (kernel and tools) on a Linux host (it should also work on other platforms though).
  • The Linux host address: 192.168.1.10
  • The Linux host main interface: enp4s0 (find it with ip a)

Initial server setup

  1. Ensure your iptables firewall has its FORWARD table policy set to DROP:

    iptables -P FORWARD DROP
  2. Generate a Wireguard private key

    echo "Private key: $(wg genkey)"
    Private key: OM5BUrGVAswOm/r8asLtdUgJB8rrXflD6TVFL5aGAHk=
    
  3. Create a file /etc/wireguard/wg0.conf (or any other name than wg0) with content:

    [Interface]
    Address = 10.0.0.1/24
    ListenPort = 51820
    PrivateKey = OM5BUrGVAswOm/r8asLtdUgJB8rrXflD6TVFL5aGAHk=
    PostUp = /etc/wireguard/postup.sh
    PostDown = /etc/wireguard/postdown.sh
    

    Don't forget to replace the PrivateKey value with the one you generated

  4. Create a file /etc/wireguard/postup.sh with content:

    WIREGUARD_INTERFACE=wg0
    WIREGUARD_LAN=10.0.0.0/24
    MASQUERADE_INTERFACE=enp4s0
    
    iptables -t nat -I POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN
    
    # Add a WIREGUARD_wg0 chain to the FORWARD chain
    CHAIN_NAME="WIREGUARD_$WIREGUARD_INTERFACE"
    iptables -N $CHAIN_NAME
    iptables -A FORWARD -j $CHAIN_NAME
    
    # Accept related or established traffic
    iptables -A $CHAIN_NAME -o $WIREGUARD_INTERFACE -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
    
    # Accept traffic from any Wireguard IP address connected to the Wireguard server
    iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -j ACCEPT
    
    # Drop everything else coming through the Wireguard interface
    iptables -A $CHAIN_NAME -i $WIREGUARD_INTERFACE -j DROP
    
    # Return to FORWARD chain
    iptables -A $CHAIN_NAME -j RETURN
  5. Create a file /etc/wireguard/postdown.sh with content:

    WIREGUARD_INTERFACE=wg0
    WIREGUARD_LAN=10.0.0.0/24
    MASQUERADE_INTERFACE=enp4s0
    CHAIN_NAME="WIREGUARD_$WIREGUARD_INTERFACE"
    
    iptables -t nat -D POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN
    
    # Remove and delete the WIREGUARD_wg0 chain
    iptables -D FORWARD -j $CHAIN_NAME
    iptables -F $CHAIN_NAME
    iptables -X $CHAIN_NAME
  6. Start the Wireguard server (without any client configured yet):

    wg-quick up wg0

First admin client

Let's setup a client with full access to Internet and your LAN through Wireguard.

  1. Install Wireguard on your client device

  2. On your client device, create a configuration file client.conf with content:

    [Interface]
    Address = 10.0.0.2/32
    DNS = 192.168.1.1
    PrivateKey = YOUR_CLIENT_PRIVATE_KEY
    
    [Peer]
    AllowedIPs = 0.0.0.0/0, ::/0
    Endpoint = 192.168.1.10:51820
    PersistentKeepalive = 25
    PublicKey = YOUR_SERVER_PUBLIC_KEY
    
  3. Replace in the configuration above YOUR_SERVER_PUBLIC_KEY with the key shown using wg show wg0 public-key on your server. For example: p6aGalk69yCM8vNhbvC5mEH/HhJr1c8f55UaeJSChX0=.

  4. Generate keys for your client:

    priv=`wg genkey` && printf "Private key: $priv\nPublic key: `echo "$priv" | wg pubkey`\n" && unset -v priv
    Private key: 2L4L8YwusK4Ot4jVoo/1wwQfLAeRM6kJ/WWxzfnWKm4=
    Public key: GmVyaj+K36xEk7ko/8jijMB9XX9dFgi4mJxsAEFMHmA=
    
  5. Replace in client.conf YOUR_CLIENT_PRIVATE_KEY with the private key just generated above.

  6. Use the public key shown above to add the following block to /etc/wireguard/wg0.conf on your server:

    [Peer]
    # Your first admin client
    PublicKey = GmVyaj+K36xEk7ko/8jijMB9XX9dFgi4mJxsAEFMHmA=
    AllowedIPs = 10.0.0.2/32
    
  7. On your server, restart Wireguard: wg-quick down wg0 && wg-quick up wg0.

  8. You should now be able to connect to the Wireguard server from your client. You can check on the server with wg and it should show a latest handshake line.

LAN only user

Let's add a user who should only have access to the LAN.

  1. Repeat steps 1 to 5 from the First admin client section above.

  2. Use the public key shown in step 4 to add the following block to /etc/wireguard/wg0.conf on your server:

    [Peer]
    # LAN only user
    PublicKey = 7GneIV/Od7WEKfTpIXr+rTzPf3okaQTBwsfBs5Eqiyw=
    AllowedIPs = 10.0.0.3/32
    
  3. Shutdown Wireguard: wg-quick down wg0

  4. Modify /etc/wireguard/postup.sh:

    • Limit full access to our first admin client 10.0.0.2 only by changing:

      iptables -A $CHAIN_NAME -s 10.0.0.0/24 -i $WIREGUARD_INTERFACE -j ACCEPT

      to

      iptables -A $CHAIN_NAME -s 10.0.0.2 -i $WIREGUARD_INTERFACE -j ACCEPT
    • Add a new line to allow our new user to LAN access only

      iptables -A $CHAIN_NAME -s 10.0.0.3 -i $WIREGUARD_INTERFACE -d 192.168.1.0/24 -j ACCEPT
  5. Start Wireguard: wg-quick up wg0

  6. Note that the client should set its AllowedIPs = 0.0.0.0/0, ::/0 to AllowedIPs = 192.168.1.0/24 so it tunnels only to when trying to reach an address on the LAN.

Restrict the user to a port

Let's re-use the user we previously created. Let's only allow him to access port 445 (Samba) on a server 192.168.1.20 for example.

  1. Shutdown Wireguard: wg-quick down wg0

  2. Modify

    iptables -A $CHAIN_NAME -s 10.0.0.3 -i $WIREGUARD_INTERFACE -d 192.168.1.0/24 -j ACCEPT

    to

    iptables -A $CHAIN_NAME -s 10.0.0.3 -i $WIREGUARD_INTERFACE -d 192.168.10.20 -p tcp --dport 445 -j ACCEPT
  3. Start Wireguard: wg-quick up wg0

Restrict the user to limited Web browsing only

That's useful for our friends who want to stream restricted content on Netflix without allowing them on your LAN 😉

  1. Shutdown Wireguard: wg-quick down wg0

  2. Modify

    iptables -A $CHAIN_NAME -s 10.0.0.3 -i $WIREGUARD_INTERFACE -d 192.168.10.20 -p tcp --dport 445 -j ACCEPT

    to

    # DNS
    iptables -A $CHAIN_NAME -s 10.0.0.3 -i $WIREGUARD_INTERFACE -d 192.168.1.1 -p udp --dport 53 -j ACCEPT
    # Drop traffic to your any private IP address
    iptables -A $CHAIN_NAME -s 10.0.0.3 -i $WIREGUARD_INTERFACE -d 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 -j DROP
    # Accept outgoing connections to HTTP(S) ports to any IP address (public because of rule above)
    iptables -A $CHAIN_NAME -s 10.0.0.3 -i $WIREGUARD_INTERFACE -d 0.0.0.0/0 -p tcp -m multiport --dports 80,443 -j ACCEPT
  3. Start Wireguard: wg-quick up wg0

  4. Don't forget to set back the client to AllowedIPs = 0.0.0.0/0, ::/0

More rules

Feel free to comment, I'll try my best to make you a rule 😉

Enjoy!

@aimtrainer
Copy link

aimtrainer commented Mar 29, 2023

This seems to work for me:
-A FORWARD -s <Wireguard-client's IP> -d 45.57.0.0/17 -i wg0 -p tcp -m multiport --dports 80,443 -j ACCEPT
Here are the ip ranges Netflix uses. I just whitelisted the one that contains the the ips my dns resolved.

@zilexa
Copy link

zilexa commented Apr 22, 2023

I need 4 clients that can only access my Jellyfin server via Wireguard:

  • can only access the host server on port 8096 through tunnel, nothing else.
  • Also means they cannot access other ports on the host + cannot connect to/from other peers.
  • client public internet traffic cannot go through tunnel.
  • my host is 192.168.88.2 (dns server is my router on 192.168.88.1)

Would this be enough, 1 rule for each peer?

iptables -A $CHAIN_NAME -s 10.0.0.3 -i $WIREGUARD_INTERFACE -d 192.168.88.2 -p tcp --dport 8096 -j ACCEPT

Does this automatically mean this is the only access they have? I do not want them to be able to change their client.conf file to let all their internet traffic go through my host. Or do I need more rules to prevent that from happening?

Also need 3 clients that can only access 2 other clients (for RDP remote support of parents).

  • for example 10.0.0.11, 10.0.0.12, 10.0.0.13 should only be allowed to access 10.0.0.20 and 10.0.0.30 (not the host running wireguard server).
  • they should not be allowed to access any services on the host.

Not sure how to do this, none of the examples fit this usecase unfortunately..

@asyncdigital
Copy link

It would be a good question here to ask how to disable ping and speedtest.net (or other speedtesters available) on a wireguard interface to save on the bandwidth. Please advise.

@zilexa
Copy link

zilexa commented Oct 3, 2023

I guess my main question left:
What if I want multiple, let's say 8 clients only allow access to a specific IP and port?
Should I just add 8 rules, creating the 'port-only' rule 8 times or is there a way to work with an IP address range?

@mailliwal
Copy link

mailliwal commented Dec 9, 2023

WIREGUARD_INTERFACE=wg0
WIREGUARD_LAN=10.123.0.0/24
MASQUERADE_INTERFACE=eth0

iptables -t nat -I POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN

Add a WIREGUARD_wg0 chain to the FORWARD chain

CHAIN_NAME="WIREGUARD_$WIREGUARD_INTERFACE"
iptables -N $CHAIN_NAME
iptables -A FORWARD -j $CHAIN_NAME

Accept related or established traffic

iptables -A $CHAIN_NAME -o $WIREGUARD_INTERFACE -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

NGINX

iptables -A WIREGUARD_wg0 -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 192.168.1.83 -p tcp -m multiport --destination-port 80,443 -j ACCEPT

DNS

iptables -A WIREGUARD_wg0 -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 192.168.1.82,192.168.1.250 -p udp --destination-port 53 -j ACCEPT

RDP

iptables -A WIREGUARD_wg0 -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 192.168.1.86 -p tcp --destination-port 3389 -j ACCEPT

SMB

iptables -A WIREGUARD_wg0 -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 192.168.1.250 -p tcp --destination-port 445 -j ACCEPT

Internet (http / https / ntp)

iptables -A WIREGUARD_wg0 -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 0.0.0.0/0 -p tcp -m multiport --destination-port 80,443,123 -j ACCEPT

Drop everything else coming through the Wireguard interface

iptables -A $CHAIN_NAME -i $WIREGUARD_INTERFACE -j DROP

Return to FORWARD chain

iptables -A $CHAIN_NAME -j RETURN

=====================================================

Hi,

Added upper content to postup.sh

But I still available to access 192.168.1.80:8080

It should be dropped and I would like to access via NGINX 443/tcp instead of direct access

May I know anything wrong ?

THANKS

@owenperkins111
Copy link

Is it best to have UFW disabled with this? When i have UFW enabled both my clients can access everything. With it disabled this works as expected. Though i would like to keep UFW enabled as a best practice

@centralhardware
Copy link

centralhardware commented Dec 27, 2023

as mentiond above config for internet only don't work,

WIREGUARD_INTERFACE=wg1
WIREGUARD_LAN=10.8.8.0/24
WIREGUARD_GUEST=10.8.8.0/24
MASQUERADE_INTERFACE=eth0

iptables -t nat -I POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN

# Add a WIREGUARD_wg0 chain to the FORWARD chain
CHAIN_NAME=WIREGUARD_$WIREGUARD_INTERFACE
iptables -N $CHAIN_NAME
iptables -A FORWARD -j $CHAIN_NAME

# Accept related or established traffic
iptables -A $CHAIN_NAME -o $WIREGUARD_INTERFACE -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# GUEST
# Drop traffic to your any private IP address
iptables -A $CHAIN_NAME -s $WIREGUARD_GUEST -i $WIREGUARD_INTERFACE -d 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 -j DROP
# Accept outgoing connections to any IP address (public because of rule above)
iptables -A $CHAIN_NAME -s $WIREGUARD_GUEST -i $WIREGUARD_INTERFACE -d 0.0.0.0/0 -j ACCEPT
# Drop ping (ICMP) from WIREGUARD_GUEST to private adress (RUN ONLY ONCE)
# iptables -I FORWARD -s $WIREGUARD_GUEST -i $WIREGUARD_INTERFACE -d 10.0.0.08,172.16.0.012,192.168.0.016 -p icmp --icmp-type echo-request -j REJECT
# iptables -I FORWARD -s $WIREGUARD_GUEST -i $WIREGUARD_INTERFACE -d 10.0.0.08,172.16.0.012,192.168.0.016 -p icmp --icmp-type echo-reply -j REJECT
# Drop everything else coming through the Wireguard interface
iptables -A $CHAIN_NAME -i $WIREGUARD_INTERFACE -j DROP

# Return to FORWARD chain
iptables -A $CHAIN_NAME -j RETURN

what's wrong?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment