Organizations are switching to WireGuard®. Why?
This makes WireGuard® ideal as a networking layer for distributed systems like IoT and Kubernetes, as well as for remote access.
However, there are still many challenges involved in setting up a WireGuard® virtual network at scale. Specifically, configuration management and access controls.
Netmaker automates WireGuard® configuration, decreasing the level of complexity necessary to manage WireGuard® across many devices.
In v0.12.0, Netmaker introduced Access Control Lists, which allow operators to manage fine-grained ACLs. Put simply, this creates an easy system for controlling which machine can talk to which.
In this article, we give a quick demonstration of managing WireGuard® access controls with Netmaker.
Our setup consists of a Netmaker server and 6 machines running in AWS: Four in us-east-1 and two in eu-central-1. All machines run Linux. All machines have been added to the Netmaker network acl-net-1.
By default, Netmaker creates a “full mesh” P2P network, meaning every machine can reach every other machine. We can see this in the Graph visualization:
This is confirmed by viewing the output of “wg show” locally:
root@linux-large-3-germany:~# wg show nm-acl-net-1
interface: nm-acl-net-1
public key: xxxxxxxxxxxxxxxxxxxx
private key: (hidden)
listening port: 34314
peer: xxxxxxxxxxxxxxxxxxxx
endpoint: (hidden):42953
allowed ips: 10.83.18.5/32
latest handshake: 1 minute, 48 seconds ago
transfer: 1.13 KiB
received, 1.27 KiB sent
persistent keepalive: every 20 seconds
peer: xxxxxxxxxxxxxxxxxxxx
endpoint: (hidden):53856
allowed ips: 10.83.18.4/32
latest handshake: 1 minute, 48 seconds ago
transfer: 1.30 KiB received, 928 B sent
persistent keepalive: every 20 seconds
peer: xxxxxxxxxxxxxxxxxxxx
endpoint: (hidden):44417
allowed ips: 10.83.18.3/32
latest handshake: 1 minute, 48 seconds ago
transfer: 340 B received, 272 B sent
persistent keepalive: every 20 seconds
peer: xxxxxxxxxxxxxxxxxxxx
endpoint: (hidden):54572
allowed ips: 10.83.18.2/32
latest handshake: 1 minute, 48 seconds ago
transfer: 340 B received, 272 B sent
persistent keepalive: every 20 seconds
peer: xxxxxxxxxxxxxxxxxxxx
endpoint: (hidden):37150
allowed ips: 10.83.18.1/32
latest handshake: 1 minute, 48 seconds ago
transfer: 1.02 KiB received, 1.02 KiB sent
persistent keepalive: every 20 seconds
peer: xxxxxxxxxxxxxxxxxxxx
endpoint: (hidden):51822
allowed ips: 10.83.18.254/32
latest handshake: 1 minute, 50 seconds ago
transfer: 124 B received, 340 B sent
persistent keepalive: every 20 seconds
A ping test confirms connectivity:
root@linux-large-3-germany:~# ping 10.83.18.3
PING 10.83.18.3 (10.83.18.3) 56(84) bytes of data.
64 bytes from 10.83.18.3: icmp_seq=1 ttl=64 time=89.0 ms
64 bytes from 10.83.18.3: icmp_seq=2 ttl=64 time=89.1 ms
64 bytes from 10.83.18.3: icmp_seq=3 ttl=64 time=89.0 ms
64 bytes from 10.83.18.3: icmp_seq=4 ttl=64 time=89.1 ms
^C
--- 10.83.18.3 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3003msrtt min/avg/max/mdev = 89.016/89.083/89.143/0.057 m
Now, let’s restrict this network with ACLs.
In the ACL page on Netmaker’s UI, you get a full list of all P2P connections in the network. You can disable any pair of connections by clicking on it.
As you can see, by default, all connections are enabled. Lets make it so the nodes in Germany cannot talk to the nodes in Virginia.
You can click on an individual node to restrict its connections:
You can also change rules from the main screen:
Once you click “SUBMIT CHANGES,” the nodes will be notified immediately and update their local settings.
This also propagates to the Graph view, which shows the update:
We confirm this change locally by looking again at the peer list. For instance, on our machine in Germany, it shows only 1 other machine in Germany, and the Netmaker server:
root@linux-large-3-germany:~# wg show nm-acl-net-1
interface: nm-acl-net-1
public key: xxxxxxxxxxxxxxxxxxxxxxxx
private key: (hidden)
listening port: 34314
peer: xxxxxxxxxxxxxxxxxxxxxxxx
endpoint: (hidden):54572
allowed ips: 10.83.18.2/32
latest handshake: 22 seconds ago
transfer: 956 B received, 456 B sent
persistent keepalive: every 20 seconds
peer: xxxxxxxxxxxxxxxxxxxxxxxx
endpoint: (hidden):51822
allowed ips: 10.83.18.254/32
latest handshake: 24 seconds ago
transfer: 308 B received, 956 B sent
persistent keepalive: every 20 seconds
This node in Germany is no longer able to ping the node in Virginia, which was previously available:
root@linux-large-3-germany:~# ping 10.83.18.3
PING 10.83.18.3 (10.83.18.3) 56(84) bytes of data.
From 10.83.18.6 icmp_seq=1 Destination Host Unreachable
ping: sendmsg: Required key not available
From 10.83.18.6 icmp_seq=2 Destination Host Unreachable
ping: sendmsg: Required key not available
From 10.83.18.6 icmp_seq=3 Destination Host Unreachable
ping: sendmsg: Required key not available
^C
--- 10.83.18.3 ping statistics ---
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2052ms
We have successfully segmented the network.
This is great, but what about the inverse? What if, by default, no machine should have access to any other machine. In this scenario, you want to manually approve every machine’s access.
We can achieve this by creating a network with a “default deny” policy. Any machine that enters the network will have no peers, and no connections. Let’s create a network with this setting enabled:
Note that “Default Access Control” is switched off. This is what changes the default policy to “deny.”
After creating, we join all of our nodes to the new network.
As you can see via the Graph, none are connected:
This is confirmed locally by checking the WireGuard interface again, where we see there are no peers:
root@linux-large-3-germany:~# wg show nm-acl-net-2
interface: nm-acl-net-2
public key: xxxxxxxxxxxxxxxxxxxxxxxxxxx
private key: (hidden)
listening port: 51821
This allows us to “build up” our network from nothing using the UI:
This time, we enable all the small machines to talk to each other:
Again, this populates to the UI after submitting:
And we confirm by checking the WireGuard peers locally:
root@linux-large-3-germany:~# wg show nm-acl-net-1
interface: nm-acl-net-1
public key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
private key: (hidden)
listening port: 34314
peer: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
endpoint: (hidden):54572
allowed ips: 10.83.18.2/32
latest handshake: 22 seconds ago
transfer: 956 B received, 456 B sent
persistent keepalive: every 20 seconds
peer: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
endpoint: (hidden):51822
allowed ips: 10.83.18.254/32
latest handshake: 24 seconds ago
transfer: 308 B received, 956 B sent
persistent keepalive: every 20 seconds
We also switched off UDP Hole Punching in this scenario. Why? UDP Hole Punching relies on the client having a connection to the server node. If this is disabled, connections will break. Alternatively, we could have switched on UDP Hole Punching, and then ensure we manually enable every node’s connection to the server in the ACL page.
This concludes our quick demonstration of implementing ACLs on WireGuard networks with Netmaker. There are many use cases this applies to, from IoT, to Kubernetes, to Remote Access. Stay tuned for additional tutorials where we look at some specific use cases.
Disclaimer: WireGuard is a registered trademark of Jason A. Donenfeld.
GET STARTED