SFTP-based VPN bootstrapping with automatic collision-free IPs assignment/peers' public data sharing
Hi,
after recent searching/thinking about how to span a VPN from a single publicly visible server with automatic collision-free IPs assignment/peers' public data sharing, I came to following scheme. Please provide your feedback on what possible improvements/security holes/pitfalls might be...
Everything starts with a central server that has it's WG interface up and running. As almost every server out there has SSH running - we will rely on it for communication with new peers that want to join the VPN. We'll setup an chrooted SFTP account with known public/private SSH keys to be used by new peers. Only one directory of this account will be used. On start there will be 2 files:
- hosts_appendix - pool of currently free/assigned IPs (root-owned read-only for peers)
- read-only link on /etc/wireguard/wg-interface.conf.d/all_peers.conf - public data (IP/public key) of all peers on the network
How it works:
1. hosts_appendix (/etc/hosts like file which will consist of generated IPs of desired subnet to use, ways to do this:
https://stackoverflow.com/a/31412705/1876484 ). It doesn't have to cover the whole range, but rather only part and once the free pool of IPs becomes nearly exhausted one can just append another piece of IPs to this file.
So at the beginning it will look like:
10.0.0.1
10.0.0.2
...
10.0.3.128
Once a new peer wants to request an IP it downloads the hosts_appendix file from the server through SFTP, takes a random line in the file and then scans it until it sees first free IP. In order to obtain the discovered IP the peer creates on the server a file with only one line inside - its public key, the name of the file is the IP that the peer requests. This way many peers may attempt to obtain IPs from the same pool without the fair of collisions. (If the peer can't create a file with that name - seemingly another peer tried to obtain it already, even though it wasn't marked as assigned inside the hosts_appendix file yet). Then the peer creates its WG interface based on this IP.
2. Once in a while (through cron) or immediately (through an inotify event) the server will see the newly created 10.0.2.113 file and do the following:
(a) mark the IP as assigned inside hosts_appendix by appending in the relevant line a comment with peers public key, like
...
10.0.2.113 # Ad32dd4kc9d...
...
if desired hostnames can be used as in regular /etc/hosts:
10.0.2.113 recently_joined_peer # Ad32dd4kc9d...
so that if peers append this file to their local /etc/hosts/ they will be able to ping/ssh/whatsover by the hostname as well.
(b) then the server adds this new peer to its WG interface and adds the peer to the file /etc/wireguard/wg-interface.conf.d/all_peers.conf
(c) the server removes the 10.0.2.113 file with the peer's public key.
Once the peer downloads
the /etc/wireguard/wg-interface.conf.d/all_peers.conf file it knows _ALL_ other peers on the network and can communicate with all of them directly.
What do you say?
My problem is that I don't have too much experience with bash/python scripting, so I'm wondering what would be easier to implement the above or just use tinc with avahi:
https://www.tinc-vpn.org/examples/zeroconf-ip-and-dns/
Thank you!
Hi ST,
It's a cool idea using the file system like that (the sticky bit would make the permissions part work correctly, perhaps), though I wonder if it's a bit complicated. If the model you're after is simply "server allocates IPs for peers already known through some channel but with unknown wireguard public keys", then maybe a better SSH-based interface is a special user that is only allowed to run one program, and that program does one thing: accepts as input a public key, and outputs [without races] an allocated IP, endpoint, and the server's public key. Under the hood that information could be stored in a variety of ways. Alternatively, this could be its own protocol over the wire or over TLS or over whatever the pre-established trust mechanism is that the idea is based on. One of the earliest dirty bash scripts for WireGuard did this (insecurely) over TCP --
https://git.zx2c4.com/WireGuard/tree/contrib/examples/ncat-client-server/server.sh
-- this is what's running on demo.wireguard.com.
Jason
Hi Jason,
thank you very much! It is indeed what I'm looking for. I'll try to learn how your script exactly works, just please tell me why and to what extent is it insecurely and what needs to be done to make it secure?
Thank you!
Quoted 19 lines · Jason A. Donenfeld
Hi ST,
It's a cool idea using the file system like that (the sticky bit would make the permissions part work correctly, perhaps), though I wonder if it's a bit complicated. If the model you're after is simply "server allocates IPs for peers already known through some channel but with unknown wireguard public keys", then maybe a better SSH-based interface is a special user that is only allowed to run one program, and that program does one thing: accepts as input a public key, and outputs [without races] an allocated IP, endpoint, and the server's public key. Under the hood that information could be stored in a variety of ways. Alternatively, this could be its own protocol over the wire or over TLS or over whatever the pre-established trust mechanism is that the idea is based on. One of the earliest dirty bash scripts for WireGuard did this (insecurely) over TCP --
https://git.zx2c4.com/WireGuard/tree/contrib/examples/ncat-client-server/server.sh
-- this is what's running on demo.wireguard.com.Jason
Hi Jason,
Quoted 17 lines · Jason A. Donenfeld
Hi ST,
It's a cool idea using the file system like that (the sticky bit would make the permissions part work correctly, perhaps), though I wonder if it's a bit complicated. If the model you're after is simply "server allocates IPs for peers already known through some channel but with unknown wireguard public keys", then maybe a better SSH-based interface is a special user that is only allowed to run one program, and that program does one thing: accepts as input a public key, and outputs [without races] an allocated IP, endpoint, and the server's public key. Under the hood that information could be stored in a variety of ways. Alternatively, this could be its own protocol over the wire or over TLS or over whatever the pre-established trust mechanism is that the idea is based on. One of the earliest dirty bash scripts for WireGuard did this (insecurely) over TCP --
https://git.zx2c4.com/WireGuard/tree/contrib/examples/ncat-client-server/server.sh
-- this is what's running on demo.wireguard.com.
I looked into those scripts - thank you! What discourages me is its warning not to use in production! So I'm wondering maybe it is possible to just use DHCP server/client somehow with WG for this purpose? Are there any expected pitfalls for such a configuration?
Thank you!
The problem is that it uses unauthenticated TCP.
What uses unauthenticated TCP - your demo.wireguard.com script or DHCP?
Quoted 1 line · Jason A. Donenfeld
The problem is that it uses unauthenticated TCP.