Skip to content

Instantly share code, notes, and snippets.

@qdm12
Last active Sep 27, 2022
Embed
What would you like to do?
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 -j 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

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, start Wireguard: 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!

@curiousercreative
Copy link

curiousercreative commented Jun 25, 2021

@qdm12 my wireguard stopped working some time ago and it's taken me until now to debug it. Turns out, there's an always reject forwarded packets policy last and since we're appending our chain, my forwarded packets are rejected before our chain is entered and packets are accepted. To resolve this for myself, I've changed iptables -A FORWARD -j $CHAIN_NAME to iptables -I FORWARD -j $CHAIN_NAME which will insert rather than append (first rather than last).

@qdm12
Copy link
Author

qdm12 commented Jun 28, 2021

@curiousercreative do you mean that you need to put rules before

# Drop everything else coming through the Wireguard interface
iptables -A $CHAIN_NAME -i $WIREGUARD_INTERFACE -j DROP

? If so, then yes 😉 On the other hand, this last rule only blocks traffic coming in through the wireguard interface so it's safe to append that chain and return from it to do other rules for other interfaces.

Now is is better to insert at the top of the table or append, it depends, but by default I prefer to append to avoid messing up things already in place (don't want to lock me out permanently again 😄)

@curiousercreative
Copy link

curiousercreative commented Jun 30, 2021

Yeah, I'm not entirely sure what changed on my server (Ubuntu 18.04 with Webmin/Virtualmin installed), but there's now a reject always as the last rule that was preventing the wireguard chain from ever being reached. Let this comment thread exist as documentation for others in a similar boat as myself :)

@psyonity
Copy link

psyonity commented Jul 2, 2021

hello

can you explain how can i access to lan when i connected with wireguard ? i cant get it to work . I cant access ssh when i connected to wireguard

@1oh1
Copy link

1oh1 commented Sep 9, 2021

@qdm12 I like the LAN only user config but if I'm not wrong, it only allows clients access to 192.168.1.0/24. Is that correct? Is there a way I can get my peers to talk to each other and also reach services hosted by each other (or at least talk to one peer and reach services hosted by that peer?)

For instance, say I have a WireGuard server wg_serv and 4 peers (p1, p2, p3 & p4)

Let's say p1 is a home server running many web services that the other peers (p2, p3 & p4) should have access to but only via wg_serv (i.e. only when they're connected to the VPN)

I also want all peers to connect to each other via wg_serv and access services hosted by each other (e.g. p2 is a mobile phone running a tiny web server and p3, p4 and p1 are able to access it)

But if any of the peers (p1, p2, p3 or p4) want to connect to the Internet, they should do so using whatever Internet connectivity they have without any Internet traffic passing through wg_serv (I guess it's called split tunneling?).

Currently I can achieve this by changing AllowedIPs on the peer configs from 0.0.0.0/0, ::/0 to something like 10.2.0.0/24 but the peer can easily change it back to whatever they want. Any way to enforce this restriction server side? Via an iptables rule maybe?

@AnonymousWebHacker
Copy link

AnonymousWebHacker commented Nov 13, 2021

Greetings, do you know if there is any rule to limit the traffic of x user? I have my friend, who watches HD :( content, and it kills my internet speed at home. Can I limit it to x amount of GB until it runs out?

@samboman
Copy link

samboman commented Jan 27, 2022

I'm trying to setup a wg interface with 10.55.0.0/22, where 10.55.0.0/24 shall have access to LAN ang WAN, and 10.55.1.0/24 only WAN access.

iptables -P FORWARD -j DROP gives me the error:
-P requires a chain and a policy

Also, shall net.ipv4.ip_forward be set to 0 or 1? - Edit: Needs to be 1

Edit: After some digging, I see that I was testing the connectivity with ICMP / ping, and it seems to be accepted from booth subnets.

@txolo99
Copy link

txolo99 commented Jan 29, 2022

How can I implement these rules in a wireguard docker? I try and I cannot... :-(

@samboman
Copy link

samboman commented Jan 31, 2022

I did add the following to deny ping from WireGuard Internet-only clients, as it is enabled by default:

# 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.0/8,172.16.0.0/12,192.168.0.0/16 -p icmp --icmp-type echo-request -j REJECT
#iptables -I FORWARD -s $WIREGUARD_GUEST -i $WIREGUARD_INTERFACE -d 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 -p icmp --icmp-type echo-reply -j REJECT

As you can see I just run it once, otherwise I'll end up in multiple inserts in iptables. Is there a way to add this to the -A $CHAIN_NAME, so it gets deleted from postdown.sh?

@MrPeanut12
Copy link

MrPeanut12 commented Jan 31, 2022

How should these rules be modified if we want to set the INPUT table policy to DROP (while allowing SSH from LAN and wg0)?

@diogopms
Copy link

diogopms commented Feb 5, 2022

Can we use ufw instead of iptables to support this flow?

@techjp
Copy link

techjp commented Feb 13, 2022

iptables -P FORWARD -j DROP gives me the error:
-P requires a chain and a policy

@samboman, I get the same error. I wonder if @qdm12 might have made a mistake with the commands? Should it not be iptables -P FORWARD DROP?

@Normanras
Copy link

Normanras commented Mar 4, 2022

@samboman Do you mind sharing what you set $WIREGUARD_GUEST to in your scripts?

@samboman
Copy link

samboman commented Mar 5, 2022

This is how I ended up and solved it for a trusted and a internet-only-network (guest).

postup.sh

WIREGUARD_INTERFACE=wg0
WIREGUARD_LAN=10.55.0.022
WIREGUARD_TRUSTED=10.55.0.024
WIREGUARD_GUEST=10.55.1.024
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

# TRUSTED
# Accept traffic from any trusted IP address
iptables -A $CHAIN_NAME -s $WIREGUARD_TRUSTED -i $WIREGUARD_INTERFACE -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.08,172.16.0.012,192.168.0.016 -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.00 -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

postdown.sh

WIREGUARD_INTERFACE=wg0
WIREGUARD_LAN=10.55.0.0/22
MASQUERADE_INTERFACE=eth0
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

@Darkyere
Copy link

Darkyere commented May 19, 2022

Is it possible in your exambles above.

To add a config that will allow acces only too specific and multiple IP's and Ports.
While at the same time allowing the person to reach the internet?

I would like to allow friends acces to certain computers on certain ports.
But i would allso like them to be able to acces the public internet while retraining acces to my LAN.

Best Regards,
Darkyere

@int2018
Copy link

int2018 commented May 30, 2022

Hi,

I am not sure if this is the right place to ask, so please be kind if not.

I have setup Wireguard on a Raspberry and it handles the remote access to the Qnap NAS in the network. So far everything is good.
The "problem" is, that multiple users can open the same file (e.g. Excel sheet) without a warning. This means there is the potential of data loss since the last one who saves the file "wins".
Is this something that can be handled in wireguard?

Thanks in advance

@curiousercreative
Copy link

curiousercreative commented May 30, 2022

The "problem" is, that multiple users can open the same file (e.g. Excel sheet) without a warning. This means there is the potential of data loss since the last one who saves the file "wins". Is this something that can be handled in wireguard?

@int2018 No, nothing Wireguard can do about this. You can look into file locking. Some applications (Adobe InDesign, darktable, KeePassXC) will handle this, but I believe SMB and NFS can perhaps be configured with similar functionality.

@ImDaBigBoss
Copy link

ImDaBigBoss commented Jun 18, 2022

Hello, is there a way I can stop the wireguard server starting a connection to one of the clients?
Say I have these clients and these permissions:

  • 10.0.0.1 Server: Can start connections to 10.0.0.3
  • 10.0.0.2 Admin client: Can start connections to everything
  • 10.0.0.3 Client: Can start connecions to 10.0.0.1

Thanks in advance

@nikkadim
Copy link

nikkadim commented Aug 4, 2022

@qdm12 my wireguard stopped working some time ago and it's taken me until now to debug it. Turns out, there's an always reject forwarded packets policy last and since we're appending our chain, my forwarded packets are rejected before our chain is entered and packets are accepted. To resolve this for myself, I've changed iptables -A FORWARD -j $CHAIN_NAME to iptables -I FORWARD -j $CHAIN_NAME which will insert rather than append (first rather than last).
+1

@int2018
Copy link

int2018 commented Aug 4, 2022

Hi all,

my wireguard is all set up and running since months (wireguard on raspi, qnap nas/ smb server and windows clients.
It is working well (despite the issue mentioned before) but I still experience a strange behavior.
When I work with Windows Explorer and doubleclick on a folder it opens, I open another folder, and so on. So far so good, everything works as expected.
But then (no exact time) I doubleclick another folder or I doubleclick to open a file (e.g. pdf) the connection "hangs". I doubleclick again and the Explorer window tile changes to ... (not responding). Again after some time (seems 5-10 seconds) the connection is working again and the click I made earlier gets handled, the folder opens or the pdf file or whatever I clicked.
This is just a description of what I see on the (remote) client side. I talked to colleagues and they have the same phenomena. And it also happens when there is only one client connected. It does not seem to be a problem of too much load, the raspberry seems mostly in idle.

So the question is how/where can I check/tweak/debug what the problem is?
Can it be related to the Qnap NAS with SMB (standard setup) ?

Thanks for a hint...

@Normanras
Copy link

Normanras commented Aug 11, 2022

@qdm12 This particular gist has been helpful for me in understanding how iptables related to wireguard and peers. However, I could never quite get the example for peers with internet access but no LAN access. I thought I was understanding it, but i couldn't get it to work consistently.

Instead, I've added this rule as a permanent iptables rule and it seems to be accomplishing exactly what I need - internet access, no LAN access.

iptables -I FORWARD -s $WG_LAN -d $LOCAL_LAN -j DROP

Am I missing some security flaw or concern in using this single rule as opposed to your post* scripts? Truly curious, not grilling or questioning your script, I want to understand.

@rafaelgimenes
Copy link

rafaelgimenes commented Sep 22, 2022

i have a wireguard server on VPS on CANADA, and internally i have a raspberry pi with the pihole for my internal DNS custom setup cloudfare getting close DNS server in my area Brazil.
When my raspberry pi is connected on wireguard/VPS, my dns resolution changes to CANADA, in this peer(rasp berry) I only would like to allow to receive some packet from another peers, but do not out going to internet and assumes that it from CANADA.

Is it possible?

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