Compare commits

...

34 Commits

Author SHA1 Message Date
braginini
528a26ea3e chore: simplify direct connection logic 2021-09-09 16:43:52 +02:00
Mikhail Bragin
13288374f1 test: fix peer reflexive connectivity (#116) 2021-09-09 10:45:04 +02:00
Mikhail Bragin
ec759bc461 Delete peer (#114)
* feature: add peer deletion

* feature: add peer deletion [CLIENT]

* fix: lint error

* test: fix sync block

* test: fix management test

* feature: add client stop after was deleted

* chore: remove permission denied cancellation

* chore: add larger signal backoff

* feature: notify deleted peer of removal

* fix: lint issue

* chore: add 2nd default key - one off

* test: fix account key check
2021-09-07 18:36:46 +02:00
braginini
a859f6c511 docs: add info about Auth0 2021-09-07 10:38:07 +02:00
braginini
081162864d fix: management config 2021-09-07 10:35:33 +02:00
braginini
090f3ae5d0 chore: rename config to management json 2021-09-07 10:30:56 +02:00
braginini
fb1116e77b chore: release archive back to tar.gz 2021-09-07 10:28:43 +02:00
braginini
879750af7c fix: docker compose 2021-09-07 10:25:55 +02:00
Mikhail Bragin
13b4be31df feature: add logging to a file (#112)
* feature: add logging to a file

* refactor: move InitLog to util lib

* docs: update signal and management docs

* chore: update docker compose

* set --log-file to console

* chore: comment out log volume in docker compose

Co-authored-by: mlsmaycon <mlsmaycon@gmail.com>
2021-09-07 09:53:18 +02:00
Mikhail Bragin
15f7d856db Update README.md 2021-09-06 16:12:57 +02:00
Mikhail Bragin
72197d1970 chore: update docker compose 2021-09-06 16:11:48 +02:00
braginini
a56aba8b06 chore: init STUNs and TURNs as empty arrays 2021-09-06 14:23:03 +02:00
Maycon Santos
4acbdc47e5 Add windows installer documentation (#111) 2021-09-06 14:15:47 +02:00
Maycon Santos
ee3c292699 Add homebrew tap (#110)
* test homebrew task

* secret HOMEBREW_TAP_GITHUB_TOKEN

* prepare for pr

* use homebrew-client

* add brew install
2021-09-06 14:15:08 +02:00
Maycon Santos
6c233fcc3f Add windows installer (#109)
* windows installer

* unpack function in local dir

* working-directory client

* using env var plugin

* test tag and publishing

* getting version from tag

* using version number

* remove unnecessary commands and add description

* using long version outputs

* uncomment docker steps
2021-09-06 10:20:26 +02:00
Mikhail Bragin
a4db0b4e94 client update of TURNs and STUNs (#106)
* feature: update STUNs and TURNs in engine

* fix: setup TURN credentials request only when refresh enabled

* feature: update TURNs and STUNs in teh client app on Management update

* chore: disable peer reflexive candidates in ICE

* chore: relocate management.json

* chore: make TURN secret and pwd plain text in config
2021-09-03 17:47:40 +02:00
Mikhail Bragin
81c5aa1341 docs: add dashboard repo link 2021-09-03 15:33:49 +02:00
Mikhail Bragin
8c5f6186f1 Update README.md 2021-09-03 11:19:53 +02:00
Mikhail Bragin
88e9d2c20d Update README.md 2021-09-03 11:18:45 +02:00
Mikhail Bragin
9d76cf1ea7 docs: add demo location 2021-09-03 11:18:17 +02:00
Mikhail Bragin
737d4b5f2c Update config.json 2021-09-03 11:07:13 +02:00
Mikhail Bragin
4485124b67 fix: docker-compose management config 2021-09-03 11:03:30 +02:00
Mikhail Bragin
b17424d630 Turn credentials generation (#102)
* abstract peer channel

* remove wip code

* refactor NewServer with Peer updates channel

* feature: add TURN credentials manager

* hmac logic

* example test function

* test: add TimeBasedAuthSecretsManager_GenerateCredentials  test

* test: make tests for now with hardcoded secret

* test: add TimeBasedAuthSecretsManager_SetupRefresh test

* test: add TimeBasedAuthSecretsManager_SetupRefresh test

* test: add TimeBasedAuthSecretsManager_CancelRefresh test

* feature: extract TURNConfig to the management config

* feature: return hash based TURN credentials only on initial sync

* feature: make TURN time based secret credentials optional

Co-authored-by: mlsmaycon <mlsmaycon@gmail.com>
2021-09-02 14:41:54 +02:00
braginini
86f3b1e5c8 chore: goreleaser zip format instead of tar.gz 2021-08-30 13:02:39 +02:00
Maycon Santos
a31cbb1f5b abstract peer channel (#101)
* abstract peer channel

* remove wip code

* refactor NewServer with Peer updates channel

* add PeersUpdateManager tests

* adding documentation

* using older version of linter

* verbose lint

* skip cache

* setup go version

* extra output

* configure fetch-depth

* exit 0

* skip-build-cache: true

* disabling failure for lint for now

* fix: darwin issue

* enable lint failure

* remove sock file for macOS

* refactor: remove tests interdependence

* fixed linux native iface

Co-authored-by: braginini <bangvalo@gmail.com>
2021-08-29 17:48:31 +02:00
Maycon Santos
4f4edf8442 fix: management amd64 docker image generation (#104) 2021-08-29 17:43:21 +02:00
braginini
a78e518327 feature: detect peers in local network 2021-08-29 16:12:39 +02:00
braginini
2c1d7c0fd4 fix: rename info package 2021-08-29 13:34:17 +02:00
braginini
593c66fea6 fix: darwin issue 2021-08-29 13:30:34 +02:00
braginini
5f8211773d fix: lint error 2021-08-29 13:23:58 +02:00
braginini
64ca05c8e7 fix: lint error 2021-08-29 13:22:52 +02:00
braginini
307d41c08a Merge remote-tracking branch 'origin/main' 2021-08-27 11:45:55 +02:00
braginini
5c7260298f chore: adjust system info discovery methods 2021-08-27 11:45:46 +02:00
braginini
7f7858b0a6 chore: adjust system info discovery methods 2021-08-27 11:34:38 +02:00
59 changed files with 1696 additions and 502 deletions

View File

@@ -11,4 +11,6 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
uses: golangci/golangci-lint-action@v2

View File

@@ -49,4 +49,30 @@ jobs:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
-
id: get_version
uses: battila7/get-version-action@v2
-
name: Install makensis
run: sudo apt update && sudo apt install -y nsis nsis-pluginapi
-
name: Download EnvVar Plugin
run: curl -L -o EnVar_plugin.zip https://nsis.sourceforge.io/mediawiki/images/7/7f/EnVar_plugin.zip
-
name: Extract EnVar plugin
run: sudo 7z x -o"/usr/share/nsis/" EnVar_plugin.zip
-
name: Generate Windows installer
run: makensis -V4 client/installer.nsis
env:
APPVER: ${{ steps.get_version.outputs.major }}.${{ steps.get_version.outputs.minor }}.${{ steps.get_version.outputs.patch }}.${{ github.run_id }}
-
name: Upload windows installer to release page
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: wiretrustee-installer.exe
asset_name: wiretrustee_installer_${{ steps.get_version.outputs.version-without-v }}_windows_amd64.exe
tag: ${{ github.ref }}

View File

@@ -103,7 +103,7 @@ dockers:
use: buildx
dockerfile: management/Dockerfile
build_flag_templates:
- "--platform=linux/arm64"
- "--platform=linux/amd64"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.version={{.Version}}"
@@ -133,7 +133,7 @@ dockers:
use: buildx
dockerfile: management/Dockerfile.debug
build_flag_templates:
- "--platform=linux/arm64"
- "--platform=linux/amd64"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.version={{.Version}}"
@@ -180,4 +180,20 @@ docker_manifests:
- name_template: wiretrustee/management:debug-latest
image_templates:
- wiretrustee/management:{{ .Version }}-debug-arm64v8
- wiretrustee/management:{{ .Version }}-debug-amd64
- wiretrustee/management:{{ .Version }}-debug-amd64
brews:
-
tap:
owner: wiretrustee
name: homebrew-client
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
commit_author:
name: Wiretrustee
email: wiretrustee@wiretrustee.com
description: Wiretrustee project.
download_strategy: CurlDownloadStrategy
homepage: https://wiretrustee.com/
license: "BSD3"
test: |
system "#{bin}/{{ .ProjectName }} -h"

View File

@@ -4,6 +4,12 @@ A WireGuard®-based mesh network that connects your devices into a single privat
**Note**: The `main` branch may be in an *unstable or even broken state* during development. For stable versions, see [releases](https://github.com/wiretrustee/wiretrustee/releases).
**Hosted demo version:** [https://beta.wiretrustee.com/](https://beta.wiretrustee.com/peers)
Please don't use the hosted demonstration version for production purposes.
[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.
@@ -58,6 +64,13 @@ wget https://github.com/wiretrustee/wiretrustee/releases/download/v<VERSION>/wir
sudo rpm -i wiretrustee_<VERSION>_linux_amd64.rpm
```
#### MACOS
**Brew install**
1. Download and install Brew at https://brew.sh/
2. Install the client
```shell
brew install wiretrustee/client/wiretrustee
```
**Installation from binary**
1. Checkout Wiretrustee [releases](https://github.com/wiretrustee/wiretrustee/releases/latest)
2. Download the latest release (**Switch VERSION to the latest**):
```shell
@@ -76,21 +89,11 @@ export PATH=$PATH:/usr/local/bin
#### Windows
1. Checkout Wiretrustee [releases](https://github.com/wiretrustee/wiretrustee/releases/latest)
2. Download the latest Windows release ```wiretrustee_<VERSION>_windows_amd64.tar.gz``` (**Switch VERSION to the latest**):
3. Decompress and move to a more fixed path in your system
4. Open Powershell
5. For Windows systems, we can use the service command to configure Wiretrustee as a service by running the following commands in Powershell:
````shell
cd C:\path\to\wiretrustee\bin
.\wiretrustee.exe service --help
.\wiretrustee.exe service install # This will prompt for administrator permissions in order to install a new service
````
> You may need to run Powershell as Administrator
6. After installing you can follow the [Client Configuration](#Client-Configuration) steps.
7. To uninstall the service simple run the command above with the uninstall flag:
````shell
.\wiretrustee.exe service uninstall
````
2. Download the latest Windows release installer ```wiretrustee_installer_<VERSION>_windows_amd64.exe``` (**Switch VERSION to the latest**):
3. Proceed with installation steps
4. This will install the client in the C:\\Program Files\\Wiretrustee and add the client service
5. After installing you can follow the [Client Configuration](#Client-Configuration) steps.
> To uninstall the client and service, you can use Add/Remove programs
### Client Configuration
1. Login to the Management Service. You need to have a `setup key` in hand (see ).
@@ -147,6 +150,9 @@ netsh interface ip show config name="wt0"
4. Repeat on other machines.
### Running Management, Signal and Coturn
Wiretrustee uses [Auth0](https://auth0.com) for user authentication and authorization, therefore you will need to create a free account
and configure AUTH0 variables in the compose file (dashboard and management).
Under infrastructure_files we have a docker-compose example to run both, Wiretrustee Management and Signal services, plus an instance of [Coturn](https://github.com/coturn/coturn), it also provides a turnserver.conf file as a simple example of Coturn configuration.
You can edit the turnserver.conf file and change its Realm setting (defaults to wiretrustee.com) to your own domain and user setting (defaults to username1:password1) to **proper credentials**.

View File

@@ -10,6 +10,7 @@ import (
"github.com/wiretrustee/wiretrustee/client/internal"
mgm "github.com/wiretrustee/wiretrustee/management/client"
mgmProto "github.com/wiretrustee/wiretrustee/management/proto"
"github.com/wiretrustee/wiretrustee/util"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@@ -23,7 +24,11 @@ var (
Use: "login",
Short: "login to the Wiretrustee Management Service (first run)",
RunE: func(cmd *cobra.Command, args []string) error {
InitLog(logLevel)
err := util.InitLog(logLevel, logFile)
if err != nil {
log.Errorf("failed initializing log %v", err)
return err
}
config, err := internal.GetConfig(managementURL, configPath)
if err != nil {

View File

@@ -2,13 +2,11 @@ package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/wiretrustee/wiretrustee/client/internal"
"os"
"os/signal"
"runtime"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
const (
@@ -21,6 +19,8 @@ var (
configPath string
defaultConfigPath string
logLevel string
defaultLogFile string
logFile string
managementURL string
rootCmd = &cobra.Command{
@@ -42,13 +42,16 @@ func init() {
stopCh = make(chan int)
defaultConfigPath = "/etc/wiretrustee/config.json"
defaultLogFile = "/var/log/wiretrustee/client.log"
if runtime.GOOS == "windows" {
defaultConfigPath = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "config.json"
defaultLogFile = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "client.log"
}
rootCmd.PersistentFlags().StringVar(&managementURL, "management-url", "", fmt.Sprintf("Management Service URL [http|https]://[host]:[port] (default \"%s\")", internal.ManagementURLDefault().String()))
rootCmd.PersistentFlags().StringVar(&configPath, "config", defaultConfigPath, "Wiretrustee config file location")
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "sets Wiretrustee log level")
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Wiretrustee log path. If console is specified the the log will be output to stdout")
rootCmd.AddCommand(serviceCmd)
rootCmd.AddCommand(upCmd)
rootCmd.AddCommand(loginCmd)
@@ -67,13 +70,3 @@ func SetupCloseHandler() {
}
}()
}
// InitLog parses and sets log-level input
func InitLog(logLevel string) {
level, err := log.ParseLevel(logLevel)
if err != nil {
log.Errorf("Failed parsing log-level %s: %s", logLevel, err)
os.Exit(ExitSetupFailed)
}
log.SetLevel(level)
}

View File

@@ -37,8 +37,10 @@ func startManagement(config *mgmt.Config, t *testing.T) (*grpc.Server, net.Liste
t.Fatal(err)
}
accountManager := mgmt.NewManager(store)
mgmtServer, err := mgmt.NewServer(config, accountManager)
peersUpdateManager := mgmt.NewPeersUpdateManager()
accountManager := mgmt.NewManager(store, peersUpdateManager)
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager)
if err != nil {
t.Fatal(err)
}

View File

@@ -2,14 +2,13 @@ package cmd
import (
"context"
"github.com/pion/ice/v2"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/wiretrustee/wiretrustee/client/internal"
"github.com/wiretrustee/wiretrustee/iface"
mgm "github.com/wiretrustee/wiretrustee/management/client"
mgmProto "github.com/wiretrustee/wiretrustee/management/proto"
signal "github.com/wiretrustee/wiretrustee/signal/client"
"github.com/wiretrustee/wiretrustee/util"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@@ -20,12 +19,15 @@ var (
Use: "up",
Short: "start wiretrustee",
RunE: func(cmd *cobra.Command, args []string) error {
InitLog(logLevel)
err := util.InitLog(logLevel, logFile)
if err != nil {
log.Errorf("failed initializing log %v", err)
return err
}
config, err := internal.ReadConfig(managementURL, configPath)
if err != nil {
log.Errorf("failed reading config %s %v", configPath, err)
//os.Exit(ExitSetupFailed)
return err
}
@@ -33,11 +35,10 @@ var (
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
if err != nil {
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
//os.Exit(ExitSetupFailed)
return err
}
ctx := context.Background()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mgmTlsEnabled := false
if config.ManagementURL.Scheme == "https" {
@@ -48,7 +49,6 @@ var (
mgmClient, loginResp, err := connectToManagement(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
if err != nil {
log.Warn(err)
//os.Exit(ExitSetupFailed)
return err
}
@@ -56,47 +56,45 @@ var (
signalClient, err := connectToSignal(ctx, loginResp.GetWiretrusteeConfig(), myPrivateKey)
if err != nil {
log.Error(err)
//os.Exit(ExitSetupFailed)
return err
}
engineConfig, err := createEngineConfig(myPrivateKey, config, loginResp.GetWiretrusteeConfig(), loginResp.GetPeerConfig())
if err != nil {
log.Error(err)
//os.Exit(ExitSetupFailed)
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)
engine := internal.NewEngine(signalClient, mgmClient, engineConfig, cancel)
err = engine.Start()
if err != nil {
log.Errorf("error while starting Wiretrustee Connection Engine: %s", err)
//os.Exit(ExitSetupFailed)
return err
}
SetupCloseHandler()
<-stopCh
select {
case <-stopCh:
case <-ctx.Done():
}
log.Infof("receive signal to stop running")
err = mgmClient.Close()
if err != nil {
log.Errorf("failed closing Management Service client %v", err)
//os.Exit(ExitSetupFailed)
return err
}
err = signalClient.Close()
if err != nil {
log.Errorf("failed closing Signal Service client %v", err)
//os.Exit(ExitSetupFailed)
return err
}
log.Debugf("removing Wiretrustee interface %s", config.WgIface)
err = iface.Close()
err = engine.Stop()
if err != nil {
log.Errorf("failed closing Wiretrustee interface %s %v", config.WgIface, err)
//os.Exit(ExitSetupFailed)
log.Errorf("failed stopping engine %v", err)
return err
}
@@ -115,13 +113,7 @@ func createEngineConfig(key wgtypes.Key, config *internal.Config, wtConfig *mgmP
iFaceBlackList[config.IFaceBlackList[i]] = struct{}{}
}
stunTurns, err := toStunTurnURLs(wtConfig)
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "failed parsing STUN and TURN URLs received from Management Service : %s", err)
}
return &internal.EngineConfig{
StunsTurns: stunTurns,
WgIface: config.WgIface,
WgAddr: peerConfig.Address,
IFaceBlackList: iFaceBlackList,
@@ -129,30 +121,6 @@ func createEngineConfig(key wgtypes.Key, config *internal.Config, wtConfig *mgmP
}, nil
}
// toStunTurnURLs converts Wiretrustee STUN and TURN configs to ice.URL array
func toStunTurnURLs(wtConfig *mgmProto.WiretrusteeConfig) ([]*ice.URL, error) {
var stunsTurns []*ice.URL
for _, stun := range wtConfig.Stuns {
url, err := ice.ParseURL(stun.Uri)
if err != nil {
return nil, err
}
stunsTurns = append(stunsTurns, url)
}
for _, turn := range wtConfig.Turns {
url, err := ice.ParseURL(turn.HostConfig.Uri)
if err != nil {
return nil, err
}
url.Username = turn.User
url.Password = turn.Password
stunsTurns = append(stunsTurns, url)
}
return stunsTurns, nil
}
// connectToSignal creates Signal Service client and established a connection
func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig, ourPrivateKey wgtypes.Key) (*signal.Client, error) {
var sigTLSEnabled bool

118
client/installer.nsis Normal file
View File

@@ -0,0 +1,118 @@
!define APP_NAME "Wiretrustee"
!define COMP_NAME "Wiretrustee"
!define WEB_SITE "wiretrustee.com"
!define VERSION $%APPVER%
!define COPYRIGHT "Wiretrustee Authors, 2021"
!define DESCRIPTION "A WireGuard®-based mesh network that connects your devices into a single private network"
!define INSTALLER_NAME "wiretrustee-installer.exe"
!define MAIN_APP_EXE "Wiretrustee"
!define ICON "ui\\wiretrustee.ico"
!define BANNER "ui\\banner.bmp"
!define LICENSE_DATA "..\\LICENSE"
!define INSTALL_DIR "$PROGRAMFILES64\${APP_NAME}"
!define INSTALL_TYPE "SetShellVarContext all"
!define REG_ROOT "HKLM"
!define REG_APP_PATH "Software\Microsoft\Windows\CurrentVersion\App Paths\${MAIN_APP_EXE}"
!define UNINSTALL_PATH "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}"
Unicode True
######################################################################
VIProductVersion "${VERSION}"
VIAddVersionKey "ProductName" "${APP_NAME}"
VIAddVersionKey "CompanyName" "${COMP_NAME}"
VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
VIAddVersionKey "FileDescription" "${DESCRIPTION}"
VIAddVersionKey "FileVersion" "${VERSION}"
######################################################################
SetCompressor /SOLID Lzma
Name "${APP_NAME}"
Caption "${APP_NAME}"
OutFile "..\\${INSTALLER_NAME}"
BrandingText "${APP_NAME}"
InstallDirRegKey "${REG_ROOT}" "${REG_APP_PATH}" ""
InstallDir "${INSTALL_DIR}"
LicenseData "${LICENSE_DATA}"
ShowInstDetails Show
######################################################################
!define MUI_ICON "${ICON}"
!define MUI_UNICON "${ICON}"
!define MUI_WELCOMEFINISHPAGE_BITMAP "${BANNER}"
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "${BANNER}"
######################################################################
!include "MUI2.nsh"
!define MUI_ABORTWARNING
!define MUI_UNABORTWARNING
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "${LICENSE_DATA}"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
!insertmacro MUI_LANGUAGE "English"
######################################################################
Section -MainProgram
${INSTALL_TYPE}
SetOverwrite ifnewer
SetOutPath "$INSTDIR"
File /r "..\\dist\\wiretrustee_windows_amd64\\"
SectionEnd
######################################################################
Section -Icons_Reg
SetOutPath "$INSTDIR"
WriteUninstaller "$INSTDIR\wiretrustee_uninstall.exe"
WriteRegStr ${REG_ROOT} "${REG_APP_PATH}" "" "$INSTDIR\${MAIN_APP_EXE}"
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayName" "${APP_NAME}"
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "UninstallString" "$INSTDIR\wiretrustee_uninstall.exe"
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayIcon" "$INSTDIR\${MAIN_APP_EXE}"
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayVersion" "${VERSION}"
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "Publisher" "${COMP_NAME}"
EnVar::SetHKLM
EnVar::AddValueEx "path" "$INSTDIR"
Exec '"$INSTDIR\${MAIN_APP_EXE}" service install'
# sleep a bit for visibility
Sleep 1000
SectionEnd
######################################################################
Section Uninstall
${INSTALL_TYPE}
Exec '"$INSTDIR\${MAIN_APP_EXE}" service uninstall'
# wait the service uninstall take unblock the executable
Sleep 3000
RmDir /r "$INSTDIR"
DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}"
DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}"
EnVar::SetHKLM
EnVar::DeleteValue "path" "$INSTDIR"
SectionEnd

View File

@@ -127,8 +127,9 @@ func (conn *Connection) Open(timeout time.Duration) error {
// create an ice.Agent that will be responsible for negotiating and establishing actual peer-to-peer connection
a, err := ice.NewAgent(&ice.AgentConfig{
// MulticastDNSMode: ice.MulticastDNSModeQueryAndGather,
NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4},
Urls: conn.Config.StunTurnURLS,
NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4},
Urls: conn.Config.StunTurnURLS,
CandidateTypes: []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive, ice.CandidateTypeRelay},
InterfaceFilter: func(s string) bool {
if conn.Config.iFaceBlackList == nil {
return true
@@ -173,25 +174,26 @@ func (conn *Connection) Open(timeout time.Duration) error {
}
isControlling := conn.Config.WgKey.PublicKey().String() > conn.Config.RemoteWgKey.String()
remoteConn, err := conn.openConnectionToRemote(isControlling, remoteAuth)
var remoteConn *ice.Conn
remoteConn, err = conn.openConnectionToRemote(isControlling, remoteAuth)
if err != nil {
log.Errorf("failed establishing connection with the remote peer %s %s", conn.Config.RemoteWgKey.String(), err)
return err
}
pair, err := conn.agent.GetSelectedCandidatePair()
var pair *ice.CandidatePair
pair, err = conn.agent.GetSelectedCandidatePair()
if err != nil {
return err
}
remoteIP := net.ParseIP(pair.Remote.Address())
myIp := net.ParseIP(pair.Remote.Address())
// in case the remote peer is in the local network or one of the peers has public static IP -> no need for a Wireguard proxy, direct communication is possible.
if (pair.Local.Type() == ice.CandidateTypeHost && pair.Remote.Type() == ice.CandidateTypeHost) && (isPublicIP(remoteIP) || isPublicIP(myIp)) {
log.Debugf("it is possible to establish a direct connection (without proxy) to peer %s - my addr: %s, remote addr: %s", conn.Config.RemoteWgKey.String(), pair.Local.Address(), pair.Remote.Address())
if !useProxy(pair) {
log.Debugf("it is possible to establish a direct connection (without proxy) to peer %s - my addr: %s, remote addr: %s", conn.Config.RemoteWgKey.String(), pair.Local, pair.Remote)
err = conn.wgProxy.StartLocal(fmt.Sprintf("%s:%d", pair.Remote.Address(), iface.WgPort))
if err != nil {
return err
}
} else {
log.Infof("establishing secure tunnel to peer %s via selected candidate pair %s", conn.Config.RemoteWgKey.String(), pair)
err = conn.wgProxy.Start(remoteConn)
@@ -200,13 +202,17 @@ func (conn *Connection) Open(timeout time.Duration) error {
}
}
if pair.Remote.Type() == ice.CandidateTypeRelay || pair.Local.Type() == ice.CandidateTypeRelay {
log.Infof("using relay with peer %s", conn.Config.RemoteWgKey)
}
conn.Status = StatusConnected
log.Infof("opened connection to peer %s", conn.Config.RemoteWgKey.String())
case <-conn.closeCond.C:
conn.Status = StatusDisconnected
return fmt.Errorf("connection to peer %s has been closed", conn.Config.RemoteWgKey.String())
case <-time.After(timeout):
err := conn.Close()
err = conn.Close()
if err != nil {
log.Warnf("error while closing connection to peer %s -> %s", conn.Config.RemoteWgKey.String(), err.Error())
}
@@ -233,6 +239,33 @@ func isPublicIP(ip net.IP) bool {
return true
}
//useProxy determines whether a direct connection (without a go proxy) is possible
//There are 3 cases: one of the peers has a public IP or both peers are in the same private network
//Please note, that this check happens when peers were already able to ping each other with ICE layer.
func useProxy(pair *ice.CandidatePair) bool {
remoteIP := net.ParseIP(pair.Remote.Address())
myIp := net.ParseIP(pair.Local.Address())
remoteIsPublic := isPublicIP(remoteIP)
myIsPublic := isPublicIP(myIp)
//one of the hosts has a public IP
if remoteIsPublic && pair.Remote.Type() == ice.CandidateTypeHost {
return false
}
if myIsPublic && pair.Local.Type() == ice.CandidateTypeHost {
return false
}
if pair.Local.Type() == ice.CandidateTypeHost && pair.Remote.Type() == ice.CandidateTypeHost {
if !remoteIsPublic && !myIsPublic {
//both hosts are in the same private network
return false
}
}
return true
}
// Close Closes a peer connection
func (conn *Connection) Close() error {
var err error

View File

@@ -1,6 +1,7 @@
package internal
import (
"context"
"fmt"
"github.com/cenkalti/backoff/v4"
ice "github.com/pion/ice/v2"
@@ -18,13 +19,11 @@ import (
// PeerConnectionTimeout is a timeout of an initial connection attempt to a remote peer.
// E.g. this peer will wait PeerConnectionTimeout for the remote peer to respond, if not successful then it will retry the connection attempt.
const PeerConnectionTimeout = 60 * time.Second
const PeerConnectionTimeout = 40 * time.Second
// EngineConfig is a config for the Engine
type EngineConfig struct {
// StunsTurns is a list of STUN and TURN servers used by ICE
StunsTurns []*ice.URL
WgIface string
WgIface string
// WgAddr is a Wireguard local address (Wiretrustee Network IP)
WgAddr string
// WgPrivateKey is a Wireguard private key of our peer (it MUST never leave the machine)
@@ -51,6 +50,13 @@ type Engine struct {
// wgPort is a Wireguard local listen port
wgPort int
// STUNs is a list of STUN servers used by ICE
STUNs []*ice.URL
// TURNs is a list of STUN servers used by ICE
TURNs []*ice.URL
cancel context.CancelFunc
}
// Peer is an instance of the Connection Peer
@@ -60,7 +66,7 @@ type Peer struct {
}
// NewEngine creates a new Connection Engine
func NewEngine(signalClient *signal.Client, mgmClient *mgm.Client, config *EngineConfig) *Engine {
func NewEngine(signalClient *signal.Client, mgmClient *mgm.Client, config *EngineConfig, cancel context.CancelFunc) *Engine {
return &Engine{
signal: signalClient,
mgmClient: mgmClient,
@@ -68,9 +74,23 @@ func NewEngine(signalClient *signal.Client, mgmClient *mgm.Client, config *Engin
peerMux: &sync.Mutex{},
syncMsgMux: &sync.Mutex{},
config: config,
STUNs: []*ice.URL{},
TURNs: []*ice.URL{},
cancel: cancel,
}
}
func (e *Engine) Stop() error {
log.Debugf("removing Wiretrustee interface %s", e.config.WgIface)
err := iface.Close()
if err != nil {
log.Errorf("failed closing Wiretrustee interface %s %v", e.config.WgIface, err)
return err
}
return nil
}
// Start creates a new Wireguard tunnel interface and listens to events from Signal and Management services
// Connections to remote peers are not established here.
// However, they will be established once an event with a list of peers to connect to will be received from Management Service
@@ -187,7 +207,7 @@ func (e *Engine) openPeerConnection(wgPort int, myKey wgtypes.Key, peer Peer) (*
WgAllowedIPs: peer.WgAllowedIps,
WgKey: myKey,
RemoteWgKey: remoteKey,
StunTurnURLS: e.config.StunsTurns,
StunTurnURLS: append(e.STUNs, e.TURNs...),
iFaceBlackList: e.config.IFaceBlackList,
}
@@ -257,54 +277,114 @@ func signalAuth(uFrag string, pwd string, myKey wgtypes.Key, remoteKey wgtypes.K
// receiveManagementEvents connects to the Management Service event stream to receive updates from the management service
// E.g. when a new peer has been registered and we are allowed to connect to it.
func (e *Engine) receiveManagementEvents() {
go func() {
err := e.mgmClient.Sync(func(update *mgmProto.SyncResponse) error {
e.syncMsgMux.Lock()
defer e.syncMsgMux.Unlock()
if update.GetWiretrusteeConfig() != nil {
err := e.updateTURNs(update.GetWiretrusteeConfig().GetTurns())
if err != nil {
return err
}
err = e.updateSTUNs(update.GetWiretrusteeConfig().GetStuns())
if err != nil {
return err
}
//todo update signal
}
if update.GetRemotePeers() != nil || update.GetRemotePeersIsEmpty() {
// empty arrays are serialized by protobuf to null, but for our case empty array is a valid state.
err := e.updatePeers(update.GetRemotePeers())
if err != nil {
return err
}
}
return nil
})
if err != nil {
e.cancel()
return
}
log.Infof("connected to Management Service updates stream")
}()
log.Debugf("connecting to Management Service updates stream")
}
e.mgmClient.Sync(func(update *mgmProto.SyncResponse) error {
// todo handle changes of global settings (in update.GetWiretrusteeConfig())
// todo handle changes of peer settings (in update.GetPeerConfig())
func (e *Engine) updateSTUNs(stuns []*mgmProto.HostConfig) error {
if len(stuns) == 0 {
return nil
}
var newSTUNs []*ice.URL
log.Debugf("got STUNs update from Management Service, updating")
for _, stun := range stuns {
url, err := ice.ParseURL(stun.Uri)
if err != nil {
return err
}
newSTUNs = append(newSTUNs, url)
}
e.STUNs = newSTUNs
e.syncMsgMux.Lock()
defer e.syncMsgMux.Unlock()
return nil
}
remotePeers := update.GetRemotePeers()
if len(remotePeers) != 0 {
func (e *Engine) updateTURNs(turns []*mgmProto.ProtectedHostConfig) error {
if len(turns) == 0 {
return nil
}
var newTURNs []*ice.URL
log.Debugf("got TURNs update from Management Service, updating")
for _, turn := range turns {
url, err := ice.ParseURL(turn.HostConfig.Uri)
if err != nil {
return err
}
url.Username = turn.User
url.Password = turn.Password
newTURNs = append(newTURNs, url)
}
e.TURNs = newTURNs
remotePeerMap := make(map[string]struct{})
for _, peer := range remotePeers {
remotePeerMap[peer.GetWgPubKey()] = struct{}{}
}
return nil
}
//remove peers that are no longer available for us
toRemove := []string{}
for p := range e.conns {
if _, ok := remotePeerMap[p]; !ok {
toRemove = append(toRemove, p)
}
}
err := e.removePeerConnections(toRemove)
if err != nil {
return err
}
func (e *Engine) updatePeers(remotePeers []*mgmProto.RemotePeerConfig) error {
log.Debugf("got peers update from Management Service, updating")
remotePeerMap := make(map[string]struct{})
for _, peer := range remotePeers {
remotePeerMap[peer.GetWgPubKey()] = struct{}{}
}
// add new peers
for _, peer := range remotePeers {
peerKey := peer.GetWgPubKey()
peerIPs := peer.GetAllowedIps()
if _, ok := e.conns[peerKey]; !ok {
go e.initializePeer(Peer{
WgPubKey: peerKey,
WgAllowedIps: strings.Join(peerIPs, ","),
})
}
//remove peers that are no longer available for us
toRemove := []string{}
for p := range e.conns {
if _, ok := remotePeerMap[p]; !ok {
toRemove = append(toRemove, p)
}
}
err := e.removePeerConnections(toRemove)
if err != nil {
return err
}
}
// add new peers
for _, peer := range remotePeers {
peerKey := peer.GetWgPubKey()
peerIPs := peer.GetAllowedIps()
if _, ok := e.conns[peerKey]; !ok {
go e.initializePeer(Peer{
WgPubKey: peerKey,
WgAllowedIps: strings.Join(peerIPs, ","),
})
}
return nil
})
log.Infof("connected to Management Service updates stream")
}
return nil
}
// receiveSignalEvents connects to the Signal Service event stream to negotiate connection with remote peers

14
client/system/info.go Normal file
View File

@@ -0,0 +1,14 @@
package system
//Info is an object that contains machine information
// Most of the code is taken from https://github.com/matishsiao/goInfo
type Info struct {
GoOS string
Kernel string
Core string
Platform string
OS string
OSVersion string
Hostname string
CPUs int
}

View File

@@ -0,0 +1,39 @@
package system
import (
"bytes"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"time"
)
func GetInfo() *Info {
out := _getInfo()
for strings.Contains(out, "broken pipe") {
out = _getInfo()
time.Sleep(500 * time.Millisecond)
}
osStr := strings.Replace(out, "\n", "", -1)
osStr = strings.Replace(osStr, "\r\n", "", -1)
osInfo := strings.Split(osStr, " ")
gio := &Info{Kernel: osInfo[0], OSVersion: osInfo[1], Core: osInfo[1], Platform: osInfo[2], OS: osInfo[0], GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
gio.Hostname, _ = os.Hostname()
return gio
}
func _getInfo() string {
cmd := exec.Command("uname", "-srm")
cmd.Stdin = strings.NewReader("some input")
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
fmt.Println("getInfo:", err)
}
return out.String()
}

View File

@@ -0,0 +1,39 @@
package system
import (
"bytes"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"time"
)
func GetInfo() *Info {
out := _getInfo()
for strings.Contains(out, "broken pipe") {
out = _getInfo()
time.Sleep(500 * time.Millisecond)
}
osStr := strings.Replace(out, "\n", "", -1)
osStr = strings.Replace(osStr, "\r\n", "", -1)
osInfo := strings.Split(osStr, " ")
gio := &Info{Kernel: osInfo[0], Core: osInfo[1], Platform: runtime.GOARCH, OS: osInfo[2], GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
gio.Hostname, _ = os.Hostname()
return gio
}
func _getInfo() string {
cmd := exec.Command("uname", "-sri")
cmd.Stdin = strings.NewReader("some input")
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
fmt.Println("getInfo:", err)
}
return out.String()
}

View File

@@ -0,0 +1,76 @@
package system
import (
"bytes"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"time"
)
func GetInfo() *Info {
info := _getInfo()
for strings.Contains(info, "broken pipe") {
info = _getInfo()
time.Sleep(500 * time.Millisecond)
}
releaseInfo := _getReleaseInfo()
for strings.Contains(info, "broken pipe") {
releaseInfo = _getReleaseInfo()
time.Sleep(500 * time.Millisecond)
}
osRelease := strings.Split(releaseInfo, "\n")
var osName string
var osVer string
for _, s := range osRelease {
if strings.HasPrefix(s, "NAME=") {
osName = strings.Split(s, "=")[1]
osName = strings.ReplaceAll(osName, "\"", "")
} else if strings.HasPrefix(s, "VERSION_ID=") {
osVer = strings.Split(s, "=")[1]
osVer = strings.ReplaceAll(osVer, "\"", "")
}
}
osStr := strings.Replace(info, "\n", "", -1)
osStr = strings.Replace(osStr, "\r\n", "", -1)
osInfo := strings.Split(osStr, " ")
if osName == "" {
osName = osInfo[3]
}
gio := &Info{Kernel: osInfo[0], Core: osInfo[1], Platform: osInfo[2], OS: osName, OSVersion: osVer, GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
gio.Hostname, _ = os.Hostname()
return gio
}
func _getInfo() string {
cmd := exec.Command("uname", "-srio")
cmd.Stdin = strings.NewReader("some")
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
fmt.Println("getInfo:", err)
}
return out.String()
}
func _getReleaseInfo() string {
cmd := exec.Command("cat", "/etc/os-release")
cmd.Stdin = strings.NewReader("some")
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
fmt.Println("getReleaseInfo:", err)
}
return out.String()
}

View File

@@ -0,0 +1,35 @@
package system
import (
"bytes"
"os"
"os/exec"
"runtime"
"strings"
)
func GetInfo() *Info {
cmd := exec.Command("cmd", "ver")
cmd.Stdin = strings.NewReader("some")
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
panic(err)
}
osStr := strings.Replace(out.String(), "\n", "", -1)
osStr = strings.Replace(osStr, "\r\n", "", -1)
tmp1 := strings.Index(osStr, "[Version")
tmp2 := strings.Index(osStr, "]")
var ver string
if tmp1 == -1 || tmp2 == -1 {
ver = "unknown"
} else {
ver = osStr[tmp1+9 : tmp2]
}
gio := &Info{Kernel: "windows", OSVersion: ver, Core: ver, Platform: "unknown", OS: "windows", GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
gio.Hostname, _ = os.Hostname()
return gio
}

View File

@@ -7,14 +7,19 @@
"Password": null
}
],
"Turns": [
{
"Proto": "udp",
"URI": "turn:stun.wiretrustee.com:3468",
"Username": "some_user",
"Password": "c29tZV9wYXNzd29yZA=="
}
],
"TURNConfig": {
"Turns": [
{
"Proto": "udp",
"URI": "turn:stun.wiretrustee.com:3468",
"Username": "some_user",
"Password": "c29tZV9wYXNzd29yZA=="
}
],
"CredentialsTTL": "1h",
"Secret": "c29tZV9wYXNzd29yZA==",
"TimeBasedCredentials": true
},
"Signal": {
"Proto": "http",
"URI": "signal.wiretrustee.com:10000",
@@ -23,11 +28,10 @@
},
"DataDir": "",
"HttpConfig": {
"LetsEncryptDomain": "",
"Address": "0.0.0.0:3000",
"AuthDomain": "<PASTE YOUR AUTH0 DOMAIN HERE>",
"AuthClientId": "<PASTE YOUR AUTH0 CLIENT ID HERE>",
"AuthClientSecret": "<PASTE YOUR AUTH0 SECRET>",
"AuthCallback": "http://localhost:3000/callback"
"LetsEncryptDomain": "<PASTE YOUR LET'S ENCRYPT DOMAIN HERE>",
"Address": "0.0.0.0:33071",
"AuthIssuer": "<PASTE YOUR AUTH0 ISSUER HERE>,",
"AuthAudience": "<PASTE YOUR AUTH0 AUDIENCE HERE>",
"AuthKeysLocation": "<PASTE YOUR AUTH0 PUBLIC JWT KEYS LOCATION HERE>"
}
}

BIN
client/ui/banner.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
client/ui/wiretrustee.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

2
go.mod
View File

@@ -9,7 +9,6 @@ require (
github.com/google/uuid v1.2.0
github.com/gorilla/mux v1.8.0
github.com/kardianos/service v1.2.0
github.com/matishsiao/goInfo v0.0.0-20200404012835-b5f882ee2288
github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.13.0
github.com/pion/ice/v2 v2.1.7
@@ -24,4 +23,5 @@ require (
golang.zx2c4.com/wireguard/windows v0.4.5
google.golang.org/grpc v1.32.0
google.golang.org/protobuf v1.26.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
)

5
go.sum
View File

@@ -11,6 +11,7 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
@@ -160,8 +161,6 @@ github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdA
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matishsiao/goInfo v0.0.0-20200404012835-b5f882ee2288 h1:cdM7et8/VlNnSBpq3KbyQWsYLCY0WsB7tvV8Fr0DUNE=
github.com/matishsiao/goInfo v0.0.0-20200404012835-b5f882ee2288/go.mod h1:yLZrFIhv+Z20hxHvcZpEyKVQp9HMsOJkXAxx7yDqtvg=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
@@ -501,6 +500,8 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=

View File

@@ -13,10 +13,13 @@ import (
const (
defaultMTU = 1280
WgPort = 51820
)
var tunIface tun.Device
var (
tunIface tun.Device
// todo check after move the WgPort constant to the client
WgPort = 51820
)
// CreateWithUserspace Creates a new Wireguard interface, using wireguard-go userspace implementation
func CreateWithUserspace(iface string, address string) error {

View File

@@ -3,6 +3,7 @@ package iface
import (
log "github.com/sirupsen/logrus"
"net"
"os"
"os/exec"
"strings"
)
@@ -40,5 +41,23 @@ func addRoute(iface string, ipNet *net.IPNet) error {
// Closes the tunnel interface
func Close() error {
return CloseWithUserspace()
name, err := tunIface.Name()
if err != nil {
return err
}
sockPath := "/var/run/wireguard/" + name + ".sock"
err = CloseWithUserspace()
if err != nil {
return err
}
if _, err := os.Stat(sockPath); err == nil {
err = os.Remove(sockPath)
if err != nil {
return err
}
}
return nil
}

View File

@@ -31,7 +31,7 @@ func CreateWithKernel(iface string, address string) error {
}
// check if interface exists
l, err := netlink.LinkByName(WgInterfaceDefault)
l, err := netlink.LinkByName(iface)
if err != nil {
switch err.(type) {
case netlink.LinkNotFoundError:
@@ -148,6 +148,7 @@ func Close() error {
return err
}
for _, wgDev := range devList {
// todo check after move the WgPort constant to the client
if wgDev.ListenPort == WgPort {
iface = wgDev.Name
break

View File

@@ -12,33 +12,61 @@ import (
// keep darwin compability
const (
ifaceName = "utun999"
key = "0PMI6OkB5JmB+Jj/iWWHekuQRx+bipZirWCWKFXexHc="
peerPubKey = "Ok0mC0qlJyXEPKh2UFIpsI2jG0L7LRpC3sLAusSJ5CQ="
)
func init() {
log.SetLevel(log.DebugLevel)
}
//
func Test_CreateInterface(t *testing.T) {
level, _ := log.ParseLevel("Debug")
log.SetLevel(level)
ifaceName := "utun999"
wgIP := "10.99.99.1/24"
err := Create(ifaceName, wgIP)
if err != nil {
t.Fatal(err)
}
defer func() {
err = Close()
if err != nil {
t.Error(err)
}
}()
wg, err := wgctrl.New()
if err != nil {
t.Fatal(err)
}
defer wg.Close()
defer func() {
err = wg.Close()
if err != nil {
t.Error(err)
}
}()
_, err = wg.Device(ifaceName)
d, err := wg.Device(ifaceName)
if err != nil {
t.Fatal(err)
}
// todo move the WgPort constant to the client
WgPort = d.ListenPort
}
func Test_ConfigureInterface(t *testing.T) {
err := Configure(ifaceName, key)
ifaceName := "utun1000"
wgIP := "10.99.99.10/24"
err := Create(ifaceName, wgIP)
if err != nil {
t.Fatal(err)
}
defer func() {
err = Close()
if err != nil {
t.Error(err)
}
}()
err = Configure(ifaceName, key)
if err != nil {
t.Fatal(err)
}
@@ -47,7 +75,12 @@ func Test_ConfigureInterface(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer wg.Close()
defer func() {
err = wg.Close()
if err != nil {
t.Error(err)
}
}()
wgDevice, err := wg.Device(ifaceName)
if err != nil {
@@ -59,14 +92,30 @@ func Test_ConfigureInterface(t *testing.T) {
}
func Test_UpdatePeer(t *testing.T) {
keepAlive := 15 * time.Second
allowedIP := "10.99.99.2/32"
endpoint := "127.0.0.1:9900"
err := UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint)
ifaceName := "utun1001"
wgIP := "10.99.99.20/24"
err := Create(ifaceName, wgIP)
if err != nil {
t.Fatal(err)
}
peer, err := getPeer()
defer func() {
err = Close()
if err != nil {
t.Error(err)
}
}()
err = Configure(ifaceName, key)
if err != nil {
t.Fatal(err)
}
keepAlive := 15 * time.Second
allowedIP := "10.99.99.2/32"
endpoint := "127.0.0.1:9900"
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint)
if err != nil {
t.Fatal(err)
}
peer, err := getPeer(ifaceName, t)
if err != nil {
t.Fatal(err)
}
@@ -95,13 +144,37 @@ func Test_UpdatePeer(t *testing.T) {
}
func Test_UpdatePeerEndpoint(t *testing.T) {
newEndpoint := "127.0.0.1:9999"
err := UpdatePeerEndpoint(ifaceName, peerPubKey, newEndpoint)
ifaceName := "utun1002"
wgIP := "10.99.99.30/24"
err := Create(ifaceName, wgIP)
if err != nil {
t.Fatal(err)
}
defer func() {
err = Close()
if err != nil {
t.Error(err)
}
}()
err = Configure(ifaceName, key)
if err != nil {
t.Fatal(err)
}
keepAlive := 15 * time.Second
allowedIP := "10.99.99.2/32"
endpoint := "127.0.0.1:9900"
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint)
if err != nil {
t.Fatal(err)
}
peer, err := getPeer()
newEndpoint := "127.0.0.1:9999"
err = UpdatePeerEndpoint(ifaceName, peerPubKey, newEndpoint)
if err != nil {
t.Fatal(err)
}
peer, err := getPeer(ifaceName, t)
if err != nil {
t.Fatal(err)
}
@@ -112,28 +185,79 @@ func Test_UpdatePeerEndpoint(t *testing.T) {
}
func Test_RemovePeer(t *testing.T) {
err := RemovePeer(ifaceName, peerPubKey)
ifaceName := "utun1003"
wgIP := "10.99.99.40/24"
err := Create(ifaceName, wgIP)
if err != nil {
t.Fatal(err)
}
_, err = getPeer()
defer func() {
err = Close()
if err != nil {
t.Error(err)
}
}()
err = Configure(ifaceName, key)
if err != nil {
t.Fatal(err)
}
keepAlive := 15 * time.Second
allowedIP := "10.99.99.2/32"
endpoint := "127.0.0.1:9900"
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint)
if err != nil {
t.Fatal(err)
}
err = RemovePeer(ifaceName, peerPubKey)
if err != nil {
t.Fatal(err)
}
_, err = getPeer(ifaceName, t)
if err.Error() != "peer not found" {
t.Fatal(err)
}
}
func Test_Close(t *testing.T) {
err := Close()
ifaceName := "utun1004"
wgIP := "10.99.99.50/24"
err := Create(ifaceName, wgIP)
if err != nil {
t.Fatal(err)
}
wg, err := wgctrl.New()
if err != nil {
t.Fatal(err)
}
defer func() {
err = wg.Close()
if err != nil {
t.Error(err)
}
}()
d, err := wg.Device(ifaceName)
if err != nil {
t.Fatal(err)
}
// todo move the WgPort constant to the client
WgPort = d.ListenPort
err = Close()
if err != nil {
t.Fatal(err)
}
}
func getPeer() (wgtypes.Peer, error) {
func getPeer(ifaceName string, t *testing.T) (wgtypes.Peer, error) {
emptyPeer := wgtypes.Peer{}
wg, err := wgctrl.New()
if err != nil {
return emptyPeer, err
}
defer wg.Close()
defer func() {
err = wg.Close()
if err != nil {
t.Error(err)
}
}()
wgDevice, err := wg.Device(ifaceName)
if err != nil {

View File

@@ -1,33 +0,0 @@
{
"Stuns": [
{
"Proto": "udp",
"URI": "stun:stun.wiretrustee.com:3468",
"Username": "",
"Password": null
}
],
"Turns": [
{
"Proto": "udp",
"URI": "turn:stun.wiretrustee.com:3468",
"Username": "some_user",
"Password": "c29tZV9wYXNzd29yZA=="
}
],
"Signal": {
"Proto": "http",
"URI": "signal.wiretrustee.com:10000",
"Username": "",
"Password": null
},
"Datadir": "",
"HttpConfig": {
"LetsEncryptDomain": "",
"Address": "0.0.0.0:3000",
"AuthIssuer": "<PASTE YOUR AUTH0 ISSUER HERE>",
"AuthAudience": "<PASTE YOUR AUTH0 AUDIENCE HERE>",
"AuthKeysLocation": "<PASTE YOUR JWT KEY SET location>",
"UIFilesLocation": "/var/lib/wiretrustee/ui/"
}
}

View File

@@ -1,28 +1,46 @@
version: "3"
services:
#UI dashboard
dashboard:
image: wiretrustee/dashboard:main
restart: unless-stopped
ports:
- 80:80
# - 443:443
environment:
- AUTH0_DOMAIN=<YOUR AUTH0 DOMAIN>
- AUTH0_CLIENT_ID=<YOUR AUTH0 CLIENT ID>
- AUTH0_AUDIENCE=<YOUR AUTH0 AUDIENCE>
- WIRETRUSTEE_MGMT_API_ENDPOINT=http://localhost:33071
# - NGINX_SSL_PORT: 443
# - LETSENCRYPT_DOMAIN: <YOUR DOMAIN>
# - LETSENCRYPT_EMAIL: <YOUR EMAIL>
# Signal
signal:
image: wiretrustee/signal:latest
restart: unless-stopped
volumes:
- wiretrustee-mgmt:/var/lib/wiretrustee
- /varl/log/wiretrustee/signal.log:/var/log/wiretrustee/signal.log
ports:
- 10000:10000
# # port and command for Let's Encrypt validation
# - 443:443
# command: ["--letsencrypt-domain", "<YOUR-DOMAIN>"]
# command: ["--letsencrypt-domain", "<YOUR-DOMAIN>", "--log-file", "console"]
# Management
management:
image: wiretrustee/management:latest
restart: unless-stopped
volumes:
- wiretrustee-mgmt:/var/lib/wiretrustee
- ./config.json:/etc/wiretrustee/config.json
- ./management.json:/etc/wiretrustee/management.json
# - /var/log/wiretrustee/management.log:/var/log/wiretrustee/management.log
ports:
- 33073:33073
- 33073:33073 #gRPC port
- 33071:33071 #HTTP port
# # port and command for Let's Encrypt validation
# - 443:443
# command: ["--letsencrypt-domain", "<YOUR-DOMAIN>"]
# command: ["--letsencrypt-domain", "<YOUR-DOMAIN>", "--log-file", "console"]
# Coturn
coturn:
image: coturn/coturn
@@ -35,4 +53,4 @@ services:
network_mode: host
volumes:
wiretrustee-mgmt:
wiretrustee-signal:
wiretrustee-signal:

View File

@@ -0,0 +1,37 @@
{
"Stuns": [
{
"Proto": "udp",
"URI": "stun:stun.wiretrustee.com:3468",
"Username": "",
"Password": null
}
],
"TURNConfig": {
"Turns": [
{
"Proto": "udp",
"URI": "turn:stun.wiretrustee.com:3468",
"Username": "some_user",
"Password": "c29tZV9wYXNzd29yZA=="
}
],
"CredentialsTTL": "1h",
"Secret": "c29tZV9wYXNzd29yZA==",
"TimeBasedCredentials": true
},
"Signal": {
"Proto": "http",
"URI": "signal.wiretrustee.com:10000",
"Username": "",
"Password": null
},
"Datadir": "",
"HttpConfig": {
"LetsEncryptDomain": "<PASTE YOUR LET'S ENCRYPT DOMAIN HERE>",
"Address": "0.0.0.0:33071",
"AuthIssuer": "<PASTE YOUR AUTH0 ISSUER HERE>,",
"AuthAudience": "<PASTE YOUR AUTH0 AUDIENCE HERE>",
"AuthKeysLocation": "<PASTE YOUR AUTH0 PUBLIC JWT KEYS LOCATION HERE>"
}
}

View File

@@ -1,3 +1,4 @@
FROM gcr.io/distroless/base
ENTRYPOINT [ "/go/bin/wiretrustee-mgmt","management"]
CMD ["--log-file", "console"]
COPY wiretrustee-mgmt /go/bin/wiretrustee-mgmt

View File

@@ -1,3 +1,4 @@
FROM gcr.io/distroless/base:debug
ENTRYPOINT [ "/go/bin/wiretrustee-mgmt","management","--log-level","debug"]
CMD ["--log-file", "console"]
COPY wiretrustee-mgmt /go/bin/wiretrustee-mgmt

View File

@@ -18,6 +18,7 @@ Flags:
Global Flags:
--config string Wiretrustee config file location to write new config to (default "/etc/wiretrustee/config.json")
--log-level string (default "info")
--log-file string sets Wiretrustee log path. If console is specified the the log will be output to stdout (default "/var/log/wiretrustee/management.log")
```
## Run Management service (Docker)

View File

@@ -4,8 +4,8 @@ import (
"context"
"crypto/tls"
"github.com/cenkalti/backoff/v4"
"github.com/matishsiao/goInfo"
log "github.com/sirupsen/logrus"
"github.com/wiretrustee/wiretrustee/client/system"
"github.com/wiretrustee/wiretrustee/encryption"
"github.com/wiretrustee/wiretrustee/management/proto"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@@ -64,54 +64,61 @@ func (c *Client) Close() error {
return c.conn.Close()
}
//defaultBackoff is a basic backoff mechanism for general issues
func defaultBackoff() backoff.BackOff {
return &backoff.ExponentialBackOff{
InitialInterval: 800 * time.Millisecond,
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,
}
}
// Sync wraps the real client's Sync endpoint call and takes care of retries and encryption/decryption of messages
// Non blocking request (executed in go routine). The result will be sent via msgHandler callback function
func (c *Client) Sync(msgHandler func(msg *proto.SyncResponse) error) {
// Blocking request. The result will be sent via msgHandler callback function
func (c *Client) Sync(msgHandler func(msg *proto.SyncResponse) error) error {
go func() {
var backOff = defaultBackoff()
var backOff = &backoff.ExponentialBackOff{
InitialInterval: 800 * time.Millisecond,
RandomizationFactor: backoff.DefaultRandomizationFactor,
Multiplier: backoff.DefaultMultiplier,
MaxInterval: 3 * time.Second,
MaxElapsedTime: time.Duration(0), //never stop retrying
Stop: backoff.Stop,
Clock: backoff.SystemClock,
}
operation := func() error {
operation := func() error {
// todo we already have it since we did the Login, maybe cache it locally?
serverPubKey, err := c.GetServerPublicKey()
if err != nil {
log.Errorf("failed getting Management Service public key: %s", err)
return err
}
stream, err := c.connectToStream(*serverPubKey)
if err != nil {
log.Errorf("failed to open Management Service stream: %s", err)
return err
}
log.Infof("connected to the Management Service Stream")
// blocking until error
err = c.receiveEvents(stream, *serverPubKey, msgHandler)
if err != nil {
return err
}
backOff.Reset()
return nil
}
err := backoff.Retry(operation, backOff)
// todo we already have it since we did the Login, maybe cache it locally?
serverPubKey, err := c.GetServerPublicKey()
if err != nil {
log.Errorf("failed communicating with Management Service %s ", err)
return
log.Errorf("failed getting Management Service public key: %s", err)
return err
}
}()
stream, err := c.connectToStream(*serverPubKey)
if err != nil {
log.Errorf("failed to open Management Service stream: %s", err)
return err
}
log.Infof("connected to the Management Service Stream")
// 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??
}*/
return err
}
backOff.Reset()
return nil
}
err := backoff.Retry(operation, backOff)
if err != nil {
log.Errorf("exiting Management Service connection retry loop due to unrecoverable error %s ", err)
return err
}
return nil
}
func (c *Client) connectToStream(serverPubKey wgtypes.Key) (proto.ManagementService_SyncClient, error) {
@@ -138,7 +145,7 @@ func (c *Client) receiveEvents(stream proto.ManagementService_SyncClient, server
return err
}
if err != nil {
log.Errorf("disconnected from Management Service syn stream: %v", err)
log.Errorf("disconnected from Management Service sync stream: %v", err)
return err
}
@@ -206,12 +213,12 @@ func (c *Client) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*proto.L
// Takes care of encrypting and decrypting messages.
// This method will also collect system info and send it with the request (e.g. hostname, os, etc)
func (c *Client) Register(serverKey wgtypes.Key, setupKey string) (*proto.LoginResponse, error) {
gi := goInfo.GetInfo()
gi := system.GetInfo()
meta := &proto.PeerSystemMeta{
Hostname: gi.Hostname,
GoOS: gi.GoOS,
OS: gi.OS,
Core: gi.Core,
Core: gi.OSVersion,
Platform: gi.Platform,
Kernel: gi.Kernel,
WiretrusteeVersion: "",

View File

@@ -60,8 +60,10 @@ func startManagement(config *mgmt.Config, t *testing.T) (*grpc.Server, net.Liste
t.Fatal(err)
}
accountManager := mgmt.NewManager(store)
mgmtServer, err := mgmt.NewServer(config, accountManager)
peersUpdateManager := mgmt.NewPeersUpdateManager()
accountManager := mgmt.NewManager(store, peersUpdateManager)
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager)
if err != nil {
t.Fatal(err)
}
@@ -144,10 +146,15 @@ func TestClient_Sync(t *testing.T) {
ch := make(chan *mgmtProto.SyncResponse, 1)
tested.Sync(func(msg *mgmtProto.SyncResponse) error {
ch <- msg
return nil
})
go func() {
err = tested.Sync(func(msg *mgmtProto.SyncResponse) error {
ch <- msg
return nil
})
if err != nil {
return
}
}()
select {
case resp := <-ch:
@@ -160,6 +167,9 @@ func TestClient_Sync(t *testing.T) {
if len(resp.GetRemotePeers()) != 1 {
t.Errorf("expecting RemotePeers size %d got %d", 1, len(resp.GetRemotePeers()))
}
if resp.GetRemotePeersIsEmpty() == true {
t.Error("expecting RemotePeers property to be false, got true")
}
if resp.GetRemotePeers()[0].GetWgPubKey() != remoteKey.PublicKey().String() {
t.Errorf("expecting RemotePeer public key %s got %s", remoteKey.PublicKey().String(), resp.GetRemotePeers()[0].GetWgPubKey())
}

View File

@@ -43,6 +43,10 @@ var (
Short: "start Wiretrustee Management Server",
Run: func(cmd *cobra.Command, args []string) {
flag.Parse()
err := util.InitLog(logLevel, logFile)
if err != nil {
log.Fatalf("failed initializing log %v", err)
}
config, err := loadConfig()
if err != nil {
@@ -60,7 +64,8 @@ var (
if err != nil {
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
}
accountManager := server.NewManager(store)
peersUpdateManager := server.NewPeersUpdateManager()
accountManager := server.NewManager(store, peersUpdateManager)
var opts []grpc.ServerOption
@@ -77,8 +82,8 @@ var (
opts = append(opts, grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
grpcServer := grpc.NewServer(opts...)
server, err := server.NewServer(config, accountManager)
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
server, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager)
if err != nil {
log.Fatalf("failed creating new server: %v", err)
}

View File

@@ -2,12 +2,10 @@ package cmd
import (
"fmt"
"github.com/spf13/cobra"
"os"
"os/signal"
"runtime"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
const (
@@ -19,6 +17,8 @@ var (
configPath string
defaultConfigPath string
logLevel string
defaultLogFile string
logFile string
rootCmd = &cobra.Command{
Use: "wiretrustee-mgmt",
@@ -38,12 +38,15 @@ func init() {
stopCh = make(chan int)
defaultConfigPath = "/etc/wiretrustee/config.json"
defaultConfigPath = "/etc/wiretrustee/management.json"
defaultLogFile = "/var/log/wiretrustee/management.log"
if runtime.GOOS == "windows" {
defaultConfigPath = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "config.json"
defaultConfigPath = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "management.json"
defaultLogFile = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "management.log"
}
rootCmd.PersistentFlags().StringVar(&configPath, "config", defaultConfigPath, "Wiretrustee config file location to write new config to")
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "")
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Wiretrustee log path. If console is specified the the log will be output to stdout")
rootCmd.AddCommand(mgmtCmd)
}
@@ -58,13 +61,3 @@ func SetupCloseHandler() {
}
}()
}
// InitLog parses and sets log-level input
func InitLog(logLevel string) {
level, err := log.ParseLevel(logLevel)
if err != nil {
log.Errorf("Failed parsing log-level %s: %s", logLevel, err)
os.Exit(ExitSetupFailed)
}
log.SetLevel(level)
}

View File

@@ -181,6 +181,8 @@ type SyncResponse struct {
WiretrusteeConfig *WiretrusteeConfig `protobuf:"bytes,1,opt,name=wiretrusteeConfig,proto3" json:"wiretrusteeConfig,omitempty"`
PeerConfig *PeerConfig `protobuf:"bytes,2,opt,name=peerConfig,proto3" json:"peerConfig,omitempty"`
RemotePeers []*RemotePeerConfig `protobuf:"bytes,3,rep,name=remotePeers,proto3" json:"remotePeers,omitempty"`
// Indicates whether remotePeers array is empty or not to bypass protobuf null and empty array equality.
RemotePeersIsEmpty bool `protobuf:"varint,4,opt,name=remotePeersIsEmpty,proto3" json:"remotePeersIsEmpty,omitempty"`
}
func (x *SyncResponse) Reset() {
@@ -236,6 +238,13 @@ func (x *SyncResponse) GetRemotePeers() []*RemotePeerConfig {
return nil
}
func (x *SyncResponse) GetRemotePeersIsEmpty() bool {
if x != nil {
return x.RemotePeersIsEmpty
}
return false
}
type LoginRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -860,7 +869,7 @@ var file_management_proto_rawDesc = []byte{
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12,
0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62,
0x6f, 0x64, 0x79, 0x22, 0x0d, 0x0a, 0x0b, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x22, 0xd3, 0x01, 0x0a, 0x0c, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x73, 0x74, 0x22, 0x83, 0x02, 0x0a, 0x0c, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74,
0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d,
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x57, 0x69, 0x72, 0x65,
@@ -873,7 +882,10 @@ var file_management_proto_rawDesc = []byte{
0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e,
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74,
0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x72, 0x65, 0x6d,
0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x22, 0x5a, 0x0a, 0x0c, 0x4c, 0x6f, 0x67, 0x69,
0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f,
0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x04,
0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72,
0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x5a, 0x0a, 0x0c, 0x4c, 0x6f, 0x67, 0x69,
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x75,
0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x74, 0x75,
0x70, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01,

View File

@@ -42,6 +42,8 @@ message SyncResponse {
PeerConfig peerConfig = 2;
repeated RemotePeerConfig remotePeers = 3;
// Indicates whether remotePeers array is empty or not to bypass protobuf null and empty array equality.
bool remotePeersIsEmpty = 4;
}
message LoginRequest {

View File

@@ -13,7 +13,8 @@ import (
type AccountManager struct {
Store Store
// mutex to synchronise account operations (e.g. generating Peer IP address inside the Network)
mux sync.Mutex
mux sync.Mutex
peersUpdateManager *PeersUpdateManager
}
// Account represents a unique account of the system
@@ -25,19 +26,20 @@ type Account struct {
}
// NewManager creates a new AccountManager with a provided Store
func NewManager(store Store) *AccountManager {
func NewManager(store Store, peersUpdateManager *PeersUpdateManager) *AccountManager {
return &AccountManager{
Store: store,
mux: sync.Mutex{},
Store: store,
mux: sync.Mutex{},
peersUpdateManager: peersUpdateManager,
}
}
//AddSetupKey generates a new setup key with a given name and type, and adds it to the specified account
func (manager *AccountManager) AddSetupKey(accountId string, keyName string, keyType SetupKeyType, expiresIn time.Duration) (*SetupKey, error) {
manager.mux.Lock()
defer manager.mux.Unlock()
func (am *AccountManager) AddSetupKey(accountId string, keyName string, keyType SetupKeyType, expiresIn time.Duration) (*SetupKey, error) {
am.mux.Lock()
defer am.mux.Unlock()
account, err := manager.Store.GetAccount(accountId)
account, err := am.Store.GetAccount(accountId)
if err != nil {
return nil, status.Errorf(codes.NotFound, "account not found")
}
@@ -45,7 +47,7 @@ func (manager *AccountManager) AddSetupKey(accountId string, keyName string, key
setupKey := GenerateSetupKey(keyName, keyType, expiresIn)
account.SetupKeys[setupKey.Key] = setupKey
err = manager.Store.SaveAccount(account)
err = am.Store.SaveAccount(account)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed adding account key")
}
@@ -54,11 +56,11 @@ func (manager *AccountManager) AddSetupKey(accountId string, keyName string, key
}
//RevokeSetupKey marks SetupKey as revoked - becomes not valid anymore
func (manager *AccountManager) RevokeSetupKey(accountId string, keyId string) (*SetupKey, error) {
manager.mux.Lock()
defer manager.mux.Unlock()
func (am *AccountManager) RevokeSetupKey(accountId string, keyId string) (*SetupKey, error) {
am.mux.Lock()
defer am.mux.Unlock()
account, err := manager.Store.GetAccount(accountId)
account, err := am.Store.GetAccount(accountId)
if err != nil {
return nil, status.Errorf(codes.NotFound, "account not found")
}
@@ -71,7 +73,7 @@ func (manager *AccountManager) RevokeSetupKey(accountId string, keyId string) (*
keyCopy := setupKey.Copy()
keyCopy.Revoked = true
account.SetupKeys[keyCopy.Key] = keyCopy
err = manager.Store.SaveAccount(account)
err = am.Store.SaveAccount(account)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed adding account key")
}
@@ -80,11 +82,11 @@ func (manager *AccountManager) RevokeSetupKey(accountId string, keyId string) (*
}
//RenameSetupKey renames existing setup key of the specified account.
func (manager *AccountManager) RenameSetupKey(accountId string, keyId string, newName string) (*SetupKey, error) {
manager.mux.Lock()
defer manager.mux.Unlock()
func (am *AccountManager) RenameSetupKey(accountId string, keyId string, newName string) (*SetupKey, error) {
am.mux.Lock()
defer am.mux.Unlock()
account, err := manager.Store.GetAccount(accountId)
account, err := am.Store.GetAccount(accountId)
if err != nil {
return nil, status.Errorf(codes.NotFound, "account not found")
}
@@ -97,7 +99,7 @@ func (manager *AccountManager) RenameSetupKey(accountId string, keyId string, ne
keyCopy := setupKey.Copy()
keyCopy.Name = newName
account.SetupKeys[keyCopy.Key] = keyCopy
err = manager.Store.SaveAccount(account)
err = am.Store.SaveAccount(account)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed adding account key")
}
@@ -106,11 +108,11 @@ func (manager *AccountManager) RenameSetupKey(accountId string, keyId string, ne
}
//GetAccount returns an existing account or error (NotFound) if doesn't exist
func (manager *AccountManager) GetAccount(accountId string) (*Account, error) {
manager.mux.Lock()
defer manager.mux.Unlock()
func (am *AccountManager) GetAccount(accountId string) (*Account, error) {
am.mux.Lock()
defer am.mux.Unlock()
account, err := manager.Store.GetAccount(accountId)
account, err := am.Store.GetAccount(accountId)
if err != nil {
return nil, status.Errorf(codes.NotFound, "account not found")
}
@@ -119,21 +121,21 @@ func (manager *AccountManager) GetAccount(accountId string) (*Account, error) {
}
// GetOrCreateAccount returns an existing account or creates a new one if doesn't exist
func (manager *AccountManager) GetOrCreateAccount(accountId string) (*Account, error) {
manager.mux.Lock()
defer manager.mux.Unlock()
func (am *AccountManager) GetOrCreateAccount(accountId string) (*Account, error) {
am.mux.Lock()
defer am.mux.Unlock()
_, err := manager.Store.GetAccount(accountId)
_, err := am.Store.GetAccount(accountId)
if err != nil {
if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound {
return manager.createAccount(accountId)
return am.createAccount(accountId)
} else {
// other error
return nil, err
}
}
account, err := manager.Store.GetAccount(accountId)
account, err := am.Store.GetAccount(accountId)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed retrieving account")
}
@@ -142,12 +144,12 @@ func (manager *AccountManager) GetOrCreateAccount(accountId string) (*Account, e
}
//AccountExists checks whether account exists (returns true) or not (returns false)
func (manager *AccountManager) AccountExists(accountId string) (*bool, error) {
manager.mux.Lock()
defer manager.mux.Unlock()
func (am *AccountManager) AccountExists(accountId string) (*bool, error) {
am.mux.Lock()
defer am.mux.Unlock()
var res bool
_, err := manager.Store.GetAccount(accountId)
_, err := am.Store.GetAccount(accountId)
if err != nil {
if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound {
res = false
@@ -162,19 +164,19 @@ func (manager *AccountManager) AccountExists(accountId string) (*bool, error) {
}
// AddAccount generates a new Account with a provided accountId and saves to the Store
func (manager *AccountManager) AddAccount(accountId string) (*Account, error) {
func (am *AccountManager) AddAccount(accountId string) (*Account, error) {
manager.mux.Lock()
defer manager.mux.Unlock()
am.mux.Lock()
defer am.mux.Unlock()
return manager.createAccount(accountId)
return am.createAccount(accountId)
}
func (manager *AccountManager) createAccount(accountId string) (*Account, error) {
func (am *AccountManager) createAccount(accountId string) (*Account, error) {
account, _ := newAccountWithId(accountId)
err := manager.Store.SaveAccount(account)
err := am.Store.SaveAccount(account)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed creating account")
}
@@ -188,17 +190,19 @@ func newAccountWithId(accountId string) (*Account, *SetupKey) {
log.Debugf("creating new account")
setupKeys := make(map[string]*SetupKey)
setupKey := GenerateDefaultSetupKey()
setupKeys[setupKey.Key] = setupKey
defaultKey := GenerateDefaultSetupKey()
oneOffKey := GenerateSetupKey("One-off key", SetupKeyOneOff, DefaultSetupKeyDuration)
setupKeys[defaultKey.Key] = defaultKey
setupKeys[oneOffKey.Key] = oneOffKey
network := &Network{
Id: uuid.New().String(),
Net: net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 192, 0, 0}},
Dns: ""}
peers := make(map[string]*Peer)
log.Debugf("created new account %s with setup key %s", accountId, setupKey.Key)
log.Debugf("created new account %s with setup key %s", accountId, defaultKey.Key)
return &Account{Id: accountId, SetupKeys: setupKeys, Network: network, Peers: peers}, setupKey
return &Account{Id: accountId, SetupKeys: setupKeys, Network: network, Peers: peers}, defaultKey
}
// newAccount creates a new Account with a default SetupKey (doesn't store in a Store)

View File

@@ -17,7 +17,7 @@ func TestAccountManager_AddAccount(t *testing.T) {
expectedId := "test_account"
expectedPeersSize := 0
expectedSetupKeysSize := 1
expectedSetupKeysSize := 2
expectedNetwork := net.IPNet{
IP: net.IP{100, 64, 0, 0},
Mask: net.IPMask{255, 192, 0, 0},
@@ -201,7 +201,7 @@ func createManager(t *testing.T) (*AccountManager, error) {
if err != nil {
return nil, err
}
return NewManager(store), nil
return NewManager(store, NewPeersUpdateManager()), nil
}
func createStore(t *testing.T) (Store, error) {

View File

@@ -1,5 +1,9 @@
package server
import (
"github.com/wiretrustee/wiretrustee/util"
)
type Protocol string
const (
@@ -12,15 +16,23 @@ const (
// Config of the Management service
type Config struct {
Stuns []*Host
Turns []*Host
Signal *Host
Stuns []*Host
TURNConfig *TURNConfig
Signal *Host
Datadir string
HttpConfig *HttpServerConfig
}
// TURNConfig is a config of the TURNCredentialsManager
type TURNConfig struct {
TimeBasedCredentials bool
CredentialsTTL util.Duration
Secret string
Turns []*Host
}
// HttpServerConfig is a config of the HTTP Management service server
type HttpServerConfig struct {
LetsEncryptDomain string
@@ -39,5 +51,5 @@ type Host struct {
// URI e.g. turns://stun.wiretrustee.com:4430 or signal.wiretrustee.com:10000
URI string
Username string
Password []byte
Password string
}

View File

@@ -190,6 +190,22 @@ func (s *FileStore) GetAccountBySetupKey(setupKey string) (*Account, error) {
return account, nil
}
func (s *FileStore) GetAccountPeers(accountId string) ([]*Peer, error) {
s.mux.Lock()
defer s.mux.Unlock()
account, err := s.GetAccount(accountId)
if err != nil {
return nil, err
}
var peers []*Peer
for _, peer := range account.Peers {
peers = append(peers, peer)
}
return peers, nil
}
func (s *FileStore) GetAccount(accountId string) (*Account, error) {

View File

@@ -3,7 +3,6 @@ package server
import (
"context"
"fmt"
"sync"
"time"
"github.com/golang/protobuf/ptypes/timestamp"
@@ -20,31 +19,28 @@ type Server struct {
accountManager *AccountManager
wgKey wgtypes.Key
proto.UnimplementedManagementServiceServer
peerChannels map[string]chan *UpdateChannelMessage
channelsMux *sync.Mutex
config *Config
peersUpdateManager *PeersUpdateManager
config *Config
turnCredentialsManager TURNCredentialsManager
}
// AllowedIPsFormat generates Wireguard AllowedIPs format (e.g. 100.30.30.1/32)
const AllowedIPsFormat = "%s/32"
type UpdateChannelMessage struct {
Update *proto.SyncResponse
}
// NewServer creates a new Management server
func NewServer(config *Config, accountManager *AccountManager) (*Server, error) {
func NewServer(config *Config, accountManager *AccountManager, peersUpdateManager *PeersUpdateManager, turnCredentialsManager TURNCredentialsManager) (*Server, error) {
key, err := wgtypes.GeneratePrivateKey()
if err != nil {
return nil, err
}
return &Server{
wgKey: key,
// peerKey -> event channel
peerChannels: make(map[string]chan *UpdateChannelMessage),
channelsMux: &sync.Mutex{},
accountManager: accountManager,
config: config,
peersUpdateManager: peersUpdateManager,
accountManager: accountManager,
config: config,
turnCredentialsManager: turnCredentialsManager,
}, nil
}
@@ -90,8 +86,15 @@ func (s *Server) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_S
return err
}
updates := s.openUpdatesChannel(peerKey.String())
updates := s.peersUpdateManager.CreateChannel(peerKey.String())
err = s.accountManager.MarkPeerConnected(peerKey.String(), true)
if err != nil {
log.Warnf("failed marking peer as connected %s %v", peerKey, err)
}
if s.config.TURNConfig.TimeBasedCredentials {
s.turnCredentialsManager.SetupRefresh(peerKey.String())
}
// keep a connection to the peer and send updates when available
for {
select {
@@ -115,19 +118,24 @@ func (s *Server) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_S
if err != nil {
return status.Errorf(codes.Internal, "failed sending update message")
}
log.Debugf("sent an update to peer %s", peerKey.String())
// condition when client <-> server connection has been terminated
case <-srv.Context().Done():
// happens when connection drops, e.g. client disconnects
log.Debugf("stream of peer %s has been closed", peerKey.String())
s.closeUpdatesChannel(peerKey.String())
s.peersUpdateManager.CloseChannel(peerKey.String())
s.turnCredentialsManager.CancelRefresh(peerKey.String())
err = s.accountManager.MarkPeerConnected(peerKey.String(), false)
if err != nil {
log.Warnf("failed marking peer as disconnected %s %v", peerKey, err)
}
// todo stop turn goroutine
return srv.Context().Err()
}
}
}
func (s *Server) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Peer, error) {
s.channelsMux.Lock()
defer s.channelsMux.Unlock()
meta := req.GetMeta()
if meta == nil {
@@ -157,16 +165,18 @@ func (s *Server) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Pe
// notify other peers of our registration
for _, remotePeer := range peers {
if channel, ok := s.peerChannels[remotePeer.Key]; ok {
// exclude notified peer and add ourselves
peersToSend := []*Peer{peer}
for _, p := range peers {
if remotePeer.Key != p.Key {
peersToSend = append(peersToSend, p)
}
// exclude notified peer and add ourselves
peersToSend := []*Peer{peer}
for _, p := range peers {
if remotePeer.Key != p.Key {
peersToSend = append(peersToSend, p)
}
update := toSyncResponse(s.config, peer, peersToSend)
channel <- &UpdateChannelMessage{Update: update}
}
update := toSyncResponse(s.config, peer, peersToSend, nil)
err = s.peersUpdateManager.SendUpdate(remotePeer.Key, &UpdateMessage{Update: update})
if err != nil {
// todo rethink if we should keep this return
return nil, err
}
}
@@ -215,7 +225,7 @@ func (s *Server) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto
// if peer has reached this point then it has logged in
loginResp := &proto.LoginResponse{
WiretrusteeConfig: toWiretrusteeConfig(s.config),
WiretrusteeConfig: toWiretrusteeConfig(s.config, nil),
PeerConfig: toPeerConfig(peer),
}
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, loginResp)
@@ -229,7 +239,7 @@ func (s *Server) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto
}, nil
}
func toResponseProto(configProto Protocol) proto.HostConfig_Protocol {
func ToResponseProto(configProto Protocol) proto.HostConfig_Protocol {
switch configProto {
case UDP:
return proto.HostConfig_UDP
@@ -247,24 +257,33 @@ func toResponseProto(configProto Protocol) proto.HostConfig_Protocol {
}
}
func toWiretrusteeConfig(config *Config) *proto.WiretrusteeConfig {
func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *proto.WiretrusteeConfig {
var stuns []*proto.HostConfig
for _, stun := range config.Stuns {
stuns = append(stuns, &proto.HostConfig{
Uri: stun.URI,
Protocol: toResponseProto(stun.Proto),
Protocol: ToResponseProto(stun.Proto),
})
}
var turns []*proto.ProtectedHostConfig
for _, turn := range config.Turns {
for _, turn := range config.TURNConfig.Turns {
var username string
var password string
if turnCredentials != nil {
username = turnCredentials.Username
password = turnCredentials.Password
} else {
username = turn.Username
password = turn.Password
}
turns = append(turns, &proto.ProtectedHostConfig{
HostConfig: &proto.HostConfig{
Uri: turn.URI,
Protocol: toResponseProto(turn.Proto),
Protocol: ToResponseProto(turn.Proto),
},
User: turn.Username,
Password: string(turn.Password),
User: username,
Password: password,
})
}
@@ -273,7 +292,7 @@ func toWiretrusteeConfig(config *Config) *proto.WiretrusteeConfig {
Turns: turns,
Signal: &proto.HostConfig{
Uri: config.Signal.URI,
Protocol: toResponseProto(config.Signal.Proto),
Protocol: ToResponseProto(config.Signal.Proto),
},
}
}
@@ -284,13 +303,9 @@ func toPeerConfig(peer *Peer) *proto.PeerConfig {
}
}
func toSyncResponse(config *Config, peer *Peer, peers []*Peer) *proto.SyncResponse {
func toRemotePeerConfig(peers []*Peer) []*proto.RemotePeerConfig {
wtConfig := toWiretrusteeConfig(config)
pConfig := toPeerConfig(peer)
remotePeers := make([]*proto.RemotePeerConfig, 0, len(peers))
remotePeers := []*proto.RemotePeerConfig{}
for _, rPeer := range peers {
remotePeers = append(remotePeers, &proto.RemotePeerConfig{
WgPubKey: rPeer.Key,
@@ -298,10 +313,23 @@ func toSyncResponse(config *Config, peer *Peer, peers []*Peer) *proto.SyncRespon
})
}
return remotePeers
}
func toSyncResponse(config *Config, peer *Peer, peers []*Peer, turnCredentials *TURNCredentials) *proto.SyncResponse {
wtConfig := toWiretrusteeConfig(config, turnCredentials)
pConfig := toPeerConfig(peer)
remotePeers := toRemotePeerConfig(peers)
return &proto.SyncResponse{
WiretrusteeConfig: wtConfig,
PeerConfig: pConfig,
RemotePeers: remotePeers,
WiretrusteeConfig: wtConfig,
PeerConfig: pConfig,
RemotePeers: remotePeers,
RemotePeersIsEmpty: len(remotePeers) == 0,
}
}
@@ -310,44 +338,6 @@ func (s *Server) IsHealthy(ctx context.Context, req *proto.Empty) (*proto.Empty,
return &proto.Empty{}, nil
}
// openUpdatesChannel creates a go channel for a given peer used to deliver updates relevant to the peer.
func (s *Server) openUpdatesChannel(peerKey string) chan *UpdateChannelMessage {
s.channelsMux.Lock()
defer s.channelsMux.Unlock()
if channel, ok := s.peerChannels[peerKey]; ok {
delete(s.peerChannels, peerKey)
close(channel)
}
//mbragin: todo shouldn't it be more? or configurable?
channel := make(chan *UpdateChannelMessage, 100)
s.peerChannels[peerKey] = channel
err := s.accountManager.MarkPeerConnected(peerKey, true)
if err != nil {
log.Warnf("failed marking peer as connected %s %v", peerKey, err)
}
log.Debugf("opened updates channel for a peer %s", peerKey)
return channel
}
// closeUpdatesChannel closes updates channel of a given peer
func (s *Server) closeUpdatesChannel(peerKey string) {
s.channelsMux.Lock()
defer s.channelsMux.Unlock()
if channel, ok := s.peerChannels[peerKey]; ok {
delete(s.peerChannels, peerKey)
close(channel)
}
err := s.accountManager.MarkPeerConnected(peerKey, false)
if err != nil {
log.Warnf("failed marking peer as disconnected %s %v", peerKey, err)
}
log.Debugf("closed updates channel of a peer %s", peerKey)
}
// sendInitialSync sends initial proto.SyncResponse to the peer requesting synchronization
func (s *Server) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.ManagementService_SyncServer) error {
@@ -356,7 +346,16 @@ func (s *Server) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.Mana
log.Warnf("error getting a list of peers for a peer %s", peer.Key)
return err
}
plainResp := toSyncResponse(s.config, peer, peers)
// make secret time based TURN credentials optional
var turnCredentials *TURNCredentials
if s.config.TURNConfig.TimeBasedCredentials {
creds := s.turnCredentialsManager.GenerateCredentials()
turnCredentials = &creds
} else {
turnCredentials = nil
}
plainResp := toSyncResponse(s.config, peer, peers, turnCredentials)
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, plainResp)
if err != nil {

View File

@@ -122,6 +122,6 @@ func toPeerResponse(peer *server.Peer) *PeerResponse {
IP: peer.IP.String(),
Connected: peer.Status.Connected,
LastSeen: peer.Status.LastSeen,
OS: fmt.Sprintf("%s %s", peer.Meta.GoOS, peer.Meta.Core),
OS: fmt.Sprintf("%s %s", peer.Meta.OS, peer.Meta.Core),
}
}

View File

@@ -119,19 +119,18 @@ var _ = Describe("Management service", func() {
Uri: "stun:stun.wiretrustee.com:3468",
Protocol: mgmtProto.HostConfig_UDP,
}
expectedTurnsConfig := &mgmtProto.ProtectedHostConfig{
HostConfig: &mgmtProto.HostConfig{
Uri: "turn:stun.wiretrustee.com:3468",
Protocol: mgmtProto.HostConfig_UDP,
},
User: "some_user",
Password: "some_password",
expectedTRUNHost := &mgmtProto.HostConfig{
Uri: "turn:stun.wiretrustee.com:3468",
Protocol: mgmtProto.HostConfig_UDP,
}
Expect(resp.WiretrusteeConfig.Signal).To(BeEquivalentTo(expectedSignalConfig))
Expect(resp.WiretrusteeConfig.Stuns).To(ConsistOf(expectedStunsConfig))
Expect(resp.WiretrusteeConfig.Turns).To(ConsistOf(expectedTurnsConfig))
// TURN validation is special because credentials are dynamically generated
Expect(resp.WiretrusteeConfig.Turns).To(HaveLen(1))
actualTURN := resp.WiretrusteeConfig.Turns[0]
Expect(len(actualTURN.User) > 0).To(BeTrue())
Expect(actualTURN.HostConfig).To(BeEquivalentTo(expectedTRUNHost))
})
})
@@ -368,7 +367,10 @@ var _ = Describe("Management service", func() {
resp := &mgmtProto.SyncResponse{}
err = pb.Unmarshal(decryptedBytes, resp)
Expect(err).NotTo(HaveOccurred())
wg.Done()
if len(resp.GetRemotePeers()) > 0 {
//only consider peer updates
wg.Done()
}
}
}()
}
@@ -388,7 +390,6 @@ var _ = Describe("Management service", func() {
err := syncClient.CloseSend()
Expect(err).NotTo(HaveOccurred())
}
})
})
})
@@ -486,12 +487,15 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) {
lis, err := net.Listen("tcp", ":0")
Expect(err).NotTo(HaveOccurred())
s := grpc.NewServer()
store, err := server.NewStore(config.Datadir)
if err != nil {
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
}
accountManager := server.NewManager(store)
mgmtServer, err := server.NewServer(config, accountManager)
peersUpdateManager := server.NewPeersUpdateManager()
accountManager := server.NewManager(store, peersUpdateManager)
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager)
Expect(err).NotTo(HaveOccurred())
mgmtProto.RegisterManagementServiceServer(s, mgmtServer)
go func() {

View File

@@ -1,6 +1,7 @@
package server
import (
"github.com/wiretrustee/wiretrustee/management/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"net"
@@ -55,11 +56,11 @@ func (p *Peer) Copy() *Peer {
}
//GetPeer returns a peer from a Store
func (manager *AccountManager) GetPeer(peerKey string) (*Peer, error) {
manager.mux.Lock()
defer manager.mux.Unlock()
func (am *AccountManager) GetPeer(peerKey string) (*Peer, error) {
am.mux.Lock()
defer am.mux.Unlock()
peer, err := manager.Store.GetPeer(peerKey)
peer, err := am.Store.GetPeer(peerKey)
if err != nil {
return nil, err
}
@@ -68,16 +69,16 @@ func (manager *AccountManager) GetPeer(peerKey string) (*Peer, error) {
}
//MarkPeerConnected marks peer as connected (true) or disconnected (false)
func (manager *AccountManager) MarkPeerConnected(peerKey string, connected bool) error {
manager.mux.Lock()
defer manager.mux.Unlock()
func (am *AccountManager) MarkPeerConnected(peerKey string, connected bool) error {
am.mux.Lock()
defer am.mux.Unlock()
peer, err := manager.Store.GetPeer(peerKey)
peer, err := am.Store.GetPeer(peerKey)
if err != nil {
return err
}
account, err := manager.Store.GetPeerAccount(peerKey)
account, err := am.Store.GetPeerAccount(peerKey)
if err != nil {
return err
}
@@ -85,7 +86,7 @@ func (manager *AccountManager) MarkPeerConnected(peerKey string, connected bool)
peerCopy := peer.Copy()
peerCopy.Status.LastSeen = time.Now()
peerCopy.Status.Connected = connected
err = manager.Store.SavePeer(account.Id, peerCopy)
err = am.Store.SavePeer(account.Id, peerCopy)
if err != nil {
return err
}
@@ -93,18 +94,18 @@ func (manager *AccountManager) MarkPeerConnected(peerKey string, connected bool)
}
//RenamePeer changes peer's name
func (manager *AccountManager) RenamePeer(accountId string, peerKey string, newName string) (*Peer, error) {
manager.mux.Lock()
defer manager.mux.Unlock()
func (am *AccountManager) RenamePeer(accountId string, peerKey string, newName string) (*Peer, error) {
am.mux.Lock()
defer am.mux.Unlock()
peer, err := manager.Store.GetPeer(peerKey)
peer, err := am.Store.GetPeer(peerKey)
if err != nil {
return nil, err
}
peerCopy := peer.Copy()
peerCopy.Name = newName
err = manager.Store.SavePeer(accountId, peerCopy)
err = am.Store.SavePeer(accountId, peerCopy)
if err != nil {
return nil, err
}
@@ -113,18 +114,60 @@ func (manager *AccountManager) RenamePeer(accountId string, peerKey string, newN
}
//DeletePeer removes peer from the account by it's IP
func (manager *AccountManager) DeletePeer(accountId string, peerKey string) (*Peer, error) {
manager.mux.Lock()
defer manager.mux.Unlock()
return manager.Store.DeletePeer(accountId, peerKey)
func (am *AccountManager) DeletePeer(accountId string, peerKey string) (*Peer, error) {
am.mux.Lock()
defer am.mux.Unlock()
peer, err := am.Store.DeletePeer(accountId, peerKey)
if err != nil {
return nil, err
}
err = am.peersUpdateManager.SendUpdate(peerKey,
&UpdateMessage{
Update: &proto.SyncResponse{
RemotePeers: []*proto.RemotePeerConfig{},
RemotePeersIsEmpty: true,
}})
if err != nil {
return nil, err
}
//notify other peers of the change
peers, err := am.Store.GetAccountPeers(accountId)
if err != nil {
return nil, err
}
for _, p := range peers {
peersToSend := []*Peer{}
for _, remote := range peers {
if p.Key != remote.Key {
peersToSend = append(peersToSend, remote)
}
}
update := toRemotePeerConfig(peersToSend)
err = am.peersUpdateManager.SendUpdate(p.Key,
&UpdateMessage{
Update: &proto.SyncResponse{
RemotePeers: update,
RemotePeersIsEmpty: len(update) == 0,
}})
if err != nil {
return nil, err
}
}
am.peersUpdateManager.CloseChannel(peerKey)
return peer, nil
}
//GetPeerByIP returns peer by it's IP
func (manager *AccountManager) GetPeerByIP(accountId string, peerIP string) (*Peer, error) {
manager.mux.Lock()
defer manager.mux.Unlock()
func (am *AccountManager) GetPeerByIP(accountId string, peerIP string) (*Peer, error) {
am.mux.Lock()
defer am.mux.Unlock()
account, err := manager.Store.GetAccount(accountId)
account, err := am.Store.GetAccount(accountId)
if err != nil {
return nil, status.Errorf(codes.NotFound, "account not found")
}
@@ -140,11 +183,11 @@ func (manager *AccountManager) GetPeerByIP(accountId string, peerIP string) (*Pe
// GetPeersForAPeer returns a list of peers available for a given peer (key)
// Effectively all the peers of the original peer's account except for the peer itself
func (manager *AccountManager) GetPeersForAPeer(peerKey string) ([]*Peer, error) {
manager.mux.Lock()
defer manager.mux.Unlock()
func (am *AccountManager) GetPeersForAPeer(peerKey string) ([]*Peer, error) {
am.mux.Lock()
defer am.mux.Unlock()
account, err := manager.Store.GetPeerAccount(peerKey)
account, err := am.Store.GetPeerAccount(peerKey)
if err != nil {
return nil, status.Errorf(codes.Internal, "Invalid peer key %s", peerKey)
}
@@ -165,9 +208,9 @@ func (manager *AccountManager) GetPeersForAPeer(peerKey string) ([]*Peer, error)
// Each new Peer will be assigned a new next net.IP from the Account.Network and Account.Network.LastIP will be updated (IP's are not reused).
// If the specified setupKey is empty then a new Account will be created //todo remove this part
// The peer property is just a placeholder for the Peer properties to pass further
func (manager *AccountManager) AddPeer(setupKey string, peer Peer) (*Peer, error) {
manager.mux.Lock()
defer manager.mux.Unlock()
func (am *AccountManager) AddPeer(setupKey string, peer Peer) (*Peer, error) {
am.mux.Lock()
defer am.mux.Unlock()
upperKey := strings.ToUpper(setupKey)
@@ -178,7 +221,7 @@ func (manager *AccountManager) AddPeer(setupKey string, peer Peer) (*Peer, error
// Empty setup key, create a new account for it.
account, sk = newAccount()
} else {
account, err = manager.Store.GetAccountBySetupKey(upperKey)
account, err = am.Store.GetAccountBySetupKey(upperKey)
if err != nil {
return nil, status.Errorf(codes.NotFound, "unknown setupKey %s", upperKey)
}
@@ -213,7 +256,7 @@ func (manager *AccountManager) AddPeer(setupKey string, peer Peer) (*Peer, error
account.Peers[newPeer.Key] = newPeer
account.SetupKeys[sk.Key] = sk.IncrementUsage()
err = manager.Store.SaveAccount(account)
err = am.Store.SaveAccount(account)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed adding peer")
}

View File

@@ -5,6 +5,7 @@ type Store interface {
DeletePeer(accountId string, peerKey string) (*Peer, error)
SavePeer(accountId string, peer *Peer) error
GetAccount(accountId string) (*Account, error)
GetAccountPeers(accountId string) ([]*Peer, error)
GetPeerAccount(peerKey string) (*Account, error)
GetAccountBySetupKey(setupKey string) (*Account, error)
SaveAccount(account *Account) error

View File

@@ -7,14 +7,19 @@
"Password": null
}
],
"Turns": [
{
"Proto": "udp",
"URI": "turn:stun.wiretrustee.com:3468",
"Username": "some_user",
"Password": "c29tZV9wYXNzd29yZA=="
}
],
"TURNConfig": {
"Turns": [
{
"Proto": "udp",
"URI": "turn:stun.wiretrustee.com:3468",
"Username": "some_user",
"Password": "some_password"
}
],
"CredentialsTTL": "1h",
"Secret": "c29tZV9wYXNzd29yZA==",
"TimeBasedCredentials": true
},
"Signal": {
"Proto": "http",
"URI": "signal.wiretrustee.com:10000",
@@ -23,11 +28,10 @@
},
"DataDir": "",
"HttpConfig": {
"LetsEncryptDomain": "",
"LetsEncryptDomain": "<PASTE YOUR LET'S ENCRYPT DOMAIN HERE>",
"Address": "0.0.0.0:3000",
"AuthDomain": "<PASTE YOUR AUTH0 DOMAIN HERE>",
"AuthClientId": "<PASTE YOUR AUTH0 CLIENT ID HERE>",
"AuthClientSecret": "<PASTE YOUR AUTH0 SECRET>",
"AuthCallback": "http://localhost:3000/callback"
"AuthIssuer": "<PASTE YOUR AUTH0 ISSUER HERE>,",
"AuthAudience": "<PASTE YOUR AUTH0 AUDIENCE HERE>",
"AuthKeysLocation": "<PASTE YOUR AUTH0 PUBLIC JWT KEYS LOCATION HERE>"
}
}

View File

@@ -0,0 +1,123 @@
package server
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
log "github.com/sirupsen/logrus"
"github.com/wiretrustee/wiretrustee/management/proto"
"sync"
"time"
)
//TURNCredentialsManager used to manage TURN credentials
type TURNCredentialsManager interface {
GenerateCredentials() TURNCredentials
SetupRefresh(peerKey string)
CancelRefresh(peerKey string)
}
//TimeBasedAuthSecretsManager generates credentials with TTL and using pre-shared secret known to TURN server
type TimeBasedAuthSecretsManager struct {
mux sync.Mutex
config *TURNConfig
updateManager *PeersUpdateManager
cancelMap map[string]chan struct{}
}
type TURNCredentials struct {
Username string
Password string
}
func NewTimeBasedAuthSecretsManager(updateManager *PeersUpdateManager, config *TURNConfig) *TimeBasedAuthSecretsManager {
return &TimeBasedAuthSecretsManager{
mux: sync.Mutex{},
config: config,
updateManager: updateManager,
cancelMap: make(map[string]chan struct{}),
}
}
//GenerateCredentials generates new time-based secret credentials - basically username is a unix timestamp and password is a HMAC hash of a timestamp with a preshared TURN secret
func (m *TimeBasedAuthSecretsManager) GenerateCredentials() TURNCredentials {
mac := hmac.New(sha1.New, []byte(m.config.Secret))
timeAuth := time.Now().Add(m.config.CredentialsTTL.Duration).Unix()
username := fmt.Sprint(timeAuth)
_, err := mac.Write([]byte(username))
if err != nil {
log.Errorln("Generating turn password failed with error: ", err)
}
bytePassword := mac.Sum(nil)
password := base64.StdEncoding.EncodeToString(bytePassword)
return TURNCredentials{
Username: username,
Password: password,
}
}
func (m *TimeBasedAuthSecretsManager) cancel(peerKey string) {
if channel, ok := m.cancelMap[peerKey]; ok {
close(channel)
delete(m.cancelMap, peerKey)
}
}
//CancelRefresh cancels scheduled peer credentials refresh
func (m *TimeBasedAuthSecretsManager) CancelRefresh(peerKey string) {
m.mux.Lock()
defer m.mux.Unlock()
m.cancel(peerKey)
}
//SetupRefresh starts peer credentials refresh. Since credentials are expiring (TTL) it is necessary to always generate them and send to the peer.
//A goroutine is created and put into TimeBasedAuthSecretsManager.cancelMap. This routine should be cancelled if peer is gone.
func (m *TimeBasedAuthSecretsManager) SetupRefresh(peerKey string) {
m.mux.Lock()
defer m.mux.Unlock()
m.cancel(peerKey)
cancel := make(chan struct{}, 1)
m.cancelMap[peerKey] = cancel
go func() {
for {
select {
case <-cancel:
return
default:
//we don't want to regenerate credentials right on expiration, so we do it slightly before (at 3/4 of TTL)
time.Sleep(m.config.CredentialsTTL.Duration / 4 * 3)
c := m.GenerateCredentials()
var turns []*proto.ProtectedHostConfig
for _, host := range m.config.Turns {
turns = append(turns, &proto.ProtectedHostConfig{
HostConfig: &proto.HostConfig{
Uri: host.URI,
Protocol: ToResponseProto(host.Proto),
},
User: c.Username,
Password: c.Password,
})
}
update := &proto.SyncResponse{
WiretrusteeConfig: &proto.WiretrusteeConfig{
Turns: turns,
},
}
err := m.updateManager.SendUpdate(peerKey, &UpdateMessage{Update: update})
if err != nil {
log.Errorf("error while sending TURN update to peer %s %v", peerKey, err)
// todo maybe continue trying?
}
}
}
}()
}

View File

@@ -0,0 +1,133 @@
package server
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"github.com/wiretrustee/wiretrustee/util"
"testing"
"time"
)
var TurnTestHost = &Host{
Proto: UDP,
URI: "turn:turn.wiretrustee.com:77777",
Username: "username",
Password: "",
}
func TestTimeBasedAuthSecretsManager_GenerateCredentials(t *testing.T) {
ttl := util.Duration{Duration: time.Hour}
secret := "some_secret"
peersManager := NewPeersUpdateManager()
tested := NewTimeBasedAuthSecretsManager(peersManager, &TURNConfig{
CredentialsTTL: ttl,
Secret: secret,
Turns: []*Host{TurnTestHost},
})
credentials := tested.GenerateCredentials()
if credentials.Username == "" {
t.Errorf("expected generated TURN username not to be empty, got empty")
}
if credentials.Password == "" {
t.Errorf("expected generated TURN password not to be empty, got empty")
}
validateMAC(credentials.Username, credentials.Password, []byte(secret), t)
}
func TestTimeBasedAuthSecretsManager_SetupRefresh(t *testing.T) {
ttl := util.Duration{Duration: 2 * time.Second}
secret := "some_secret"
peersManager := NewPeersUpdateManager()
peer := "some_peer"
updateChannel := peersManager.CreateChannel(peer)
tested := NewTimeBasedAuthSecretsManager(peersManager, &TURNConfig{
CredentialsTTL: ttl,
Secret: secret,
Turns: []*Host{TurnTestHost},
})
tested.SetupRefresh(peer)
if _, ok := tested.cancelMap[peer]; !ok {
t.Errorf("expecting peer to be present in a cancel map, got not present")
}
var updates []*UpdateMessage
loop:
for timeout := time.After(5 * time.Second); ; {
select {
case update := <-updateChannel:
updates = append(updates, update)
case <-timeout:
break loop
}
if len(updates) >= 2 {
break loop
}
}
if len(updates) < 2 {
t.Errorf("expecting 2 peer credentials updates, got %v", len(updates))
}
firstUpdate := updates[0].Update.GetWiretrusteeConfig().Turns[0]
secondUpdate := updates[1].Update.GetWiretrusteeConfig().Turns[0]
if firstUpdate.Password == secondUpdate.Password {
t.Errorf("expecting first credential update password %v to be diffeerent from second, got equal", firstUpdate.Password)
}
}
func TestTimeBasedAuthSecretsManager_CancelRefresh(t *testing.T) {
ttl := util.Duration{Duration: time.Hour}
secret := "some_secret"
peersManager := NewPeersUpdateManager()
peer := "some_peer"
tested := NewTimeBasedAuthSecretsManager(peersManager, &TURNConfig{
CredentialsTTL: ttl,
Secret: secret,
Turns: []*Host{TurnTestHost},
})
tested.SetupRefresh(peer)
if _, ok := tested.cancelMap[peer]; !ok {
t.Errorf("expecting peer to be present in a cancel map, got not present")
}
tested.CancelRefresh(peer)
if _, ok := tested.cancelMap[peer]; ok {
t.Errorf("expecting peer to be not present in a cancel map, got present")
}
}
func validateMAC(username string, actualMAC string, key []byte, t *testing.T) {
mac := hmac.New(sha1.New, key)
_, err := mac.Write([]byte(username))
if err != nil {
t.Fatal(err)
}
expectedMAC := mac.Sum(nil)
decodedMAC, err := base64.StdEncoding.DecodeString(actualMAC)
if err != nil {
t.Fatal(err)
}
equal := hmac.Equal(decodedMAC, expectedMAC)
if !equal {
t.Errorf("expected password MAC to be %s. got %s", expectedMAC, decodedMAC)
}
}

View File

@@ -0,0 +1,64 @@
package server
import (
log "github.com/sirupsen/logrus"
"github.com/wiretrustee/wiretrustee/management/proto"
"sync"
)
type UpdateMessage struct {
Update *proto.SyncResponse
}
type PeersUpdateManager struct {
peerChannels map[string]chan *UpdateMessage
channelsMux *sync.Mutex
}
// NewPeersUpdateManager returns a new instance of PeersUpdateManager
func NewPeersUpdateManager() *PeersUpdateManager {
return &PeersUpdateManager{
peerChannels: make(map[string]chan *UpdateMessage),
channelsMux: &sync.Mutex{},
}
}
// SendUpdate sends update message to the peer's channel
func (p *PeersUpdateManager) SendUpdate(peer string, update *UpdateMessage) error {
p.channelsMux.Lock()
defer p.channelsMux.Unlock()
if channel, ok := p.peerChannels[peer]; ok {
channel <- update
return nil
}
log.Debugf("peer %s has no channel", peer)
return nil
}
// CreateChannel creates a go channel for a given peer used to deliver updates relevant to the peer.
func (p *PeersUpdateManager) CreateChannel(peerKey string) chan *UpdateMessage {
p.channelsMux.Lock()
defer p.channelsMux.Unlock()
if channel, ok := p.peerChannels[peerKey]; ok {
delete(p.peerChannels, peerKey)
close(channel)
}
//mbragin: todo shouldn't it be more? or configurable?
channel := make(chan *UpdateMessage, 100)
p.peerChannels[peerKey] = channel
log.Debugf("opened updates channel for a peer %s", peerKey)
return channel
}
// CloseChannel closes updates channel of a given peer
func (p *PeersUpdateManager) CloseChannel(peerKey string) {
p.channelsMux.Lock()
defer p.channelsMux.Unlock()
if channel, ok := p.peerChannels[peerKey]; ok {
delete(p.peerChannels, peerKey)
close(channel)
}
log.Debugf("closed updates channel of a peer %s", peerKey)
}

View File

@@ -0,0 +1,49 @@
package server
import (
"github.com/wiretrustee/wiretrustee/management/proto"
"testing"
)
var peersUpdater *PeersUpdateManager
func TestCreateChannel(t *testing.T) {
peer := "test-create"
peersUpdater = NewPeersUpdateManager()
defer peersUpdater.CloseChannel(peer)
_ = peersUpdater.CreateChannel(peer)
if _, ok := peersUpdater.peerChannels[peer]; !ok {
t.Error("Error creating the channel")
}
}
func TestSendUpdate(t *testing.T) {
peer := "test-sendupdate"
update := &UpdateMessage{Update: &proto.SyncResponse{}}
_ = peersUpdater.CreateChannel(peer)
if _, ok := peersUpdater.peerChannels[peer]; !ok {
t.Error("Error creating the channel")
}
err := peersUpdater.SendUpdate(peer, update)
if err != nil {
t.Error("Error sending update: ", err)
}
select {
case <-peersUpdater.peerChannels[peer]:
default:
t.Error("Update wasn't send")
}
}
func TestCloseChannel(t *testing.T) {
peer := "test-close"
_ = peersUpdater.CreateChannel(peer)
if _, ok := peersUpdater.peerChannels[peer]; !ok {
t.Error("Error creating the channel")
}
peersUpdater.CloseChannel(peer)
if _, ok := peersUpdater.peerChannels[peer]; ok {
t.Error("Error closing the channel")
}
}

View File

@@ -1,3 +1,4 @@
FROM gcr.io/distroless/base:debug
ENTRYPOINT [ "/go/bin/wiretrustee-signal","run" ]
CMD ["--log-file", "console"]
COPY wiretrustee-signal /go/bin/wiretrustee-signal

View File

@@ -18,6 +18,7 @@ Flags:
Global Flags:
--log-level string (default "info")
--log-file string sets Wiretrustee log path. If console is specified the the log will be output to stdout (default "/var/log/wiretrustee/management.log")
```
## Running the Signal service (Docker)

View File

@@ -75,6 +75,19 @@ func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled boo
}, nil
}
//defaultBackoff is a basic backoff mechanism for general issues
func defaultBackoff() backoff.BackOff {
return &backoff.ExponentialBackOff{
InitialInterval: 800 * time.Millisecond,
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,
}
}
// Receive Connects to the Signal Exchange message stream and starts receiving messages.
// The messages will be handled by msgHandler function provided.
// This function runs a goroutine underneath and reconnects to the Signal Exchange if errors occur (e.g. Exchange restart)
@@ -83,15 +96,7 @@ func (c *Client) Receive(msgHandler func(msg *proto.Message) error) {
c.connWg.Add(1)
go func() {
var backOff = &backoff.ExponentialBackOff{
InitialInterval: backoff.DefaultInitialInterval,
RandomizationFactor: backoff.DefaultRandomizationFactor,
Multiplier: backoff.DefaultMultiplier,
MaxInterval: 3 * time.Second,
MaxElapsedTime: time.Duration(0), //never stop
Stop: backoff.Stop,
Clock: backoff.SystemClock,
}
var backOff = defaultBackoff()
operation := func() error {
err := c.connect(c.key.PublicKey().String(), msgHandler)

View File

@@ -2,10 +2,10 @@ package cmd
import (
"fmt"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"os"
"os/signal"
"runtime"
)
const (
@@ -14,7 +14,9 @@ const (
)
var (
logLevel string
logLevel string
defaultLogFile string
logFile string
rootCmd = &cobra.Command{
Use: "wiretrustee-signal",
@@ -33,10 +35,14 @@ func Execute() error {
func init() {
stopCh = make(chan int)
defaultLogFile = "/var/log/wiretrustee/signal.log"
if runtime.GOOS == "windows" {
defaultLogFile = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "signal.log"
}
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "")
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Wiretrustee log path. If console is specified the the log will be output to stdout")
rootCmd.AddCommand(runCmd)
InitLog(logLevel)
}
// SetupCloseHandler handles SIGTERM signal and exits with success
@@ -50,13 +56,3 @@ func SetupCloseHandler() {
}
}()
}
// InitLog parses and sets log-level input
func InitLog(logLevel string) {
level, err := log.ParseLevel(logLevel)
if err != nil {
log.Errorf("Failed parsing log-level %s: %s", logLevel, err)
os.Exit(ExitSetupFailed)
}
log.SetLevel(level)
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/wiretrustee/wiretrustee/encryption"
"github.com/wiretrustee/wiretrustee/signal/proto"
"github.com/wiretrustee/wiretrustee/signal/server"
"github.com/wiretrustee/wiretrustee/util"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
@@ -39,6 +40,10 @@ var (
Short: "start Wiretrustee Signal Server daemon",
Run: func(cmd *cobra.Command, args []string) {
flag.Parse()
err := util.InitLog(logLevel, logFile)
if err != nil {
log.Fatalf("failed initializing log %v", err)
}
var opts []grpc.ServerOption
if signalLetsencryptDomain != "" {

37
util/duration.go Normal file
View File

@@ -0,0 +1,37 @@
package util
import (
"encoding/json"
"errors"
"time"
)
//Duration is used strictly for JSON requests/responses due to duration marshalling issues
type Duration struct {
time.Duration
}
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case float64:
d.Duration = time.Duration(value)
return nil
case string:
var err error
d.Duration, err = time.ParseDuration(value)
if err != nil {
return err
}
return nil
default:
return errors.New("invalid duration")
}
}

39
util/log.go Normal file
View File

@@ -0,0 +1,39 @@
package util
import (
log "github.com/sirupsen/logrus"
"gopkg.in/natefinch/lumberjack.v2"
"io"
"path/filepath"
"time"
)
// InitLog parses and sets log-level input
func InitLog(logLevel string, logPath string) error {
level, err := log.ParseLevel(logLevel)
if err != nil {
log.Errorf("Failed parsing log-level %s: %s", logLevel, err)
return err
}
if logPath != "" && logPath != "console" {
lumberjackLogger := &lumberjack.Logger{
// Log file absolute path, os agnostic
Filename: filepath.ToSlash(logPath),
MaxSize: 5, // MB
MaxBackups: 10,
MaxAge: 30, // days
Compress: true,
}
log.SetOutput(io.Writer(lumberjackLogger))
}
logFormatter := new(log.TextFormatter)
logFormatter.TimestampFormat = time.RFC3339 // or RFC3339
logFormatter.FullTimestamp = true
log.SetFormatter(logFormatter)
log.SetLevel(level)
return nil
}