Skip to content

Commit

Permalink
Merge pull request #4 from influxdata/feature/http-backend
Browse files Browse the repository at this point in the history
HTTP Backend
  • Loading branch information
fntlnz authored Apr 25, 2018
2 parents 486a05d + 1f0e569 commit eaca900
Show file tree
Hide file tree
Showing 9 changed files with 395 additions and 66 deletions.
112 changes: 110 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,115 @@ Each machine should be able to see the same distributed backend in order to join
## Implemented backends

- etcd
- aws metadata api (in progress)
- http(s) - with optional basic auth

### ETCD

The etcd backend is useful when you want to use etcd to synchronize wireguard peers.

Example usage:

- endpoint: the listen ip address on the current machine
- ipaddr: the ip address you want to assign to the interface
- etcd comma seprated list of etcd servers

```bash
./bin/wirey --endpoint 192.168.33.11 --ipaddr 172.30.0.4 --etcd 192.168.33.10:2379
```

### HTTP(s) with optional basic auth

The http backend is useful when you want to write your own implementation.

The only suppported auth mechanism for now is Basic Authentication.

Example usage:

- endpoint: the listen ip address on the current machine
- ipaddr: the ip address you want to assign to the interface
- http: the http endpoint where to reach the server without trailing slash (/)
- httpbasicauth: username and password to use if the server implements basic auth, in the form `username:password`

```bash
./bin/wirey --endpoint 192.168.33.12 --ipaddr 10.30.0.80 --http http://192.168.33.10:8080 --httpbasicauth "time:series"
```

#### HTTP Server endpoints
You can find an example of http server in [examples/httpbackend](examples/httpbackend)

Starting from the endpoint you provide you provide to wirey, the expected routes are:

#### POST `/{ifname}/{publickeysha}`

**URL parameters:**

- ifname: interface name, wirey defaults to `wg0`
- publickeysha: the sha256 of the public key, this is just used as a key and as of now it's not matched with anything in `wirey` since the real public key is embedded in the body.

**URL Example:**

```
https://myservice.com/wireguard-discovery/wg0/234sfkske03kdssk32
```

**Request Body example:**

```json
{
"Endpoint": "192.168.33.11:2345",
"IP": "10.30.0.10",
"PublicKey": "T053azhMRW1sV2tQbjVISUgycnZtQWt5bDdKN3hJL3IwMjhDWG1zNVRpbz0K"
}
```

**Expected status codes:**

- 201 Created
- 401 Unauthorized (for basic auth)

#### GET `/{ifname}`

**URL Example:**

```
https://myservice.com/wireguard-discovery/wg0
```

**URL parameters:**

- ifname: interface name, wirey defaults to `wg0`

**Description:**

Returns all the peers for the provided interface.


**Expected status codes:**

- 200 OK
- 401 Unauthorized (for basic auth)

**Response body example:**

```json
[
{
"Endpoint": "192.168.33.11:2345",
"IP": "10.30.0.10",
"PublicKey": "T053azhMRW1sV2tQbjVISUgycnZtQWt5bDdKN3hJL3IwMjhDWG1zNVRpbz0K"
},
{
"Endpoint": "192.168.33.12:2345",
"IP": "10.30.0.80",
"PublicKey": "ZlE5a005ZDV1enpGei8xc25STXpnb3U4MVJkYVFmTXczL0NRR2svdEFpRT0K"
},
{
"Endpoint": "192.168.33.13:2345",
"IP": "10.30.0.60",
"PublicKey": "WUp2cDFPb0FhTkU5UC9vdlQrb0tIK29XRGtxVDhQenlzZnR1R1p4eEF5OD0K"
}
]
```


## Local Development
Expand Down Expand Up @@ -47,7 +155,7 @@ make
vagrant ssh net-1
sudo su -
cd /vagrant
./wirey --endpoint 192.168.33.11 --ipaddr 172.30.0.4 --etcd 192.168.33.10:2379
./bin/wirey --endpoint 192.168.33.11 --ipaddr 172.30.0.4 --etcd 192.168.33.10:2379
```

### on net-2
Expand Down
1 change: 0 additions & 1 deletion backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@ package backend

type Backend interface {
Join(ifname string, peer Peer) error
Leave(ifname string, peer Peer) error
GetPeers(ifname string) ([]Peer, error)
}
19 changes: 4 additions & 15 deletions backend/etcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const (
)

type EtcdBackend struct {
Client *clientv3.Client
client *clientv3.Client
}

func NewEtcdBackend(endpoints []string) (*EtcdBackend, error) {
Expand All @@ -26,7 +26,7 @@ func NewEtcdBackend(endpoints []string) (*EtcdBackend, error) {
return nil, err
}
return &EtcdBackend{
Client: cli,
client: cli,
}, nil
}

Expand All @@ -37,7 +37,7 @@ func (e *EtcdBackend) Join(ifname string, p Peer) error {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
kvc := clientv3.NewKV(e.Client)
kvc := clientv3.NewKV(e.client)
_, err = kvc.Put(ctx, fmt.Sprintf("%s/%s/%s", etcdWireyPrefix, ifname, p.PublicKey), string(pj))
cancel()
if err != nil {
Expand All @@ -46,20 +46,9 @@ func (e *EtcdBackend) Join(ifname string, p Peer) error {
return nil
}

func (e *EtcdBackend) Leave(ifname string, p Peer) error {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
kvc := clientv3.NewKV(e.Client)
_, err := kvc.Delete(ctx, fmt.Sprintf("%s/%s/%s", etcdWireyPrefix, ifname, p.PublicKey))
cancel()
if err != nil {
return err
}
return nil
}

func (e *EtcdBackend) GetPeers(ifname string) ([]Peer, error) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
kvc := clientv3.NewKV(e.Client)
kvc := clientv3.NewKV(e.client)
res, err := kvc.Get(ctx, fmt.Sprintf("%s/%s", etcdWireyPrefix, ifname), clientv3.WithPrefix())
cancel()
if err != nil {
Expand Down
102 changes: 102 additions & 0 deletions backend/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package backend

import (
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"net"
"net/http"
"time"
)

type BasicAuth struct {
Username string
Password string
}
type HTTPBackend struct {
client *http.Client
baseurl string
BasicAuth *BasicAuth
}

func NewHTTPBackend(baseurl string) (*HTTPBackend, error) {
var transportWithTimeout = &http.Transport{
Dial: (&net.Dialer{
Timeout: 5 * time.Second,
}).Dial,
TLSHandshakeTimeout: 5 * time.Second,
}
return &HTTPBackend{
client: &http.Client{
Timeout: time.Second * 10,
Transport: transportWithTimeout,
},
baseurl: baseurl,
}, nil
}

func publicKeySHA256(key []byte) string {
h := sha256.New()
h.Write(key)
return fmt.Sprintf("%x", h.Sum(nil))
}

func (b *HTTPBackend) Join(ifname string, p Peer) error {
joinURL := fmt.Sprintf("%s/%s/%s", b.baseurl, ifname, publicKeySHA256(p.PublicKey))

jsonPeer, err := json.Marshal(p)
if err != nil {
return err
}
buf := bytes.NewBuffer(jsonPeer)
req, err := http.NewRequest("POST", joinURL, buf)
if err != nil {
return err
}

if b.BasicAuth != nil {
req.SetBasicAuth(b.BasicAuth.Username, b.BasicAuth.Password)
}

res, err := b.client.Do(req)
if err != nil {
return fmt.Errorf("request error during join: %s", err.Error())
}

if res.StatusCode != http.StatusCreated {
return fmt.Errorf("the join http request gave an unexpected status code: %d", res.StatusCode)
}
return nil
}

func (b *HTTPBackend) GetPeers(ifname string) ([]Peer, error) {
getPeersURL := fmt.Sprintf("%s/%s", b.baseurl, ifname)

req, err := http.NewRequest("GET", getPeersURL, nil)
if err != nil {
return nil, err
}

if b.BasicAuth != nil {
req.SetBasicAuth(b.BasicAuth.Username, b.BasicAuth.Password)
}

res, err := b.client.Do(req)
if err != nil {
return nil, fmt.Errorf("request error during get peers: %s", err.Error())
}

if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("the get peers http request gave an unexpected status code: %d", res.StatusCode)
}

peers := []Peer{}
err = json.NewDecoder(res.Body).Decode(&peers)

if err != nil {
return nil, fmt.Errorf("error decoding peers during get peers: %s", err.Error())
}

return peers, nil
}
51 changes: 22 additions & 29 deletions backend/plumber.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package backend
import (
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"io/ioutil"
"log"
Expand All @@ -17,6 +18,8 @@ import (
"github.com/vishvananda/netlink"
)

const ifnamesiz = 16

type Peer struct {
PublicKey []byte
Endpoint string
Expand Down Expand Up @@ -44,6 +47,12 @@ func NewInterface(b Backend, ifname string, endpoint string, ipaddr string, priv
return nil, err
}

// Check that the passed interface name is ok for the kernel
// https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git/tree/include/uapi/linux/if.h?h=v4.14.36#n33
if len(ifname) > ifnamesiz {
return nil, fmt.Errorf("the interface name size cannot be more than %d", ifnamesiz)
}

if _, err := os.Stat(privateKeyPath); os.IsNotExist(err) {
privKey, err := wireguard.Genkey()
if err != nil {
Expand Down Expand Up @@ -79,29 +88,6 @@ func NewInterface(b Backend, ifname string, endpoint string, ipaddr string, priv
}, nil
}

func checkLinkAlreadyConnected(name string, peers []Peer, localPeer Peer) bool {
link, err := netlink.LinkByName(name)
if err != nil {
return false
}
if link == nil {
return false
}

for _, peer := range peers {
if bytes.Equal(peer.PublicKey, localPeer.PublicKey) {
// oh gosh, I have the interface but the link is down
if link.Attrs().OperState != netlink.OperUp {
// TODO(fntlnz): check here that the link type is wireguard?
return false
}
// Well I am already connected
return true
}
}
return false
}

func extractPeersSHA(workingPeers []Peer) string {
sort.Slice(workingPeers, func(i, j int) bool {
comparison := bytes.Compare(workingPeers[i].PublicKey, workingPeers[j].PublicKey)
Expand All @@ -112,9 +98,14 @@ func extractPeersSHA(workingPeers []Peer) string {
})
keys := ""
for _, p := range workingPeers {
keys = fmt.Sprintf("%s%s", keys, p.PublicKey)
// hash the full peer to verify if it changed
peerj, _ := json.Marshal(p)
peerh := sha256.New()
peerh.Write(peerj)
keys = fmt.Sprintf("%s%x", keys, peerh.Sum(nil))
}

// hash of all the peers
h := sha256.New()
h.Write([]byte(keys))

Expand All @@ -135,17 +126,18 @@ func (i *Interface) addressAlreadyTaken() (bool, error) {
}

func (i *Interface) Connect() error {
restart:
taken, err := i.addressAlreadyTaken()

if err != nil {
return err
log.Printf("Error during the first connection: %s, Retry in 5 seconds.", err.Error())
time.Sleep(time.Second * 5)
goto restart
}

if taken {
return fmt.Errorf("address already taken: %s", *i.LocalPeer.IP)
}
// Leave so I can recreate the peer on the distributed store
i.Backend.Leave(i.Name, i.LocalPeer)

// Join
err = i.Backend.Join(i.Name, i.LocalPeer)
Expand All @@ -158,13 +150,14 @@ func (i *Interface) Connect() error {
for {
workingPeers, err := i.Backend.GetPeers(i.Name)
if err != nil {
return err
log.Printf("Error extracting getting peers from backend: %s. Retry in 5 seconds", err.Error())
time.Sleep(time.Second * 5)
continue
}

// We don't change anything if the peers remain the same
newPeersSHA := extractPeersSHA(workingPeers)
if newPeersSHA == peersSHA {
peersSHA = newPeersSHA
time.Sleep(time.Second * 5)
continue
}
Expand Down
Loading

0 comments on commit eaca900

Please sign in to comment.