diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 793b4307d..fbc44da2f 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -221,7 +221,7 @@ uploads: ids: - deb mode: archive - target: https://pkgs.wiretrustee.com/debian/pool/{{ .ArtifactName }};deb.distribution=stable;deb.component=main;deb.architecture={{ .Arch }} + target: https://pkgs.wiretrustee.com/debian/pool/{{ .ArtifactName }};deb.distribution=stable;deb.component=main username: dev@wiretrustee.com method: PUT - name: yum @@ -230,4 +230,4 @@ uploads: mode: archive target: https://pkgs.wiretrustee.com/yum/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }} username: dev@wiretrustee.com - method: PUT \ No newline at end of file + method: PUT diff --git a/README.md b/README.md index 68652b79c..21f67fdd3 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,16 @@ It requires zero configuration effort leaving behind the hassle of opening ports There is no centralized VPN server with Wiretrustee - your computers, devices, machines, and servers connect to each other directly over a fast encrypted tunnel. +**Wiretrustee automates Wireguard-based networks, offering a management layer with:** +* Centralized Peer IP management with a neat UI dashboard. +* Automatic Peer discovery and configuration. +* UDP hole punching to establish peer-to-peer connections behind NAT, firewall, and without a public static IP. +* Connection relay fallback in case a peer-to-peer connection is not possible. +* Multitenancy (coming soon). +* Client application SSO with MFA (coming soon). +* Access Controls (coming soon). +* Activity Monitoring (coming soon). + ### Secure peer-to-peer VPN in minutes
@@ -45,22 +55,6 @@ Hosted demo version:
[UI Dashboard Repo](https://github.com/wiretrustee/wiretrustee-dashboard)
-### Why using Wiretrustee?
-
-* Connect multiple devices to each other via a secure peer-to-peer Wireguard VPN tunnel. At home, the office, or anywhere else.
-* No need to open ports and expose public IPs on the device, routers etc.
-* Uses Kernel Wireguard module if available.
-* Automatic network change detection. When a new peer joins the network others are notified and keys are exchanged automatically.
-* Automatically reconnects in case of network failures or switches.
-* Automatic NAT traversal.
-* Relay server fallback in case of an unsuccessful peer-to-peer connection.
-* Private key never leaves your device.
-* Automatic IP address management.
-* Intuitive UI Dashboard.
-* Works on ARM devices (e.g. Raspberry Pi).
-* Open-source (including Management Service)
-
-
### A bit on Wiretrustee internals
* Wiretrustee features a Management Service that offers peer IP management and network updates distribution (e.g. when new peer joins the network).
* Wiretrustee uses WebRTC ICE implemented in [pion/ice library](https://github.com/pion/ice) to discover connection candidates when establishing a peer-to-peer connection between devices.
diff --git a/client/cmd/service_controller.go b/client/cmd/service_controller.go
index ca20b4842..82873fcab 100644
--- a/client/cmd/service_controller.go
+++ b/client/cmd/service_controller.go
@@ -1,7 +1,6 @@
package cmd
import (
- "github.com/cenkalti/backoff/v4"
"github.com/kardianos/service"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -11,31 +10,12 @@ import (
func (p *program) Start(s service.Service) error {
- var backOff = &backoff.ExponentialBackOff{
- InitialInterval: time.Second,
- RandomizationFactor: backoff.DefaultRandomizationFactor,
- Multiplier: backoff.DefaultMultiplier,
- MaxInterval: 30 * time.Second,
- MaxElapsedTime: 24 * 3 * time.Hour, //stop after 3 days trying
- Stop: backoff.Stop,
- Clock: backoff.SystemClock,
- }
-
// Start should not block. Do the actual work async.
log.Info("starting service") //nolint
go func() {
- operation := func() error {
- err := runClient()
- if err != nil {
- log.Warnf("retrying Wiretrustee client app due to error: %v", err)
- return err
- }
- return nil
- }
-
- err := backoff.Retry(operation, backOff)
+ err := runClient()
if err != nil {
- log.Errorf("exiting client retry loop due to unrecoverable error: %s", err)
+ log.Errorf("stopped Wiretrustee client app due to error: %v", err)
return
}
}()
diff --git a/client/cmd/up.go b/client/cmd/up.go
index 181d585e5..48141d490 100644
--- a/client/cmd/up.go
+++ b/client/cmd/up.go
@@ -2,6 +2,7 @@ package cmd
import (
"context"
+ "github.com/cenkalti/backoff/v4"
"github.com/kardianos/service"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -12,6 +13,7 @@ import (
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
+ "time"
)
var (
@@ -117,86 +119,107 @@ func connectToManagement(ctx context.Context, managementAddr string, ourPrivateK
}
func runClient() error {
- config, err := internal.ReadConfig(managementURL, configPath)
+ var backOff = &backoff.ExponentialBackOff{
+ InitialInterval: time.Second,
+ RandomizationFactor: backoff.DefaultRandomizationFactor,
+ Multiplier: backoff.DefaultMultiplier,
+ MaxInterval: time.Hour,
+ MaxElapsedTime: 24 * 3 * time.Hour,
+ Stop: backoff.Stop,
+ Clock: backoff.SystemClock,
+ }
+
+ operation := func() error {
+
+ config, err := internal.ReadConfig(managementURL, configPath)
+ if err != nil {
+ log.Errorf("failed reading config %s %v", configPath, err)
+ return err
+ }
+
+ //validate our peer's Wireguard PRIVATE key
+ myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
+ if err != nil {
+ log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
+ return err
+ }
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ mgmTlsEnabled := false
+ if config.ManagementURL.Scheme == "https" {
+ mgmTlsEnabled = true
+ }
+
+ // connect (just a connection, no stream yet) and login to Management Service to get an initial global Wiretrustee config
+ mgmClient, loginResp, err := connectToManagement(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
+ if err != nil {
+ log.Warn(err)
+ return err
+ }
+
+ // with the global Wiretrustee config in hand connect (just a connection, no stream yet) Signal
+ signalClient, err := connectToSignal(ctx, loginResp.GetWiretrusteeConfig(), myPrivateKey)
+ if err != nil {
+ log.Error(err)
+ return err
+ }
+
+ peerConfig := loginResp.GetPeerConfig()
+
+ engineConfig, err := createEngineConfig(myPrivateKey, config, peerConfig)
+ if err != nil {
+ log.Error(err)
+ return err
+ }
+
+ // create start the Wiretrustee Engine that will connect to the Signal and Management streams and manage connections to remote peers.
+ engine := internal.NewEngine(signalClient, mgmClient, engineConfig, cancel, ctx)
+ err = engine.Start()
+ if err != nil {
+ log.Errorf("error while starting Wiretrustee Connection Engine: %s", err)
+ return err
+ }
+
+ log.Print("Wiretrustee engine started, my IP is: ", peerConfig.Address)
+
+ select {
+ case <-stopCh:
+ case <-ctx.Done():
+ }
+
+ backOff.Reset()
+
+ err = mgmClient.Close()
+ if err != nil {
+ log.Errorf("failed closing Management Service client %v", err)
+ return err
+ }
+ err = signalClient.Close()
+ if err != nil {
+ log.Errorf("failed closing Signal Service client %v", err)
+ return err
+ }
+
+ err = engine.Stop()
+ if err != nil {
+ log.Errorf("failed stopping engine %v", err)
+ return err
+ }
+
+ go func() {
+ cleanupCh <- struct{}{}
+ }()
+
+ log.Info("stopped Wiretrustee client")
+
+ return ctx.Err()
+ }
+
+ err := backoff.Retry(operation, backOff)
if err != nil {
- log.Errorf("failed reading config %s %v", configPath, err)
+ log.Errorf("exiting client retry loop due to unrecoverable error: %s", err)
return err
}
-
- //validate our peer's Wireguard PRIVATE key
- myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
- if err != nil {
- log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
- return err
- }
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- mgmTlsEnabled := false
- if config.ManagementURL.Scheme == "https" {
- mgmTlsEnabled = true
- }
-
- // connect (just a connection, no stream yet) and login to Management Service to get an initial global Wiretrustee config
- mgmClient, loginResp, err := connectToManagement(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
- if err != nil {
- log.Warn(err)
- return err
- }
-
- // with the global Wiretrustee config in hand connect (just a connection, no stream yet) Signal
- signalClient, err := connectToSignal(ctx, loginResp.GetWiretrusteeConfig(), myPrivateKey)
- if err != nil {
- log.Error(err)
- return err
- }
-
- peerConfig := loginResp.GetPeerConfig()
-
- engineConfig, err := createEngineConfig(myPrivateKey, config, peerConfig)
- if err != nil {
- log.Error(err)
- return err
- }
-
- // create start the Wiretrustee Engine that will connect to the Signal and Management streams and manage connections to remote peers.
- engine := internal.NewEngine(signalClient, mgmClient, engineConfig, cancel, ctx)
- err = engine.Start()
- if err != nil {
- log.Errorf("error while starting Wiretrustee Connection Engine: %s", err)
- return err
- }
-
- log.Print("Wiretrustee engine started, my IP is: ", peerConfig.Address)
-
- select {
- case <-stopCh:
- case <-ctx.Done():
- }
-
- err = mgmClient.Close()
- if err != nil {
- log.Errorf("failed closing Management Service client %v", err)
- return err
- }
- err = signalClient.Close()
- if err != nil {
- log.Errorf("failed closing Signal Service client %v", err)
- return err
- }
-
- err = engine.Stop()
- if err != nil {
- log.Errorf("failed stopping engine %v", err)
- return err
- }
-
- go func() {
- cleanupCh <- struct{}{}
- }()
-
- log.Info("stopped Wiretrustee client")
-
- return ctx.Err()
-
+ return nil
}
diff --git a/management/client/client.go b/management/client/client.go
index 902278bb4..51a6b7b87 100644
--- a/management/client/client.go
+++ b/management/client/client.go
@@ -32,7 +32,7 @@ func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsE
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
}
- mgmCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
+ mgmCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
conn, err := grpc.DialContext(
mgmCtx,
@@ -40,8 +40,8 @@ func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsE
transportOption,
grpc.WithBlock(),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
- Time: 3 * time.Second,
- Timeout: 2 * time.Second,
+ Time: 15 * time.Second,
+ Timeout: 10 * time.Second,
}))
if err != nil {
@@ -70,8 +70,8 @@ func defaultBackoff(ctx context.Context) backoff.BackOff {
InitialInterval: 800 * time.Millisecond,
RandomizationFactor: backoff.DefaultRandomizationFactor,
Multiplier: backoff.DefaultMultiplier,
- MaxInterval: 30 * time.Second,
- MaxElapsedTime: 24 * 3 * time.Hour, //stop after 3 days trying
+ MaxInterval: 15 * time.Minute,
+ MaxElapsedTime: time.Hour, //stop after an hour of trying, the error will be propagated to the general retry of the client
Stop: backoff.Stop,
Clock: backoff.SystemClock,
}, ctx)
@@ -103,12 +103,10 @@ func (c *Client) Sync(msgHandler func(msg *proto.SyncResponse) error) error {
// blocking until error
err = c.receiveEvents(stream, *serverPubKey, msgHandler)
if err != nil {
- /*if errStatus, ok := status.FromError(err); ok && errStatus.Code() == codes.PermissionDenied {
- //todo handle differently??
- }*/
+ backOff.Reset()
return err
}
- backOff.Reset()
+
return nil
}
diff --git a/management/server/account.go b/management/server/account.go
index 6714e6e1a..9f8e10e6c 100644
--- a/management/server/account.go
+++ b/management/server/account.go
@@ -3,11 +3,11 @@ package server
import (
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
+ "github.com/wiretrustee/wiretrustee/util"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"net"
"sync"
- "time"
)
type AccountManager struct {
@@ -35,16 +35,21 @@ func NewManager(store Store, peersUpdateManager *PeersUpdateManager) *AccountMan
}
//AddSetupKey generates a new setup key with a given name and type, and adds it to the specified account
-func (am *AccountManager) AddSetupKey(accountId string, keyName string, keyType SetupKeyType, expiresIn time.Duration) (*SetupKey, error) {
+func (am *AccountManager) AddSetupKey(accountId string, keyName string, keyType SetupKeyType, expiresIn *util.Duration) (*SetupKey, error) {
am.mux.Lock()
defer am.mux.Unlock()
+ keyDuration := DefaultSetupKeyDuration
+ if expiresIn != nil {
+ keyDuration = expiresIn.Duration
+ }
+
account, err := am.Store.GetAccount(accountId)
if err != nil {
return nil, status.Errorf(codes.NotFound, "account not found")
}
- setupKey := GenerateSetupKey(keyName, keyType, expiresIn)
+ setupKey := GenerateSetupKey(keyName, keyType, keyDuration)
account.SetupKeys[setupKey.Key] = setupKey
err = am.Store.SaveAccount(account)
diff --git a/management/server/http/handler/setupkeys.go b/management/server/http/handler/setupkeys.go
index ba1a44c9f..bbd8bbe9a 100644
--- a/management/server/http/handler/setupkeys.go
+++ b/management/server/http/handler/setupkeys.go
@@ -5,6 +5,7 @@ import (
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"github.com/wiretrustee/wiretrustee/management/server"
+ "github.com/wiretrustee/wiretrustee/util"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"net/http"
@@ -34,7 +35,7 @@ type SetupKeyResponse struct {
type SetupKeyRequest struct {
Name string
Type server.SetupKeyType
- ExpiresIn Duration
+ ExpiresIn *util.Duration
Revoked bool
}
@@ -102,7 +103,7 @@ func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.R
return
}
- setupKey, err := h.accountManager.AddSetupKey(accountId, req.Name, req.Type, req.ExpiresIn.Duration)
+ setupKey, err := h.accountManager.AddSetupKey(accountId, req.Name, req.Type, req.ExpiresIn)
if err != nil {
errStatus, ok := status.FromError(err)
if ok && errStatus.Code() == codes.NotFound {
diff --git a/signal/client/client.go b/signal/client/client.go
index c8b284c2f..5702a8c1c 100644
--- a/signal/client/client.go
+++ b/signal/client/client.go
@@ -48,7 +48,7 @@ func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled boo
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
}
- sigCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
+ sigCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
conn, err := grpc.DialContext(
sigCtx,
@@ -56,8 +56,8 @@ func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled boo
transportOption,
grpc.WithBlock(),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
- Time: 3 * time.Second,
- Timeout: 2 * time.Second,
+ Time: 15 * time.Second,
+ Timeout: 10 * time.Second,
}))
if err != nil {
@@ -81,8 +81,8 @@ func defaultBackoff(ctx context.Context) backoff.BackOff {
InitialInterval: 800 * time.Millisecond,
RandomizationFactor: backoff.DefaultRandomizationFactor,
Multiplier: backoff.DefaultMultiplier,
- MaxInterval: 30 * time.Second,
- MaxElapsedTime: 24 * 3 * time.Hour, //stop after 3 days trying
+ MaxInterval: 15 * time.Minute,
+ MaxElapsedTime: time.Hour, //stop after an hour of trying, the error will be propagated to the general retry of the client
Stop: backoff.Stop,
Clock: backoff.SystemClock,
}, ctx)
@@ -101,14 +101,19 @@ func (c *Client) Receive(msgHandler func(msg *proto.Message) error) {
operation := func() error {
- err := c.connect(c.key.PublicKey().String(), msgHandler)
+ stream, err := c.connect(c.key.PublicKey().String())
if err != nil {
log.Warnf("disconnected from the Signal Exchange due to an error: %v", err)
c.connWg.Add(1)
return err
}
- backOff.Reset()
+ err = c.receive(stream, msgHandler)
+ if err != nil {
+ backOff.Reset()
+ return err
+ }
+
return nil
}
@@ -120,7 +125,7 @@ func (c *Client) Receive(msgHandler func(msg *proto.Message) error) {
}()
}
-func (c *Client) connect(key string, msgHandler func(msg *proto.Message) error) error {
+func (c *Client) connect(key string) (proto.SignalExchange_ConnectStreamClient, error) {
c.stream = nil
// add key fingerprint to the request header to be identified on the server side
@@ -131,23 +136,23 @@ func (c *Client) connect(key string, msgHandler func(msg *proto.Message) error)
c.stream = stream
if err != nil {
- return err
+ return nil, err
}
// blocks
header, err := c.stream.Header()
if err != nil {
- return err
+ return nil, err
}
registered := header.Get(proto.HeaderRegistered)
if len(registered) == 0 {
- return fmt.Errorf("didn't receive a registration header from the Signal server whille connecting to the streams")
+ return nil, fmt.Errorf("didn't receive a registration header from the Signal server whille connecting to the streams")
}
//connection established we are good to use the stream
c.connWg.Done()
log.Infof("connected to the Signal Exchange Stream")
- return c.receive(stream, msgHandler)
+ return stream, nil
}
// WaitConnected waits until the client is connected to the message stream