From 892080bc38175e55e8f1b9291d75b896580cc28f Mon Sep 17 00:00:00 2001 From: Mikhail Bragin Date: Wed, 27 Oct 2021 13:56:55 +0200 Subject: [PATCH 1/5] docs: update key features --- README.md | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) 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. From 673e807528df4411556bc450c5fb54fb35aaeb00 Mon Sep 17 00:00:00 2001 From: Mikhail Bragin Date: Sun, 31 Oct 2021 12:06:44 +0100 Subject: [PATCH 2/5] chore: set default key expiration if not provided by frontednd (#142) --- management/server/account.go | 11 ++++++++--- management/server/http/handler/setupkeys.go | 5 +++-- 2 files changed, 11 insertions(+), 5 deletions(-) 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 { From e9066b4651d9280619a95a12fdc2c3c41afa24ab Mon Sep 17 00:00:00 2001 From: braginini Date: Sun, 31 Oct 2021 12:14:00 +0100 Subject: [PATCH 3/5] chore: increase signal and management gRPC clients timeouts --- management/client/client.go | 6 +++--- signal/client/client.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/management/client/client.go b/management/client/client.go index 902278bb4..019c8668b 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 { diff --git a/signal/client/client.go b/signal/client/client.go index c8b284c2f..5b19c3c1f 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 { From 2c729fe5cc86f3c0f2d2cb45f3bf3d1dd7364676 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Mon, 1 Nov 2021 09:33:22 +0100 Subject: [PATCH 4/5] remove architecture info from deb (#145) --- .goreleaser.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From d040cfed7eab1201f597b859bae620585e76ab1d Mon Sep 17 00:00:00 2001 From: Mikhail Bragin Date: Mon, 1 Nov 2021 09:34:06 +0100 Subject: [PATCH 5/5] fix: client app retry logic (#144) * fix: retry logic --- client/cmd/service_controller.go | 24 +--- client/cmd/up.go | 181 +++++++++++++++++-------------- management/client/client.go | 10 +- signal/client/client.go | 23 ++-- 4 files changed, 122 insertions(+), 116 deletions(-) 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 019c8668b..51a6b7b87 100644 --- a/management/client/client.go +++ b/management/client/client.go @@ -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/signal/client/client.go b/signal/client/client.go index 5b19c3c1f..5702a8c1c 100644 --- a/signal/client/client.go +++ b/signal/client/client.go @@ -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