Create guard tunnel

This commit is contained in:
Michael Crosby 2019-07-11 12:06:54 +00:00
parent 1bd241e745
commit 717e860f65
3 changed files with 85 additions and 15 deletions

View file

@ -9,6 +9,15 @@ A GRPC server for managing wireguard tunnels.
Wireguard and it's untilities, `wg`, `wg-quick`, and `wg-quick@.service` must be installed on the system hosting Wireguard and it's untilities, `wg`, `wg-quick`, and `wg-quick@.service` must be installed on the system hosting
the `guard` server. the `guard` server.
### Run the server
When you run the wireguard server it will automatically create its own wireguard tunnel
that the server binds to. This makes the server secure to manage across your network.
Use the `--address` flag to manage this server.
```bash
> sudo guard server
```
### Create a new tunnel ### Create a new tunnel

44
main.go
View file

@ -59,7 +59,7 @@ func main() {
cli.StringFlag{ cli.StringFlag{
Name: "address,a", Name: "address,a",
Usage: "grpc address", Usage: "grpc address",
Value: "127.0.0.1:10100", Value: "10.199.199.1:10100",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "sentry-dsn", Name: "sentry-dsn",
@ -112,6 +112,28 @@ var serverCommand = cli.Command{
server := newGRPC() server := newGRPC()
v1.RegisterWireguardServer(server, wg) v1.RegisterWireguardServer(server, wg)
ctx := cancelContext()
address := clix.GlobalString("address")
host, _, err := net.SplitHostPort(address)
if err != nil {
return errors.Wrap(err, "splitting tunnel address")
}
r, err := wg.Create(ctx, &v1.CreateRequest{
ID: guardTunnel,
Address: host + "/32",
Endpoint: address,
})
if err == nil {
logrus.Info("created guard0 tunnel")
if err := jsonTunnel(r.Tunnel); err != nil {
return err
}
} else if err != ErrTunnelExists {
return err
}
// create our server tunnel
signals := make(chan os.Signal, 32) signals := make(chan os.Signal, 32)
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT) signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
@ -119,7 +141,7 @@ var serverCommand = cli.Command{
<-signals <-signals
server.Stop() server.Stop()
}() }()
l, err := net.Listen("tcp", clix.GlobalString("address")) l, err := net.Listen("tcp", address)
if err != nil { if err != nil {
return errors.Wrap(err, "listen tcp") return errors.Wrap(err, "listen tcp")
} }
@ -162,9 +184,7 @@ var createCommand = cli.Command{
if err != nil { if err != nil {
return err return err
} }
enc := json.NewEncoder(os.Stdout) return jsonTunnel(r.Tunnel)
enc.SetIndent("", " ")
return enc.Encode(r.Tunnel)
}, },
} }
@ -298,9 +318,7 @@ var peersCommand = cli.Command{
if err != nil { if err != nil {
return err return err
} }
enc := json.NewEncoder(os.Stdout) return jsonTunnel(r.Tunnel)
enc.SetIndent("", " ")
return enc.Encode(r.Tunnel)
}, },
}, },
{ {
@ -325,9 +343,7 @@ var peersCommand = cli.Command{
if err != nil { if err != nil {
return err return err
} }
enc := json.NewEncoder(os.Stdout) return jsonTunnel(r.Tunnel)
enc.SetIndent("", " ")
return enc.Encode(r.Tunnel)
}, },
}, },
}, },
@ -390,3 +406,9 @@ func cancelContext() context.Context {
}() }()
return ctx return ctx
} }
func jsonTunnel(t *v1.Tunnel) error {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(t)
}

View file

@ -42,13 +42,18 @@ import (
v1 "github.com/crosbymichael/guard/api/v1" v1 "github.com/crosbymichael/guard/api/v1"
"github.com/gogo/protobuf/types" "github.com/gogo/protobuf/types"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus"
) )
var empty = &types.Empty{} var (
empty = &types.Empty{}
ErrTunnelExists = errors.New("tunnel exists")
)
const ( const (
defaultWireguardDir = "/etc/wireguard" defaultWireguardDir = "/etc/wireguard"
tunnelData = "tunnel.json" tunnelData = "tunnel.json"
guardTunnel = "guard0"
) )
func newServer(dir string) (*server, error) { func newServer(dir string) (*server, error) {
@ -74,6 +79,7 @@ func (s *server) Create(ctx context.Context, r *v1.CreateRequest) (*v1.TunnelRes
if r.Endpoint == "" { if r.Endpoint == "" {
return nil, errors.New("endpoint cannot be empty") return nil, errors.New("endpoint cannot be empty")
} }
log := logrus.WithField("tunnel", r.ID)
host, port, err := net.SplitHostPort(r.Endpoint) host, port, err := net.SplitHostPort(r.Endpoint)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "cannot split endpoint into host and port") return nil, errors.Wrap(err, "cannot split endpoint into host and port")
@ -81,7 +87,8 @@ func (s *server) Create(ctx context.Context, r *v1.CreateRequest) (*v1.TunnelRes
path := filepath.Join(s.dir, r.ID) path := filepath.Join(s.dir, r.ID)
if err := os.Mkdir(path, 0700); err != nil { if err := os.Mkdir(path, 0700); err != nil {
if os.IsExist(err) { if os.IsExist(err) {
return nil, errors.New("tunnel already exists") log.Error("existing tunnel")
return nil, ErrTunnelExists
} }
return nil, errors.Wrap(err, "create tunnel directory") return nil, errors.Wrap(err, "create tunnel directory")
} }
@ -101,21 +108,24 @@ func (s *server) Create(ctx context.Context, r *v1.CreateRequest) (*v1.TunnelRes
PublicKey: pub, PublicKey: pub,
Endpoint: host, Endpoint: host,
} }
if err := s.saveTunnel(&t); err != nil { if err := s.saveTunnel(&t); err != nil {
log.WithError(err).Error("save tunnel")
return nil, err return nil, err
} }
if err := s.saveConf(&t); err != nil { if err := s.saveConf(&t); err != nil {
log.WithError(err).Error("save config")
os.RemoveAll(path) os.RemoveAll(path)
return nil, err return nil, err
} }
if err := wgquick(ctx, "enable", t.ID); err != nil { if err := wgquick(ctx, "enable", t.ID); err != nil {
log.WithError(err).Error("enable tunnel")
return nil, errors.Wrap(err, "enable tunnel") return nil, errors.Wrap(err, "enable tunnel")
} }
if err := wgquick(ctx, "start", t.ID); err != nil { if err := wgquick(ctx, "start", t.ID); err != nil {
log.WithError(err).Error("start tunnel")
return nil, errors.Wrap(err, "start tunnel") return nil, errors.Wrap(err, "start tunnel")
} }
log.Info("tunnel created")
return &v1.TunnelResponse{ return &v1.TunnelResponse{
Tunnel: &t, Tunnel: &t,
}, nil }, nil
@ -125,16 +135,23 @@ func (s *server) NewPeer(ctx context.Context, r *v1.NewPeerRequest) (*v1.PeerRes
if r.ID == "" { if r.ID == "" {
return nil, errors.New("tunnel id cannot be empty") return nil, errors.New("tunnel id cannot be empty")
} }
log := logrus.WithFields(logrus.Fields{
"tunnel": r.ID,
"peer": r.PeerID,
})
t, err := s.loadTunnel(r.ID) t, err := s.loadTunnel(r.ID)
if err != nil { if err != nil {
log.WithError(err).Error("load tunnel")
return nil, err return nil, err
} }
peerKey, err := newPrivateKey(ctx) peerKey, err := newPrivateKey(ctx)
if err != nil { if err != nil {
log.WithError(err).Error("new private key")
return nil, err return nil, err
} }
publicKey, err := publicKey(ctx, peerKey) publicKey, err := publicKey(ctx, peerKey)
if err != nil { if err != nil {
log.WithError(err).Error("new public key")
return nil, err return nil, err
} }
peer := &v1.Peer{ peer := &v1.Peer{
@ -146,13 +163,17 @@ func (s *server) NewPeer(ctx context.Context, r *v1.NewPeerRequest) (*v1.PeerRes
}, },
} }
t.Peers = append(t.Peers, peer) t.Peers = append(t.Peers, peer)
// TODO: make atomic swaps
if err := s.saveTunnel(t); err != nil { if err := s.saveTunnel(t); err != nil {
log.WithError(err).Error("save tunnel")
return nil, err return nil, err
} }
if err := s.saveConf(t); err != nil { if err := s.saveConf(t); err != nil {
log.WithError(err).Error("save config")
return nil, err return nil, err
} }
if err := wgquick(ctx, "restart", t.ID); err != nil { if err := wgquick(ctx, "restart", t.ID); err != nil {
log.WithError(err).Error("restart config")
return nil, errors.Wrap(err, "restart tunnel") return nil, errors.Wrap(err, "restart tunnel")
} }
return &v1.PeerResponse{ return &v1.PeerResponse{
@ -165,16 +186,23 @@ func (s *server) AddPeer(ctx context.Context, r *v1.AddPeerRequest) (*v1.TunnelR
if r.ID == "" { if r.ID == "" {
return nil, errors.New("tunnel id cannot be empty") return nil, errors.New("tunnel id cannot be empty")
} }
log := logrus.WithFields(logrus.Fields{
"tunnel": r.ID,
"peer": r.PeerID,
})
t, err := s.loadTunnel(r.ID) t, err := s.loadTunnel(r.ID)
if err != nil { if err != nil {
log.WithError(err).Error("load tunnel")
return nil, err return nil, err
} }
t.Peers = append(t.Peers, r.Peer) t.Peers = append(t.Peers, r.Peer)
if err := s.saveTunnel(t); err != nil { if err := s.saveTunnel(t); err != nil {
log.WithError(err).Error("save tunnel")
return nil, err return nil, err
} }
if err := s.saveConf(t); err != nil { if err := s.saveConf(t); err != nil {
log.WithError(err).Error("save config")
return nil, err return nil, err
} }
if err := wgquick(ctx, "restart", t.ID); err != nil { if err := wgquick(ctx, "restart", t.ID); err != nil {
@ -192,6 +220,11 @@ func (s *server) DeletePeer(ctx context.Context, r *v1.DeletePeerRequest) (*v1.T
if r.PeerID == "" { if r.PeerID == "" {
return nil, errors.New("peer id cannot be empty") return nil, errors.New("peer id cannot be empty")
} }
log := logrus.WithFields(logrus.Fields{
"tunnel": r.ID,
"peer": r.PeerID,
})
t, err := s.loadTunnel(r.ID) t, err := s.loadTunnel(r.ID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -210,8 +243,10 @@ func (s *server) DeletePeer(ctx context.Context, r *v1.DeletePeerRequest) (*v1.T
return nil, err return nil, err
} }
if err := wgquick(ctx, "restart", t.ID); err != nil { if err := wgquick(ctx, "restart", t.ID); err != nil {
log.WithError(err).Error("restart config")
return nil, errors.Wrap(err, "restart tunnel") return nil, errors.Wrap(err, "restart tunnel")
} }
log.Info("delete peer")
return &v1.TunnelResponse{ return &v1.TunnelResponse{
Tunnel: t, Tunnel: t,
}, nil }, nil
@ -221,11 +256,14 @@ func (s *server) Delete(ctx context.Context, r *v1.DeleteRequest) (*types.Empty,
if r.ID == "" { if r.ID == "" {
return nil, errors.New("tunnel id cannot be empty") return nil, errors.New("tunnel id cannot be empty")
} }
log := logrus.WithField("tunnel", r.ID)
path := filepath.Join(s.dir, r.ID) path := filepath.Join(s.dir, r.ID)
if err := wgquick(ctx, "disable", r.ID); err != nil { if err := wgquick(ctx, "disable", r.ID); err != nil {
log.WithError(err).Error("disable tunnel")
return nil, errors.Wrap(err, "disable tunnel") return nil, errors.Wrap(err, "disable tunnel")
} }
if err := wgquick(ctx, "stop", r.ID); err != nil { if err := wgquick(ctx, "stop", r.ID); err != nil {
log.WithError(err).Error("stop tunnel")
return nil, errors.Wrap(err, "stop tunnel") return nil, errors.Wrap(err, "stop tunnel")
} }
if err := os.RemoveAll(path); err != nil { if err := os.RemoveAll(path); err != nil {
@ -234,6 +272,7 @@ func (s *server) Delete(ctx context.Context, r *v1.DeleteRequest) (*types.Empty,
if err := os.Remove(filepath.Join(s.dir, fmt.Sprintf("%s.conf", r.ID))); err != nil { if err := os.Remove(filepath.Join(s.dir, fmt.Sprintf("%s.conf", r.ID))); err != nil {
return nil, errors.Wrap(err, "remove configuration") return nil, errors.Wrap(err, "remove configuration")
} }
log.Info("delete tunnel")
return empty, nil return empty, nil
} }