Add autogen for peers
This commit is contained in:
parent
99b6b1577b
commit
1bd241e745
6 changed files with 1061 additions and 89 deletions
102
README.md
Normal file
102
README.md
Normal file
|
@ -0,0 +1,102 @@
|
|||
# guard
|
||||
|
||||
A GRPC server for managing wireguard tunnels.
|
||||
|
||||
## Status: alpha
|
||||
|
||||
## Requirements
|
||||
|
||||
Wireguard and it's untilities, `wg`, `wg-quick`, and `wg-quick@.service` must be installed on the system hosting
|
||||
the `guard` server.
|
||||
|
||||
|
||||
### Create a new tunnel
|
||||
|
||||
|
||||
To create a new tunnel specify the address and the endpoint for the tunnel.
|
||||
The last argument is used as the tunnel ID and interface name on the server.
|
||||
|
||||
```bash
|
||||
> guard create --address 192.168.5.1/32 --endpoint 127.0.0.1:31000 wg0
|
||||
|
||||
{
|
||||
"id": "wg0",
|
||||
"private_key": "+EymZwYNHxGVe5T1gmTbwKmQgWksDlZzldTwoZi5lnQ=",
|
||||
"listen_port": "31000",
|
||||
"address": "192.168.5.1/32",
|
||||
"public_key": "irDV3wkkNe6f1GLAPFNGjj0xsQsoxPCNko4Lf3igcjM=",
|
||||
"endpoint": "127.0.0.1"
|
||||
}
|
||||
```
|
||||
|
||||
### Delete a tunnel
|
||||
|
||||
Delete a tunnel using the tunnel ID
|
||||
|
||||
```bash
|
||||
> guard delete wg0
|
||||
```
|
||||
|
||||
### Create a new peer
|
||||
|
||||
To create a new peer and have all the keys generated for you use the `peers new` command.
|
||||
The peer configuration will be output to `stdout` that you can copy and paste into your client.
|
||||
|
||||
```bash
|
||||
> guard peers --tunnel wg0 new --ip 192.168.5.2/32 --dns 192.168.5.1 --ips 192.168.5.0/24 --ips 192.168.0.1/24 mypeer
|
||||
|
||||
[Interface]
|
||||
PrivateKey = kFJ6VSq+l6sBPaI2DUbEWSVI83Kcfz/yo7WfVheT+FI=
|
||||
Address = 192.168.5.2/32
|
||||
DNS = 192.168.5.1
|
||||
|
||||
# wg0
|
||||
[Peer]
|
||||
PublicKey = irDV3wkkNe6f1GLAPFNGjj0xsQsoxPCNko4Lf3igcjM=
|
||||
AllowedIPs = 192.168.5.0/24, 192.168.0.1/24
|
||||
Endpoint = 127.0.0.1:31000
|
||||
```
|
||||
|
||||
### List all tunnels
|
||||
|
||||
```bash
|
||||
> guard list
|
||||
|
||||
[
|
||||
{
|
||||
"id": "wg0",
|
||||
"private_key": "+EymZwYNHxGVe5T1gmTbwKmQgWksDlZzldTwoZi5lnQ=",
|
||||
"listen_port": "31000",
|
||||
"address": "192.168.5.1/32",
|
||||
"peers": [
|
||||
{
|
||||
"id": "mypeer",
|
||||
"public_key": "u/eGf6olYeFSH4XoPvOSZJb9swA/qWPAlfSxRBi6Uw8=",
|
||||
"allowed_ips": [
|
||||
"192.168.5.2/32"
|
||||
],
|
||||
"private_key": "kFJ6VSq+l6sBPaI2DUbEWSVI83Kcfz/yo7WfVheT+FI="
|
||||
}
|
||||
],
|
||||
"public_key": "irDV3wkkNe6f1GLAPFNGjj0xsQsoxPCNko4Lf3igcjM=",
|
||||
"endpoint": "127.0.0.1"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Delete a peer by ID
|
||||
|
||||
You can remove and update peers using the `peers` commands.
|
||||
|
||||
```bash
|
||||
> guard peers --tunnel wg0 delete mypeer
|
||||
|
||||
{
|
||||
"id": "wg0",
|
||||
"private_key": "+EymZwYNHxGVe5T1gmTbwKmQgWksDlZzldTwoZi5lnQ=",
|
||||
"listen_port": "31000",
|
||||
"address": "192.168.5.1/32",
|
||||
"public_key": "irDV3wkkNe6f1GLAPFNGjj0xsQsoxPCNko4Lf3igcjM=",
|
||||
"endpoint": "127.0.0.1"
|
||||
}
|
||||
```
|
File diff suppressed because it is too large
Load diff
|
@ -8,16 +8,24 @@ import "google/protobuf/empty.proto";
|
|||
option go_package = "github.com/crosbymichael/guard/api/v1;v1";
|
||||
|
||||
service Wireguard {
|
||||
// Create a new tunnel
|
||||
rpc Create(CreateRequest) returns (TunnelResponse);
|
||||
// Delete a tunnel
|
||||
rpc Delete(DeleteRequest) returns (google.protobuf.Empty);
|
||||
// List all tunnels
|
||||
rpc List(google.protobuf.Empty) returns (ListResponse);
|
||||
// NewPeer to the tunnel with gernerated keys
|
||||
rpc NewPeer(NewPeerRequest) returns (PeerResponse);
|
||||
// AddPeer to the tunnel
|
||||
rpc AddPeer(AddPeerRequest) returns (TunnelResponse);
|
||||
// DeletePeer from a tunnel
|
||||
rpc DeletePeer(DeletePeerRequest) returns (TunnelResponse);
|
||||
}
|
||||
|
||||
message CreateRequest {
|
||||
// id of the tunnel
|
||||
string id = 1 [(gogoproto.customname) = "ID"];
|
||||
uint32 listen_port = 2;
|
||||
string endpoint = 2;
|
||||
string address = 3;
|
||||
}
|
||||
|
||||
|
@ -25,17 +33,32 @@ message TunnelResponse {
|
|||
Tunnel tunnel = 1;
|
||||
}
|
||||
|
||||
message PeerResponse {
|
||||
Tunnel tunnel = 1;
|
||||
Peer peer = 2;
|
||||
}
|
||||
|
||||
message NewPeerRequest {
|
||||
// id of the tunnel
|
||||
string id = 1 [(gogoproto.customname) = "ID"];
|
||||
string peer_id = 2 [(gogoproto.customname) = "PeerID"];
|
||||
string address = 3;
|
||||
}
|
||||
|
||||
message AddPeerRequest {
|
||||
// id of the tunnel
|
||||
string id = 1 [(gogoproto.customname) = "ID"];
|
||||
Peer peer = 2;
|
||||
}
|
||||
|
||||
message DeletePeerRequest {
|
||||
// id of the tunnel
|
||||
string id = 1 [(gogoproto.customname) = "ID"];
|
||||
string peer_id = 2 [(gogoproto.customname) = "PeerID"];
|
||||
}
|
||||
|
||||
message DeleteRequest {
|
||||
// id of the tunnel
|
||||
string id = 1 [(gogoproto.customname) = "ID"];
|
||||
}
|
||||
|
||||
|
@ -46,11 +69,13 @@ message ListResponse {
|
|||
message Tunnel {
|
||||
string id = 1 [(gogoproto.customname) = "ID"];
|
||||
string private_key = 2;
|
||||
uint32 listen_port = 3;
|
||||
string listen_port = 3;
|
||||
string address = 4;
|
||||
string dns = 5 [(gogoproto.customname) = "DNS"];
|
||||
repeated Peer peers = 6;
|
||||
Masquerade masquerade = 7;
|
||||
string public_key = 8;
|
||||
string endpoint = 9;
|
||||
}
|
||||
|
||||
message Peer {
|
||||
|
@ -59,6 +84,7 @@ message Peer {
|
|||
repeated string allowed_ips = 3 [(gogoproto.customname) = "AllowedIPs"];
|
||||
string endpoint = 4;
|
||||
uint32 persistent_keepalive = 5;
|
||||
string private_key = 6;
|
||||
}
|
||||
|
||||
message Masquerade {
|
||||
|
|
|
@ -37,15 +37,13 @@ import (
|
|||
|
||||
const confFmt = `[Interface]
|
||||
PrivateKey = {{.PrivateKey}}
|
||||
{{if gt .ListenPort 0}}ListenPort = {{.ListenPort}}{{end}}
|
||||
{{if .ListenPort}}ListenPort = {{.ListenPort}}{{end}}
|
||||
Address = {{.Address}}
|
||||
{{if .DNS }}DNS = {{.DNS}}{{end}}
|
||||
|
||||
{{if .Masquerade}}
|
||||
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o {{.Masquerade.Interface}} -j MASQUERADE
|
||||
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o {{.Masquerade.Interface}} -j MASQUERADE
|
||||
{{end}}
|
||||
|
||||
{{range $peer := .Peers -}}
|
||||
# {{$peer.ID}}
|
||||
[Peer]
|
||||
|
|
68
main.go
68
main.go
|
@ -136,9 +136,10 @@ var createCommand = cli.Command{
|
|||
Name: "address,a",
|
||||
Usage: "cidr for the tunnel address",
|
||||
},
|
||||
cli.UintFlag{
|
||||
Name: "port,p",
|
||||
Usage: "listen port for the tunnel",
|
||||
cli.StringFlag{
|
||||
Name: "endpoint,e",
|
||||
Usage: "set the endpoint for the tunnel",
|
||||
Value: "127.0.0.1:31000",
|
||||
},
|
||||
},
|
||||
Action: func(clix *cli.Context) error {
|
||||
|
@ -154,9 +155,9 @@ var createCommand = cli.Command{
|
|||
)
|
||||
|
||||
r, err := client.Create(ctx, &v1.CreateRequest{
|
||||
ID: clix.Args().First(),
|
||||
Address: clix.String("address"),
|
||||
ListenPort: uint32(clix.Uint("port")),
|
||||
ID: clix.Args().First(),
|
||||
Address: clix.String("address"),
|
||||
Endpoint: clix.String("endpoint"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -204,6 +205,61 @@ var peersCommand = cli.Command{
|
|||
},
|
||||
},
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "new",
|
||||
Description: "create a new peer",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "ip,i",
|
||||
Usage: "ip cidr for the peer",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "dns",
|
||||
Usage: "dns for the peer",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "ips",
|
||||
Usage: "allowed ips",
|
||||
Value: &cli.StringSlice{},
|
||||
},
|
||||
},
|
||||
Action: func(clix *cli.Context) error {
|
||||
conn, err := grpc.Dial(clix.GlobalString("address"), grpc.WithInsecure())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "dial server")
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var (
|
||||
ctx = cancelContext()
|
||||
client = v1.NewWireguardClient(conn)
|
||||
)
|
||||
|
||||
r, err := client.NewPeer(ctx, &v1.NewPeerRequest{
|
||||
ID: clix.GlobalString("tunnel"),
|
||||
PeerID: clix.Args().First(),
|
||||
Address: clix.String("ip"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// generate peer tunnel config
|
||||
t := &v1.Tunnel{
|
||||
PrivateKey: r.Peer.PrivateKey,
|
||||
Address: r.Peer.AllowedIPs[0],
|
||||
DNS: clix.String("dns"),
|
||||
Peers: []*v1.Peer{
|
||||
{
|
||||
ID: r.Tunnel.ID,
|
||||
PublicKey: r.Tunnel.PublicKey,
|
||||
Endpoint: net.JoinHostPort(r.Tunnel.Endpoint, r.Tunnel.ListenPort),
|
||||
AllowedIPs: clix.StringSlice("ips"),
|
||||
},
|
||||
},
|
||||
}
|
||||
return t.Render(os.Stdout)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "add",
|
||||
Description: "add a peer",
|
||||
|
|
73
server.go
73
server.go
|
@ -31,7 +31,9 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
@ -69,8 +71,12 @@ func (s *server) Create(ctx context.Context, r *v1.CreateRequest) (*v1.TunnelRes
|
|||
if r.Address == "" {
|
||||
return nil, errors.New("address cannot be empty")
|
||||
}
|
||||
if r.ListenPort == 0 {
|
||||
return nil, errors.New("listen port cannot be 0")
|
||||
if r.Endpoint == "" {
|
||||
return nil, errors.New("endpoint cannot be empty")
|
||||
}
|
||||
host, port, err := net.SplitHostPort(r.Endpoint)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot split endpoint into host and port")
|
||||
}
|
||||
path := filepath.Join(s.dir, r.ID)
|
||||
if err := os.Mkdir(path, 0700); err != nil {
|
||||
|
@ -83,11 +89,17 @@ func (s *server) Create(ctx context.Context, r *v1.CreateRequest) (*v1.TunnelRes
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pub, err := publicKey(ctx, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t := v1.Tunnel{
|
||||
ID: r.ID,
|
||||
ListenPort: r.ListenPort,
|
||||
ListenPort: port,
|
||||
Address: r.Address,
|
||||
PrivateKey: key,
|
||||
PublicKey: pub,
|
||||
Endpoint: host,
|
||||
}
|
||||
|
||||
if err := s.saveTunnel(&t); err != nil {
|
||||
|
@ -109,6 +121,46 @@ func (s *server) Create(ctx context.Context, r *v1.CreateRequest) (*v1.TunnelRes
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s *server) NewPeer(ctx context.Context, r *v1.NewPeerRequest) (*v1.PeerResponse, error) {
|
||||
if r.ID == "" {
|
||||
return nil, errors.New("tunnel id cannot be empty")
|
||||
}
|
||||
t, err := s.loadTunnel(r.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peerKey, err := newPrivateKey(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
publicKey, err := publicKey(ctx, peerKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peer := &v1.Peer{
|
||||
ID: r.PeerID,
|
||||
PublicKey: publicKey,
|
||||
PrivateKey: peerKey,
|
||||
AllowedIPs: []string{
|
||||
r.Address,
|
||||
},
|
||||
}
|
||||
t.Peers = append(t.Peers, peer)
|
||||
if err := s.saveTunnel(t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.saveConf(t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := wgquick(ctx, "restart", t.ID); err != nil {
|
||||
return nil, errors.Wrap(err, "restart tunnel")
|
||||
}
|
||||
return &v1.PeerResponse{
|
||||
Tunnel: t,
|
||||
Peer: peer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *server) AddPeer(ctx context.Context, r *v1.AddPeerRequest) (*v1.TunnelResponse, error) {
|
||||
if r.ID == "" {
|
||||
return nil, errors.New("tunnel id cannot be empty")
|
||||
|
@ -250,11 +302,26 @@ func newPrivateKey(ctx context.Context) (string, error) {
|
|||
return strings.TrimSuffix(string(data), "\n"), nil
|
||||
}
|
||||
|
||||
func publicKey(ctx context.Context, privateKey string) (string, error) {
|
||||
r := strings.NewReader(privateKey)
|
||||
data, err := wireguardData(ctx, r, "pubkey")
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "%s", data)
|
||||
}
|
||||
return strings.TrimSuffix(string(data), "\n"), nil
|
||||
}
|
||||
|
||||
func wireguard(ctx context.Context, args ...string) ([]byte, error) {
|
||||
cmd := exec.CommandContext(ctx, "wg", args...)
|
||||
return cmd.CombinedOutput()
|
||||
}
|
||||
|
||||
func wireguardData(ctx context.Context, r io.Reader, args ...string) ([]byte, error) {
|
||||
cmd := exec.CommandContext(ctx, "wg", args...)
|
||||
cmd.Stdin = r
|
||||
return cmd.CombinedOutput()
|
||||
}
|
||||
|
||||
func wgquick(ctx context.Context, action, name string) error {
|
||||
cmd := exec.CommandContext(ctx, "systemctl", action, fmt.Sprintf("wg-quick@%s", name))
|
||||
out, err := cmd.CombinedOutput()
|
||||
|
|
Loading…
Reference in a new issue