Create guard tunnel
This commit is contained in:
parent
1bd241e745
commit
717e860f65
3 changed files with 85 additions and 15 deletions
|
@ -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
44
main.go
|
@ -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)
|
||||||
|
}
|
||||||
|
|
47
server.go
47
server.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue