Setting up a WireGuard VPN Server Architecture for Internal Network Access

Julian Runnels
Nov 5, 2020 · 12 min read

Utilizing a Cloud Command and Control (C2) server, along with various endpoint configurations, you can easily set up a full WireGuard network that allows direct access to private internal networks, or even routes all traffic through one IP for easy auditing. This blog and project was born out of a penetration testing need, but the concept can be easily applied for home and enterprise use elsewhere.

One of my recent challenges for my job was the creation of a way to allow internal penetration testers to securely access a company’s on-site internal network. The solution I came up with needed to cover three main criteria:

1. Easily scalable, publicly accessible and securely owned by us.

2. All internal penetration testers should come out of the same public IP for external testing, so the customer could track the requests coming in and easily audit connections.

3. Internal testers needed to be able to directly connect to and see the internal network, with minimal if any configuration needed between clients.

To meet these criteria, I decided to use WireGuard in combination with Azure for the public Command and Control (C2) server and a combination of preconfigured OVA’s, Raspberry Pis, and even Azure Site-To-Site VPN for the client access. The WireGuard and Azure C2 allow the creation of a private VPN that I could add any new testers to easily, and outbound traffic from external machines would be sent through the Azure C2, outputting as a single public IP. Lastly, since the VPN connected our tester’s machines and the C2 server together, any other subnets or networks that were connected to the C2, whether by Site-To-Site or by a Raspberry Pi’s or OVA dialing outbound, would be accessible to the testers like they were on the network right there.

Image for post
Image for post
Basic Network Diagram for WireGuard VPN

It actually doesn’t really matter what provider you use for the C2 server, as long as it has a publicly accessible IP and the correct sizing and bandwidth to support the VPN Network. Additionally the OS doesn’t really matter, although I would highly suggest a flavor of Linux, as most of the custom rules are using /bin/ip or iptables. I choose to actually use Kali Linux, in case the testers ever wanted to run exploits through that machine, due to network issues with their own clients.

From Lucian Constantin in his article here:

WireGuard is a security-focused virtual private network (VPN) known for its simplicity and ease of use. It uses proven cryptography protocols and algorithms to protect data. Originally developed for the Linux kernel, it is now deployable on Windows, macOS, BSD, iOS and Android.

There is an ongoing debate between using WireGuard and other solutions like OpenVPN users, with no definite clear-cut answer. Each brings their own strengths to the field. For this project specifically, since the server and clients were all going to be Linux based and overall the speed of the connection was a big factor, we chose to go with WireGuard, which is designed for speed and performance on the Linux kernel. The main benefit of OpenVPN is the ability to support multiple ciphers and algorithms, which was not relevant to our uses. One interesting article about the benefits of WireGuard is here.

Network Setup Overview

For this network setup, there are going to be 3 main WireGuard configurations:

1. The C2 server, which handles translations connections between clients, as well as acting as a central NAT for public internet access for testers.

2. The client facing connections, which connect back to the C2 and additionally forward traffic through the VPN to the internal network they are connected on. I created an OVA VM image and a RPi configuration that would automatically connect back to the C2 on boot, which would bypass any NAT issues.

3. The tester’s connections, which simply connect to the C2 directly and route all traffic through the interface, letting the C2 ultimately handle routing.

WireGuard installation

For Kali, WireGuard is already installed in most cases, but if not a simple `apt install wireguard` will install it. You may need to restart your system. For other systems please refer to the specific installation instructions here.

WireGuard Configuration

Unlike other VPNs like OpenVPN, there is no specific server application that you start, WireGuard directly connects peer-to-peer. To create the server-client relationship, you simply must configure a specific client to route communications to the other clients while accepting inbound communication. Additionally, WireGuard works through the creation and usage of a set of private/public keys for each client which you create yourself. Once you have created a set of keys, the public key is placed in the configuration of the “server” which allows it to encrypt the traffic outbound. Meanwhile the “client” configuration contains the private key, which is used to decrypt the inbound traffic.

Basically, if you want data to be sent outbound to an endpoint, you simply need to create a peer with that endpoint’s public key (this will make more sense once you see some configurations). The WireGuard configuration file is placed at /etc/wireguard as a .conf file. Whatever you name that file will be reflected as the interface name, for my setup I used wg0 as the server interface and wg0-client for the client’s configs.

Creating a WireGuard key pair is pretty straightforward, on Linux the private key is created with wg genkey and the public key with wg pubkey with the private key piped in. To make it a bit easier and more repeatable I have included a small shell script that you run with ./create-key.sh <name of key> and that will create and save the private and public key pair with the correct name.

create_key.sh

Here is the overall server configuration, which we will break down piece by piece below. In the interest of not releasing my own configuration, the private and public key values will be removed, you would just fill it in with your own. Remember that the PublicKey is the value of the peer, not the host.

C2 Server WireGuard Configuration

All WireGuard configurations start with [Interface], which is where you insert that client’s private key and IP in the network. In this case 10.10.20.1, like shown in the initial diagram, and the key is one created using the shell script shown previously. ListenPort is simply what UDP port you would like open for inbound connections, with 51820 being the default port.

PostUp is used to run commands when the interface is stood up, with PostDown doing the same when the interface is stood down. In this case, these values were copied from here and serve to route all traffic that comes in through the VPN interface back out through the eth0 interface. In other words, these lines tell the WireGuard client to act as NAT gateway for all incoming VPN traffic that is directed to the public internet. Make sure to adjust the eth0 value if you have a different interface for public internet.

The [Peer] values each represent a new VPN connection point. For these values, the PublicKey is the public key that matches whatever private key is in the [Interface] portion for the client. The AllowedIPs acts as a routing table and security group for each client. Basically, any value that is placed there the C2 server will route inbound traffic to those IPs. Notice for the 10.10.20.10 peer, how the internal IP range that is shown on the original Network Diagram is added to the AllowedIPs. This lets the C2 know that any incoming traffic for those ranges must be sent to this peer. It is this setup that allows our tester client’s to directly connect to the internal networks (there is also additional setup needed on the endpoint).

IMPORTANT!

In order for this to work, you must configure IPv4 forwarding on the C2 host, the client endpoint and the tester endpoint. To do so:

# Ensure forwarding is allowed by adding below to /etc/sysctl.conf

net.ipv4.ip_forward=1

or run sudo sysctl net.ipv4.ip_forward=1

To spin up the C2 WireGuard, run the command wg-quick up wg0 as root, or with sudo. You should see the wg interface with ifconfig now. To have the interface run as startup, simply run sudo systemctl enable wg-quick@wg0

Replace the wg0 with whatever you named the configuration file (without the .conf)

Now that the C2 is setup, we need to configure an endpoint to connect back and allow access into the customer’s internal network. I set up both VM OVA images and hardware Raspberry Pi’s with this configuration, to allow the easiest method for getting to a client’s network.

Client Endpoint WireGuard Configuration

The [Interface] and PostUp/PostDown is basically the same as the C2 setup, just with adjusted Address and PrivateKey. Unlike the C2 however, there is only one peer (which is actually the C2) and it has a few more options. Specifically, the Endpoint value is what actually connects the two clients. Since the RPi might be behind a NAT, or otherwise unreachable via direct internet we don’t add an endpoint value to the C2, instead we have the endpoint connect to the C2 public IP or DNS. Think of a reverse shell for all you hackers out there. To make sure this connect stays alive, we add the PersistentKeepalive option, which maintains the connection ever x seconds. Additionally, notice that instead of just having the specific /32 address, we allow connections from the whole WireGuard 10.10.20.0/24 network.

Remember to enable ipv4 forwarding and enable the wg service for bootup.

For the Pen Tester configuration, we need to setup some PostUp/PostDown ip route rules that create a static route out to the C2, so that when we send all traffic through the interface, it doesn’t try to send the C2 traffic and basically loop on itself. You will need to adjust the rules to match whatever your default gateway IP is.

Notice that like the client setup, we connect back the C2 and keep the connection alive. The only big difference is that we add 0.0.0.0/0 to the AllowedIPs, which basically informs the WireGuard interface to forward ALL traffic through the interface and let the C2 server handle sending to the internet. Additionally, we create a static route to the C2 via the normal internet interface so that the C2 traffic doesn’t try to get sent through the interface and loop on itself.

Once again, you have to make sure that IP FORWARDING IS ENABLED, for this to work correctly. If everything works as expected, you should see your public IP changed to the C2 public ip. Additionally, you should be able to ping and talk to the WireGuard internal network.

Example Time!

To clear up the practical use of this setup, let’s go through a little example. I am a pen tester, with a Kali VM on my local network. I configure this VM with a WireGuard client with an IP of 10.10.20.2, connecting to the Azure C2 Kali server that I have previously setup and allowed access through the security groups. I need to test a client’s internal network that has several machines behind a NAT. The client doesn’t own the outbound firewall, so they can’t set up a Site-to-Site VPN, but they can confirm that there is no outbound filtering in place on connections. I send over to them an OVA with a preconfigured VM that connect back to the C2 as 10.10.20.10.

(Pretend the steps below are setting up the network and VM before its shipped out.)

Image for post
Image for post
Sample Network Diagram
  1. Set up VPN C2, client and tester key.
Image for post
Image for post
Image for post
Image for post

2. Configure C2 VPN configuration (/etc/wireguard/ex.conf).

[Interface]
Address = 10.10.20.1
PrivateKey = GFYJl5xBIhQqeedYGckNaFBIS+n9+5hWEwWeQM5Iyks=
ListenPort = 51820
[Peer]
AllowedIPs = 10.10.20.2
PublicKey = 5OeEHFIejwuEHoQUx/CeuT35IF5aWkvbjPUWPRCEfhE=
[Peer]
AllowedIPs = 10.10.20.10
PublicKey = AgYvXOVn+6ED/ylxhIfMjlhgW4/CPvWfFCb3PzmS3mc

3. Spin up C2 VPN.

wg-quick up ex

4. Installation of Client configuration on remote endpoint (/etc/wireguard/ex-client.conf).

[Interface]
Address = 10.10.20.10
PrivateKey = ILaVhY6259CoqUY6/dMnjKZsIel578cF+ssqKC+wrXY=
[Peer]
PublicKey = Q5+LvmPu8a76oKnKOnxGfbWzKZ7WhxxP4YNKy6LgJ0k=
Endpoint = <C2 Public IP>:51820
AllowedIPs = 10.10.20.0/24
PersistentKeepalive = 25

5. Spin up client VM, and set to start at bootup.

wg-quick up ex-client

sudo systemctl enable wg-quick@ex-client

6. Check connection.

Image for post
Image for post

7. Set up connection on Tester endpoint (/etc/wireguard/ex-client.conf).

[Interface]
Address = 10.10.20.2
PrivateKey = yJAl7s7xoRpYZI6B/bAdCX/Ccj1oleUD5gEjDOfv+nm=
[Peer]
PublicKey = Q5+LvmPu8a76oKnKOnxGfbWzKZ7WhxxP4YNKy6LgJ0k=
Endpoint = <C2 Public IP>:51820
AllowedIPs = 10.10.20.0/24
PersistentKeepalive = 25

8. Test connection between C2 and tester.

Image for post
Image for post
Image for post
Image for post

9. Test connection from Tester->Client via C2.

Image for post
Image for post

10. Now comes the fun part, lets adjust the server to route to the internal network on the client endpoint, so that we can actually do something with this connection.

[Interface]
Address = 10.10.20.1
PrivateKey = GFYJl5xBIhQqeedYGckNaFBIS+n9+5hWEwWeQM5Iyks=
ListenPort = 51820
[Peer]
AllowedIPs = 10.10.20.2
PublicKey = 5OeEHFIejwuEHoQUx/CeuT35IF5aWkvbjPUWPRCEfhE=
[Peer]
AllowedIPs = 10.10.20.10, 192.168.1.0/24
PublicKey = AgYvXOVn+6ED/ylxhIfMjlhgW4/CPvWfFCb3PzmS3mc=

11. And adjust the client configuration to add PostUp rules to forward traffic to the internal endpoint.

[Interface]
Address = 10.10.20.10
PrivateKey = ILaVhY6259CoqUY6/dMnjKZsIel578cF+ssqKC+wrXY=
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = Q5+LvmPu8a76oKnKOnxGfbWzKZ7WhxxP4YNKy6LgJ0k=
Endpoint = <C2 Public IP>:51820
AllowedIPs = 10.10.20.0/24
PersistentKeepalive = 25

12. And lastly, adjust the tester to route correctly.

[Interface]
Address = 10.10.20.2
PrivateKey = yJAl7s7xoRpYZI6B/bAdCX/Ccj1oleUD5gEjDOfv+nm=
[Peer]
PublicKey = Q5+LvmPu8a76oKnKOnxGfbWzKZ7WhxxP4YNKy6LgJ0k=
Endpoint = <C2 Public IP>:51820
AllowedIPs = 10.10.20.0/24, 192.168.1.0/24
PersistentKeepalive = 25

13. Check connection.

From C2:

Image for post
Image for post

From tester:

Image for post
Image for post

You can see that the packet is routing though the C2 and through the client endpoint, and to my tester it is a seamless experience.

14. As my tester, I now have full access into the internal 192.168.1.0/24 network as if I was present on the network!

Bonus: Configuring the tester and C2 configuration to route all traffic through the C2, to have one public ip! This also reduces configuration needed for tester, as they won’t have to adjust the config when a new client is added or removed.

1. You need to add the following lines in the C2 server config:

[Interface]
Address = 10.10.20.1
PrivateKey = GFYJl5xBIhQqeedYGckNaFBIS+n9+5hWEwWeQM5Iyks=
ListenPort = 51820
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE # Add forwarding when VPN is started PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE # Remove forwarding when VPN is shutdown[Peer]
AllowedIPs = 10.10.20.2
PublicKey = 5OeEHFIejwuEHoQUx/CeuT35IF5aWkvbjPUWPRCEfhE=
[Peer]
AllowedIPs = 10.10.20.10
PublicKey = AgYvXOVn+6ED/ylxhIfMjlhgW4/CPvWfFCb3PzmS3mc[Interface]

This (like the client endpoints) turns the C2 into a NAT gateway for traffic inbound on the WireGuard interface. Make sure to adjust the eth0 if you have a different internet gateway

2. For the tester client you need to add these lines and add 0.0.0.0/0 to the AllowedIPs

[Interface]
Address = 10.10.20.2
PrivateKey = yJAl7s7xoRpYZI6B/bAdCX/Ccj1oleUD5gEjDOfv+nm=
PostUp = /bin/ip route add <C2 Public IP> via <Default gateway ip> PostDown = /bin/ip route del <C2 Public IP> via <Default gateway ip>[Peer]
PublicKey = Q5+LvmPu8a76oKnKOnxGfbWzKZ7WhxxP4YNKy6LgJ0k=
Endpoint = <C2 Public IP>:51820
AllowedIPs = 10.10.20.0/24, 10.10.20.1/32, 0.0.0.0/0
PersistentKeepalive = 25

The PostUp makes sure that requests being send to the C2 are sent through the normal internet gateway, rather than trying to be sent through the WG interface, which would break.

Side note: Make sure you have the 10.10.20.1/32 or whatever your C2 internal IP is as a specific allowed IP in addition to the normal WireGuard CIDR range, not sure why but this setup won’t work unless you specify that.

3. Check change by curling https://ifconfig.me

Image for post
Image for post

4. Now all of your public Internet connections are showing as out of the C2 public IP, since it is acting like a NAT gateway for you. Additionally, since you are routing all traffic through the WireGuard interface and letting the C2 handle the routing, you don’t have to specify the internal networks you want to route to on the client side, rather just set it on the server side.

Obviously this setup can be built on and improved, but I found it to be reliable and easily used and maintained, and would highly recommend its usage!

InfoSec Write-ups

A collection of write-ups from the best hackers in the…

By InfoSec Write-ups

Newsletter from Infosec Writeups Take a look

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Julian Runnels

Written by

Security Engineer with a focus on Cloud Security and Penetration Testing.

InfoSec Write-ups

A collection of write-ups from the best hackers in the world on topics ranging from bug bounties and CTFs to vulnhub machines, hardware challenges and real life encounters. In a nutshell, we are the largest InfoSec publication on Medium. Maintained by Hackrew

Julian Runnels

Written by

Security Engineer with a focus on Cloud Security and Penetration Testing.

InfoSec Write-ups

A collection of write-ups from the best hackers in the world on topics ranging from bug bounties and CTFs to vulnhub machines, hardware challenges and real life encounters. In a nutshell, we are the largest InfoSec publication on Medium. Maintained by Hackrew

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store