Skip to content

Commit

Permalink
Introduced new config format where the local node is listed as a norm…
Browse files Browse the repository at this point in the history
…al peer to allow for copying the same file to different machines.
  • Loading branch information
apognu committed Mar 23, 2020
1 parent bbf462b commit d36045b
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 147 deletions.
98 changes: 63 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,24 @@ This tool is very opinionated and designed for my own use (working on that), it

The configuration file (which is subject to breaking changes until 1.0) should look like this:

```
interface:
description: Personal VPN server #1
address: 192.168.0.1/32
listen_port: 42000
private_key: /etc/wireguard/vpn1.key
fwmark: 1024
routes: false
post_up:
- [ '/usr/bin/notify-send', 'WireGuard tunnel went up', 'A WireGuard tunnel was just brought up. Congrats.' ]
pre_down:
- [ '/usr/bin/notify-send', 'WireGuard tunnel went down', 'A WireGuard tunnel was just torn down. Congrats.' ]
```yaml
description: Personal VPN server #1
private_key: /etc/wireguard/vpn1.key
peers:
- description: Local laptop
address: 192.168.0.1/32
listen_port: 42000
public_key: BooRta+d0t/2djkdZ3xfe/5xndKvPtfqH3pdZcdZ2TY=
preshared_key: e16f1596201850fd4a63680b27f603cb64e67176159be3d8ed78a4403fdb1700
fwmark: 1024
routes: false
post_up:
- [ '/usr/bin/notify-send', 'WireGuard tunnel went up', 'A WireGuard tunnel was just brought up. Congrats.' ]
pre_down:
- [ '/usr/bin/notify-send', 'WireGuard tunnel went down', 'A WireGuard tunnel was just torn down. Congrats.' ]
- description: VPN gateway at provider X
address: 192.168.0.2/32
listen_port: 42000
public_key: cyfBMbaJ6kgnDYjio6xqWikvTz2HvpmvSQocRmF/ZD4=
preshared_key: e16f1596201850fd4a63680b27f603cb64e67176159be3d8ed78a4403fdb1700
endpoint: 1.2.3.4:42000
Expand All @@ -38,37 +42,41 @@ The ```post_up``` and ```pre_down``` directives take an array of arrays of comma

Keep in mind that in order to put IPv6 addresses in the configuration, you'll need to coerce the value to a string with quotes :

```
```yaml
peers:
- endpoint: '[cafe:1:2:3::1]:10000'
```

The configuration is built so as to be able to be copied on all peers identically, the current node is detected when a peer public key matches the private key at the root of the file.

## Build

```
```shell
$ go get -u github.com/apognu/wgctl
```

or

```
```shell
$ git clone https://github.com/apognu/wgctl.git && cd wgctl
$ dep ensure
$ go build .
```

You can, of course, get a prebuilt binary from the [Releases](https://github.com/apognu/wgctl/releases) section.

### Testing

You can run the tests for this project, as root (since we are testing netlink communication and device creation). Keep in mind that this will modify properties on your live system (devices, routes, /proc settings, etc.), so use with caution.

```
```shell
$ sudo -E go test ./...
```

## Usage

```
# wgctl help
```shell
$ wgctl help
usage: wgctl [<flags>] <command> [<args> ...]
WireGuard control plane helper
Expand Down Expand Up @@ -96,36 +104,36 @@ Commands:

### Control the state of tunnels

```
# wgctl start vpn
# wgctl start -f vpn
# wgctl stop vpn
# wgctl restart vpn
```shell
$ wgctl start -f vpn
$ wgctl start vpn
$ wgctl stop vpn
$ wgctl restart vpn
```

### Obtain the state of all configured or active tunnels

The ```-s``` option only displays the name of active tunnels, for ease of use in scripts.

```
# wgctl status
$ wgctl status
[] tunnel 'vpn1' is down
[] tunnel 'vpn2' is up and running
[] tunnel 'corporate' is down
[] tunnel 'personal' is up and running

# wgctl status -s
$ wgctl status -s
vpn2
personal

# wgctl status vpn1
$ wgctl status vpn1
[] tunnel 'vpn1' is down
```
### Get configuration and runtime details for an active tunnel
```
# wgctl info vpn2
```shell
$ wgctl info vpn2
tunnel:
interface: Personal VPN tunnel #2
public key: SqtWXnIGoHWibfqZwAe6iFc560wWuV6zUL+4CqzDxlQ=
Expand All @@ -142,15 +150,15 @@ tunnel:

Those changes are not persisted, if you want to export the current configuration of a tunnel, use ```export``` below. Please note that you can provide a subset of the options shown below.

```
```shell
# Change properties on the interface itself
# wgctl set vpn1 privkey=/etc/wireguard/new.key port=43210 fwmark=1437
$ wgctl set vpn1 privkey=/etc/wireguard/new.key port=43210 fwmark=1437

# Add a new peer or change the properties of the peer with the given public key
# wgctl peer set vpn1 pubkey=sSg9kL+KsMBQpFPO+TXl7A4OKjLb0xWORx7eR3JDjXM= endpoint=192.168.255.254:10000 allowedips=2.2.2.2/24,3.3.3.3/30 keepalive=20 psk=636493c476092bf06806794d6c2d62c990c68a39b71b73019a328a4d646d9e42
$ wgctl peer set vpn1 pubkey=sSg9kL+KsMBQpFPO+TXl7A4OKjLb0xWORx7eR3JDjXM= endpoint=192.168.255.254:10000 allowedips=2.2.2.2/24,3.3.3.3/30 keepalive=20 psk=636493c476092bf06806794d6c2d62c990c68a39b71b73019a328a4d646d9e42

# Replace the whole set of peers with the given one
# wgctl peer replace vpn1 pubkey=sSg9kL+KsMBQpFPO+TXl7A4OKjLb0xWORx7eR3JDjXM= endpoint=192.168.255.254:10000 allowedips=2.2.2.2/24,3.3.3.3/30 keepalive=20 psk=636493c476092bf06806794d6c2d62c990c68a39b71b73019a328a4d646d9e42
$ wgctl peer replace vpn1 pubkey=sSg9kL+KsMBQpFPO+TXl7A4OKjLb0xWORx7eR3JDjXM= endpoint=192.168.255.254:10000 allowedips=2.2.2.2/24,3.3.3.3/30 keepalive=20 psk=636493c476092bf06806794d6c2d62c990c68a39b71b73019a328a4d646d9e42
```

### Export the configuration of a tunnel
Expand All @@ -159,8 +167,8 @@ You can export the current configuration of an active tunnel by using the ```wgc

Please note that if the tunnel was not created through ```wgctl```, the private key path will be left blank.

```
# wgctl export vpn1
```shell
$ wgctl export vpn1
interface:
description: Personal VPN server #1
address: 192.168.0.1/32
Expand All @@ -184,7 +192,7 @@ peers:

### Generate keys to be used by WireGuard

```
```shell
$ wgctl key private
nAyxQotWfano6/cC9S6fjSRYe9oQ0/GQn2mK9/PXvyg=
$ wgctl key private | wgctl key public
Expand All @@ -200,3 +208,23 @@ By default, ```wgctl``` will add routes matching your allowed IP addresses in or
If you want to manage the routing yourself, you can pass ```--no-routes``` to ```wgctl start``` and ```wgctl restart``` to prevent that behavior. You can also set the ```interface``` directive ```routes``` to ```false``` to disable this behavior permanently.

```wgctl``` will not touch your firewall rules, if you need to open a port or add specific rules, you'll need to do it yourself manually, or use a ```post_up``` directive.

## Use as a service

You can tell `wgctl` to stay in the foreground by starting your tunnel with the `-f` flag. This allows you to start up your tunnels as daemons with, for example, this `systemd` service unit:

```shell
$ cat /etc/systemd/system/[email protected]
[Unit]
Description=Wireguard tunnel

[Service]
Type=simple
Restart=always
WorkingDirectory=/etc/wireguard
ExecStart=/usr/local/bin/wgctl start -f %i
ExecStopPost=-/usr/local/bin/wgctl stop %i

[Install]
WantedBy=multi-user.target
```
30 changes: 16 additions & 14 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ func exportConfig(instance string) {
logrus.Fatal(err)
}

c := lib.Config{}
c := lib.Config{
Self: &lib.Peer{},
}

addrs4, err4 := nl.AddrList(rtdev, unix.AF_INET)
addrs6, err6 := nl.AddrList(rtdev, unix.AF_INET6)
Expand All @@ -29,7 +31,7 @@ func exportConfig(instance string) {
ip := addrs[0].IP
mask, _ := addrs[0].IPNet.Mask.Size()

c.Interface.Address = &lib.IPMask{IP: ip, Mask: mask}
c.Self.Address = &lib.IPMask{IP: ip, Mask: mask}
}

priv := lib.PrivateKey{Path: "/path/to/private.key"}
Expand All @@ -38,20 +40,20 @@ func exportConfig(instance string) {
postUp := [][]string{}
routes := new(bool)
if currentConfig != nil {
priv = currentConfig.Interface.PrivateKey
description = currentConfig.Interface.Description
preDown = currentConfig.Interface.PreDown
postUp = currentConfig.Interface.PostUp
routes = currentConfig.Interface.SetUpRoutes
priv = currentConfig.PrivateKey
description = currentConfig.Description
preDown = currentConfig.Self.PreDown
postUp = currentConfig.Self.PostUp
routes = currentConfig.Self.SetUpRoutes
}

c.Interface.Description = description
c.Interface.PrivateKey = priv
c.Interface.ListenPort = wgdev.ListenPort
c.Interface.FWMark = wgdev.FirewallMark
c.Interface.PreDown = preDown
c.Interface.PostUp = postUp
c.Interface.SetUpRoutes = routes
c.Description = description
c.PrivateKey = priv
c.Self.ListenPort = wgdev.ListenPort
c.Self.FWMark = wgdev.FirewallMark
c.Self.PreDown = preDown
c.Self.PostUp = postUp
c.Self.SetUpRoutes = routes

peers := make([]*lib.Peer, len(wgdev.Peers))
for idx, wgp := range wgdev.Peers {
Expand Down
51 changes: 30 additions & 21 deletions lib/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,30 +104,26 @@ func (ip IPMask) String() string {

// Config represents a YAML-encodable configuration for a WireGuard tunnel
type Config struct {
Interface Interface `yaml:"interface"`
Peers []*Peer `yaml:"peers"`
}

// Interface represents a YAML-encodable configuration for a WireGuard interface
type Interface struct {
Description string `yaml:"description"`
Address *IPMask `yaml:"address"`
ListenPort int `yaml:"listen_port"`
PrivateKey PrivateKey `yaml:"private_key"`
FWMark int `yaml:"fwmark,omitempty"`
PostUp [][]string `yaml:"post_up,omitempty"`
PreDown [][]string `yaml:"pre_down,omitempty"`
SetUpRoutes *bool `yaml:"routes,omitempty"`
Self *Peer `yaml:"-"`
Peers []*Peer `yaml:"peers"`
}

// Peer represents a YAML-encodable configuration for a WireGuard peer
type Peer struct {
Description string `yaml:"description"`
Address *IPMask `yaml:"address"`
ListenPort int `yaml:"listen_port"`
PublicKey Key `yaml:"public_key"`
PresharedKey *PresharedKey `yaml:"preshared_key,omitempty"`
Endpoint *UDPAddr `yaml:"endpoint,omitempty"`
AllowedIPS []IPNet `yaml:"allowed_ips,omitempty"`
KeepaliveInterval time.Duration `yaml:"keepalive_interval,omitempty"`
FWMark int `yaml:"fwmark,omitempty"`
PostUp [][]string `yaml:"post_up,omitempty"`
PreDown [][]string `yaml:"pre_down,omitempty"`
SetUpRoutes *bool `yaml:"routes,omitempty"`
}

// ParseConfig unmarshals a Config from a YAML string
Expand Down Expand Up @@ -164,21 +160,34 @@ func ParseConfigReader(config io.Reader) (*Config, error) {
// Check verifies that all mandatory config directive have been given for a Config
// It also sets default values for some fields
func (c *Config) Check() error {
if c.Interface.SetUpRoutes == nil {
v := true
c.Interface.SetUpRoutes = &v
}
if c.Interface.PrivateKey.Data == EmptyPSK {
if c.PrivateKey.Data == EmptyPSK {
return fmt.Errorf("'private_key' must be provided")
}
if c.Interface.ListenPort == 0 {
return fmt.Errorf("'listen_port' must be provided")
}

for _, p := range c.Peers {
for idx, p := range c.Peers {
if len(p.PublicKey) != wgtypes.KeyLen {
return fmt.Errorf("peer's 'public_key' must be provided")
}

privkey := c.PrivateKey.Bytes()
pubkey := p.PublicKey.Bytes()

if bytes.Equal(ComputePublicKey(privkey[:]), pubkey[:]) {
c.Self = p
c.Peers = append(c.Peers[:idx], c.Peers[idx+1:]...)
}
}

if c.Self == nil {
return fmt.Errorf("could not find self in peer list")
}

if c.Self.SetUpRoutes == nil {
v := true
c.Self.SetUpRoutes = &v
}
if c.Self.ListenPort == 0 {
return fmt.Errorf("'listen_port' must be provided")
}

return nil
Expand Down
Loading

0 comments on commit d36045b

Please sign in to comment.