WireGuard (with peering negotiation) over Keybase
Disclaimer
This repository is not an official Keybase product. This is an experiment. It will likely not work for you.
Idea
(Keybase service and KBFS has to be running in the background.)
User selects a Keybase team and "connects to team VPN" - right now a using CLI command with team name as an argument. kb-wireguard sets up a WireGuard device, and negotiates peering using Keybase chat. There is a peers.json
file stored in team's KBFS share which maps username+device name to virtual network IPv4 address.
Example peers.json
:
[
{ "username": "zaputest", "device": "Serv 1", "ip": "100.0.0.1" },
{ "username": "zaputest", "device": "Serv 2", "ip": "100.0.0.2" },
{ "username": "zaputest", "device": "Linux Host", "ip": "100.0.0.3" }
]
IP addresses are mapped per device (not per user). This way, a single user can use this to connect all of their devices, no matter where physically they are and what public network they are connected to.
The convention is to use 100.0.0.x
addresses but that's not a technical requirement, it's simply a quick&dirty design decision.
Benefits of using Keybase
There is a central resource that can store peering source of truth that's protected by users' and teams' signature chains. Keybase can't inject new peers into users' VPNs.
Keybase offers a file sharing service that can be used to store configuration, as well as real time text chat service that can be used for presence notifications and pubkey / endpoint announcements.
Peer list management
Successful peering depends on every client managing a list of peers locally. Initially it's loaded from peers.json
. Note lack of endpoint addresses and public keys - what's in peers.json
is not enough to establish a connection.
When a peer comes on-line (kb-wireguard
tool is launched), the following happen:
- Load
peers.json
. See if current device can peer with that team, if not, abort. (TODO: clients should not have to be in the peers table to participate in the network to support "VPN to the servers but not each other" scenario) - Setup a WireGuard device with a public/private key pair (new key pair every time).
- Fetch recent messages from
#announce
channel of team's chat on Keybase. Match messages to peers loaded frompeers.json
. Add peers to WireGuard config and sync it. At this point we should be able to connect to these peers using VPN IP addresses. - Send a message to
#announce
channel with our endpoint IP and public key.
Example "announce" message looks like this:
ANNOUNCE 94.130.0.10:7321 jc+Ipv9/W4B6WD/EuVsFMVQjMcBYFfiw5NJD28ffqzE=
They are being exchanged using "CHAT" topic type for easier debugging, but the plan is to just move to "DEV".
Code layout
cmd/kb-wireguard
- Main entry point to the program. Does setup and runs background tasks.cmd/run-dev
- Separate program, ran as super user, to setup WireGuard device usingip
andwg
commands. Receives configuration updates (peer list) over named pipe and synchronizes it usingwg syncconf
command. Removes WireGuard device after INT or TERM signal.devowner/wireguard.go
- Utilities forrun-dev
to interact withwg
command.kbwg/program.go
- Types that hold current state ofkb-wireguard
program.kbwg/peerlist.go
- Types for peer list and functions to load them from KBFS and serialize to WireGuard config compatible types to send torun-dev
.kbwg/announce.go
- Reading and sending announcements through Keybase chat.kbwg/keybase.go
- Keybase utilities that were not available ingo-keybase-chat-bot
library.kbwg/run_dev_owner.go
- Runs and communicates withrun-dev
program.run-dev
is ran with stdin passed fromkb-wireguard
process so interactivesudo
works.libpipe
- Types and functions forkb-wireguard
andrun-dev
to communicate through named pipe.libwireguard
- More helper functions and types to interact with WireGuard config file andwg
command.
Additionally, not required by kb-wireguard
to function:
cmd/lan-chat
- Test program that broadcasts to UDP messages to all 100.0.0.x IP addresses (NOTE: WireGuard does not support 100.0.0.255 broadcast address by design), and listens as well.cmd/stun-test
- Contacts Google STUN server to discover IP and port, prints that to stdout, does an UDP listen on that port for testing.
Problems / TODOs
-
Announcing just one endpoint through Keybase chat is not enough to peer with everyone. It works if you have confirmed, working, public ip address and port. Consider the following scenarios which need more effort:
- You have a punchable NAT, but it turns out some peers are in the same local network as you are. You won't be able to connect to them using their punched IP/Port.
- (Similar to above) you have more than one network interface and connection to some peers is better through one interface than the other. There should be a zero-config way of establishing these for the user.
- Someone is behind NAT that's not connectable to. TURN(-like) connection negotiation is required.
- To solve this, keep the announcements in
#announce
channel, but do additional negotiation steps that peers will do either out of band (but still on Keybase), or in the team channel.
-
Add a way of connection to VPN without being in
peers.json
. Consider an organization that has some servers and want people to access them via VPN.- There would be a
foo_org.vpn
team for that with all sysadmins and servers in there. - But only servers should be defined in
peers.json
with static IP addresses. - Everyone else would need a dynamically assigned IP address, in same subnet.
- But there is no central authority to assign them. Clients would need to randomize addresses themselves and resolve conflicts E.g. if two announced same random address at roughly the same time, look at messageId, lower wins. Loser recognizes it lost and re-rolls, winner doesn't have to do anything.
- There would be a
License:
No license yet. All rights reserved. Please don't use this, it's not ready.