mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-16 15:26:40 +00:00
Compare commits
34 Commits
v0.1.0-rc-
...
v0.1.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
528a26ea3e | ||
|
|
13288374f1 | ||
|
|
ec759bc461 | ||
|
|
a859f6c511 | ||
|
|
081162864d | ||
|
|
090f3ae5d0 | ||
|
|
fb1116e77b | ||
|
|
879750af7c | ||
|
|
13b4be31df | ||
|
|
15f7d856db | ||
|
|
72197d1970 | ||
|
|
a56aba8b06 | ||
|
|
4acbdc47e5 | ||
|
|
ee3c292699 | ||
|
|
6c233fcc3f | ||
|
|
a4db0b4e94 | ||
|
|
81c5aa1341 | ||
|
|
8c5f6186f1 | ||
|
|
88e9d2c20d | ||
|
|
9d76cf1ea7 | ||
|
|
737d4b5f2c | ||
|
|
4485124b67 | ||
|
|
b17424d630 | ||
|
|
86f3b1e5c8 | ||
|
|
a31cbb1f5b | ||
|
|
4f4edf8442 | ||
|
|
a78e518327 | ||
|
|
2c1d7c0fd4 | ||
|
|
593c66fea6 | ||
|
|
5f8211773d | ||
|
|
64ca05c8e7 | ||
|
|
307d41c08a | ||
|
|
5c7260298f | ||
|
|
7f7858b0a6 |
4
.github/workflows/golangci-lint.yml
vendored
4
.github/workflows/golangci-lint.yml
vendored
@@ -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
|
||||
|
||||
|
||||
|
||||
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
@@ -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 }}
|
||||
@@ -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"
|
||||
36
README.md
36
README.md
@@ -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**.
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
118
client/installer.nsis
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
14
client/system/info.go
Normal 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
|
||||
}
|
||||
39
client/system/info_darwin.go
Normal file
39
client/system/info_darwin.go
Normal 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()
|
||||
}
|
||||
39
client/system/info_freebsd.go
Normal file
39
client/system/info_freebsd.go
Normal 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()
|
||||
}
|
||||
76
client/system/info_linux.go
Normal file
76
client/system/info_linux.go
Normal 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()
|
||||
}
|
||||
35
client/system/info_windows.go
Normal file
35
client/system/info_windows.go
Normal 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
|
||||
}
|
||||
32
client/testdata/management.json
vendored
32
client/testdata/management.json
vendored
@@ -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
BIN
client/ui/banner.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
client/ui/wiretrustee.ico
Normal file
BIN
client/ui/wiretrustee.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
2
go.mod
2
go.mod
@@ -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
5
go.sum
@@ -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=
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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/"
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
37
infrastructure_files/management.json
Normal file
37
infrastructure_files/management.json
Normal 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>"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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: "",
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
30
management/server/testdata/management.json
vendored
30
management/server/testdata/management.json
vendored
@@ -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>"
|
||||
}
|
||||
}
|
||||
123
management/server/turncredentials.go
Normal file
123
management/server/turncredentials.go
Normal 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?
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
133
management/server/turncredentials_test.go
Normal file
133
management/server/turncredentials_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
64
management/server/updatechannel.go
Normal file
64
management/server/updatechannel.go
Normal 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)
|
||||
}
|
||||
49
management/server/updatechannel_test.go
Normal file
49
management/server/updatechannel_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
37
util/duration.go
Normal 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
39
util/log.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user