Compare commits

...

50 Commits

Author SHA1 Message Date
Maycon Santos
e4f9406d44 Removed installer and add workflow dispatch (#120) 2021-09-25 19:30:12 +02:00
braginini
7c79ff62ee fix: coturn port 2021-09-25 19:29:43 +02:00
Mikhail Bragin
32c369257b management/support cert from file (#122)
* feature: support cert file in management service

* docs: add new management commands
2021-09-25 19:22:49 +02:00
Mikhail Bragin
08dd719aa1 self-hosting guide (#121)
* docs: first steps of the self-hosting guide

* feature: add setup configurator for the self-hosted guide

* docs: add setup.env comments

* docs: simplify installation steps - support ./configure.sh

* docs: fix file references

* docs: fix minor docs issues

* docs: remove unused title
2021-09-25 19:12:05 +02:00
Mikhail Bragin
84c714dd93 Update quickstart.md 2021-09-23 14:39:55 +02:00
Mikhail Bragin
996c8d7c62 docs: referer to the new video 2021-09-23 14:38:51 +02:00
Mikhail Bragin
25e68ce493 docs: fix broken intro link 2021-09-22 14:18:48 +02:00
Mikhail Bragin
4881dcbd51 docs: add Getting Started hosted version guide (#119)
* docs: add Getting Started hosted version guide

* docs: fix screenshot sizes

* docs: self-hosting section

* docs: increase screenshots width

* docs: reference getting started from main readme

* docs: add refs to sections

* docs: move docs to a separate folder

* docs: add intro

* docs: correct intro docs

* docs: correct image location

* docs: correct language
2021-09-22 14:16:46 +02:00
Mikhail Bragin
d505f70972 Update README.md 2021-09-13 08:50:15 +02:00
Mikhail Bragin
6a80684378 docs: add slack 2021-09-13 08:18:18 +02:00
Mikhail Bragin
2624a7c4e6 docs: update Auth0 notes 2021-09-13 08:06:28 +02:00
Mikhail Bragin
9a412e7bf1 Update README.md 2021-09-13 07:58:52 +02:00
Mikhail Bragin
b5d1690129 Update README.md 2021-09-12 20:38:26 +02:00
Mikhail Bragin
d4bec15ca3 Update README.md 2021-09-12 20:37:55 +02:00
Mikhail Bragin
3212aca7c7 docs: add reference to auth0 react guide 2021-09-12 09:39:03 +03:00
Mikhail Bragin
b97a2251d3 fix docker compose signal volume 2021-09-12 09:08:55 +03:00
braginini
528a26ea3e chore: simplify direct connection logic 2021-09-09 16:43:52 +02:00
Mikhail Bragin
13288374f1 test: fix peer reflexive connectivity (#116) 2021-09-09 10:45:04 +02:00
Mikhail Bragin
ec759bc461 Delete peer (#114)
* feature: add peer deletion

* feature: add peer deletion [CLIENT]

* fix: lint error

* test: fix sync block

* test: fix management test

* feature: add client stop after was deleted

* chore: remove permission denied cancellation

* chore: add larger signal backoff

* feature: notify deleted peer of removal

* fix: lint issue

* chore: add 2nd default key - one off

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

* refactor: move InitLog to util lib

* docs: update signal and management docs

* chore: update docker compose

* set --log-file to console

* chore: comment out log volume in docker compose

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

* secret HOMEBREW_TAP_GITHUB_TOKEN

* prepare for pr

* use homebrew-client

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

* unpack function in local dir

* working-directory client

* using env var plugin

* test tag and publishing

* getting version from tag

* using version number

* remove unnecessary commands and add description

* using long version outputs

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

* fix: setup TURN credentials request only when refresh enabled

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

* chore: disable peer reflexive candidates in ICE

* chore: relocate management.json

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

* remove wip code

* refactor NewServer with Peer updates channel

* feature: add TURN credentials manager

* hmac logic

* example test function

* test: add TimeBasedAuthSecretsManager_GenerateCredentials  test

* test: make tests for now with hardcoded secret

* test: add TimeBasedAuthSecretsManager_SetupRefresh test

* test: add TimeBasedAuthSecretsManager_SetupRefresh test

* test: add TimeBasedAuthSecretsManager_CancelRefresh test

* feature: extract TURNConfig to the management config

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

* feature: make TURN time based secret credentials optional

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

* remove wip code

* refactor NewServer with Peer updates channel

* add PeersUpdateManager tests

* adding documentation

* using older version of linter

* verbose lint

* skip cache

* setup go version

* extra output

* configure fetch-depth

* exit 0

* skip-build-cache: true

* disabling failure for lint for now

* fix: darwin issue

* enable lint failure

* remove sock file for macOS

* refactor: remove tests interdependence

* fixed linux native iface

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

View File

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

View File

@@ -49,4 +49,15 @@ 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 }}
-
name: Trigger Windows binaries sign pipeline
uses: benc-uk/workflow-dispatch@v1
with:
workflow: Sign windows bin and installer
repo: wiretrustee/windows-sign-pipeline
ref: v0.0.1
token: ${{ secrets.SIGN_GITHUB_TOKEN }}
inputs: '{ "tag": "${{ github.ref }}" }'

4
.gitignore vendored
View File

@@ -3,4 +3,6 @@
dist/
.env
conf.json
http-cmds.sh
http-cmds.sh
infrastructure_files/management.json
infrastructure_files/docker-compose.yml

View File

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

View File

@@ -4,6 +4,15 @@ 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).
The number of peers is limited to 15.
Please don't use the hosted demonstration version for production purposes. We appreciate your usage of the demonstration version and will love to hear your feedback, please reach out via Issue or [Slack](https://wiretrustee.slack.com/).
[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.
@@ -20,7 +29,7 @@ A WireGuard®-based mesh network that connects your devices into a single privat
* Open-source (including Management Service)
### Secure peer-to-peer VPN in minutes
![animation](media/peers.gif)
![animation](docs/media/peers.gif)
### A bit on Wiretrustee internals
* Wiretrustee features a Management Service that offers peer IP management and network updates distribution (e.g. when new peer joins the network).
@@ -36,6 +45,10 @@ A WireGuard®-based mesh network that connects your devices into a single privat
- [Public Roadmap](https://github.com/wiretrustee/wiretrustee/projects/2)
- [Public Roadmap Progress Tracking](https://github.com/wiretrustee/wiretrustee/projects/1)
### Getting started
See [Docs](docs/README.md) for managed and self-hosting guides.
### Client Installation
#### Linux
1. Checkout Wiretrustee [releases](https://github.com/wiretrustee/wiretrustee/releases)
@@ -58,6 +71,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 +96,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 ).
@@ -146,8 +156,20 @@ netsh interface ip show config name="wt0"
4. Repeat on other machines.
### Running Management, Signal and Coturn
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.
### Running Dashboard, 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 in the management config file.
We chose Auth0 to "outsource" the user management part of our platform because we believe that implementing a proper user auth is not a trivial task and requires significant amount of time to make it right. We focused on connectivity instead.
It is worth mentioning that dependency to Auth0 is the only one that cannot be self-hosted.
Configuring Wiretrustee Auth0 integration:
- check [How to run](https://github.com/wiretrustee/wiretrustee-dashboard#how-to-run) to obtain Auth0 environment variables for UI Dashboard
- set these variables in the [environment section of the docker-compose file](https://github.com/wiretrustee/wiretrustee/blob/main/infrastructure_files/docker-compose.yml)
- check [Auth0 Golang API Guide](https://auth0.com/docs/quickstart/backend/golang) to obtain ```AuthIssuer```, ```AuthAudience```, and ```AuthKeysLocation```
- set these properties in the [management config files](https://github.com/wiretrustee/wiretrustee/blob/main/infrastructure_files/management.json#L33)
Under infrastructure_files we have a docker-compose example to run Dashboard, 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**.
The example is set to use the official images from Wiretrustee and Coturn, you can find our documentation to run the signal server in docker in [Running the Signal service](#running-the-signal-service), the management in [Management](./management/README.md), and the Coturn official documentation [here](https://hub.docker.com/r/coturn/coturn).

View File

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

View File

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

View File

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

View File

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

118
client/installer.nsis Normal file
View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

BIN
client/ui/banner.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
client/ui/wiretrustee.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

31
docs/README.md Normal file
View File

@@ -0,0 +1,31 @@
## Introduction
Wiretrustee is a WireGuard®-based platform that connects your devices securely into a peer-to-peer private network.
It simplifies VPN creation and management for your organization without the hassle of opening ports, complex firewall rules, and so forth.
It literally takes less than 5 minutes to provision a secure peer-to-peer VPN with Wiretrustee.
### High-level overview
In essence, Wiretrustee is an open source platform consisting of a collection of systems, responsible for handling peer-to-peer connections, tunneling and network management (IP, keys, ACLs, etc).
<p align="center">
<img src="media/high-level-dia.png" alt="high-level-dia" width="781"/>
</p>
Wiretrustee uses open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), [Coturn](https://github.com/coturn/coturn),
and [software](https://github.com/wiretrustee/wiretrustee) developed by Wiretrustee authors to make it all work together.
To learn more about Wiretrustee architecture, please refer to the [architecture section](../docs/architecture.md).
### Getting Started
There are 2 ways of getting started with Wiretrustee:
- use Cloud Managed version
- self-hosting
We recommend starting with the cloud managed version hosted at [beta.wiretrustee.com](https://beta.wiretrustee.com) - the quickest way to get familiar with the system.
See [Quickstart Guide](../docs/quickstart.md) for instructions.
If you don't want to use the managed version, check out our [Self-hosting Guide](../docs/self-hosting.md).

2
docs/architecture.md Normal file
View File

@@ -0,0 +1,2 @@
### Architecture
TODO

BIN
docs/media/add-peer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
docs/media/auth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
docs/media/empty-peers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 MiB

After

Width:  |  Height:  |  Size: 5.9 MiB

BIN
docs/media/peers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

41
docs/quickstart.md Normal file
View File

@@ -0,0 +1,41 @@
## Quickstart guide (Cloud Managed version)
Step-by-step video guide on YouTube:
[![IMAGE ALT TEXT](https://img.youtube.com/vi/cWTsGUJAUaU/0.jpg)](https://youtu.be/cWTsGUJAUaU "Wiretrustee - secure private network in less than 5 minutes")
This guide describes how to create secure VPN and connect 2 machines peer-to-peer.
One machine is a Raspberry Pi Compute Module 4 hosted at home (Peer A), and the other one is a regular Ubuntu server running in the Data Center (Peer B).
Both machines are running Linux (Raspbian and Ubuntu respectively), but you could also use Mac or Windows operating systems.
1. Sign-up at [https://beta.wiretrustee.com/](https://beta.wiretrustee.com/peers)
You can use your email and password to sign-up or any available social login option (e.g., GitHub account)
<img src="media/auth.png" alt="auth" width="350"/>
2. After a successful login you will be redirected to the ```Peers``` screen which is empty because you don't have any peers yet.
Click ```Add peer``` to add a new machine.
<img src="media/empty-peers.png" alt="empty-peers" width="700"/>
3. Choose a setup key which will be used to associate your new machine with your account (in our case it is ```Default key```).
Choose your machine operating system (in our case it is ```Linux```) and proceed with the installation steps on the machine.
<img src="media/add-peer.png" alt="add-peer" width="700"/>
4. Repeat #3 for the 2nd machine.
5. Return to ```Peers``` and you should notice 2 new machines with status ```Connected```
<img src="media/peers.png" alt="peers" width="700"/>
6. To test the connection you could try pinging devices:
On Peer A:
```ping 100.64.0.2```
On Peer B:
```ping 100.64.0.1```
7. Done! You now have a secure peer-to-peer VPN configured.

92
docs/self-hosting.md Normal file
View File

@@ -0,0 +1,92 @@
### Self-hosting
Wiretrustee is an open-source platform that can be self-hosted on your servers.
It relies on components developed by Wiretrustee Authors [Management Service](https://github.com/wiretrustee/wiretrustee/tree/main/management), [Management UI Dashboard](https://github.com/wiretrustee/wiretrustee-dashboard), [Signal Service](https://github.com/wiretrustee/wiretrustee/tree/main/signal),
a 3rd party open-source STUN/TURN service [Coturn](https://github.com/coturn/coturn) and a 3rd party service [Auth0](https://auth0.com/).
All the components can be self-hosted except for the Auth0 service.
We chose Auth0 to "outsource" the user management part of the platform because we believe that implementing a proper user auth requires significant amount of time to make it right.
We focused on connectivity instead.
If you would like to learn more about the architecture please refer to the [Wiretrustee Architecture section](architecture.md).
### Requirements
- Virtual machine offered by any cloud provider (e.g., AWS, DigitalOcean, Hetzner, Google Cloud, Azure ...).
- Any Linux OS.
- Docker Compose installed (see [Install Docker Compose](https://docs.docker.com/compose/install/)).
- Domain name pointing to the public IP address of your server.
- Open ports ```443, 33071, 33073, 3478``` (Dashboard, Management HTTP API, Management gRpc API, Coturn STUN/TURN respectively) on your server.
- Maybe a cup of coffee or tea :)
### Step-by-step guide
For this tutorial we will be using domain ```test.wiretrustee.com``` which points to our Ubuntu 20.04 machine hosted at Hetzner.
1. Create Auth0 account at [auth0.com](https://auth0.com/).
2. Login to your server, clone Wiretrustee repository:
```bash
git clone https://github.com/wiretrustee/wiretrustee.git wiretrustee/
```
and switch to the ```wiretrustee/infrastructure_files/``` folder that contains docker compose file:
```bash
cd wiretrustee/infrastructure_files/
```
3. Prepare configuration files.
To simplify the setup we have prepared a script to substitute required properties in the [docker-compose.yml.tmpl](../infrastructure_files/docker-compose.yml.tmpl) and [management.json.tmpl](../infrastructure_files/management.json.tmpl) files.
The [setup.env](../infrastructure_files/setup.env) file contains the following properties that have to be filled:
```bash
# e.g. app.mydomain.com
WIRETRUSTEE_DOMAIN=""
# e.g. dev-24vkclam.us.auth0.com
WIRETRUSTEE_AUTH0_DOMAIN=""
# e.g. 61u3JMXRO0oOevc7gCkZLCwePQvT4lL0
WIRETRUSTEE_AUTH0_CLIENT_ID=""
# e.g. https://app.mydomain.com/
WIRETRUSTEE_AUTH0_AUDIENCE=""
# e.g. hello@mydomain.com
WIRETRUSTEE_LETSENCRYPT_EMAIL=""
```
Please follow the steps to get the values.
4. Configure ```WIRETRUSTEE_AUTH0_DOMAIN``` ```WIRETRUSTEE_AUTH0_CLIENT_ID``` ```WIRETRUSTEE_AUTH0_AUDIENCE``` properties.
* To obtain these, please use [Auth0 React SDK Guide](https://auth0.com/docs/quickstart/spa/react/01-login#configure-auth0) up until "Install the Auth0 React SDK".
:grey_exclamation: Use ```https://YOUR DOMAIN``` as ````Allowed Callback URLs````, ```Allowed Logout URLs```, ```Allowed Web Origins``` and ```Allowed Origins (CORS)```
* set the variables in the ```setup.env```
5. Configure ```WIRETRUSTEE_AUTH0_AUDIENCE``` property.
* Check [Auth0 Golang API Guide](https://auth0.com/docs/quickstart/backend/golang) to obtain AuthAudience.
* set the property in the ```setup.env``` file.
6. Configure ```WIRETRUSTEE_LETSENCRYPT_EMAIL``` property.
This can be any email address. [Let's Encrypt](https://letsencrypt.org/) will create an account while creating a new domain.
7. Make sure all the properties set in the ```setup.env``` file and run:
```bash
./configure.sh
```
This will export all the properties as environment variables and generate ```docker-compose.yml``` and ```management.json``` files substituting required variables.
8. Run docker compose:
```bash
docker-compose up -d
```
9. Optionally check the logs by running:
```bash
docker-compose logs signal
docker-compose logs management
docker-compose logs coturn
docker-compose logs dashboard

2
go.mod
View File

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

5
go.sum
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
#!/bin/bash
unset $(grep -v '^#' ./setup.env | sed -E 's/(.*)=.*/\1/' | xargs)
export $(grep -v '^#' ./setup.env | xargs)
envsubst < docker-compose.yml.tmpl > docker-compose.yml
envsubst < management.json.tmpl > management.json

View File

@@ -1,38 +0,0 @@
version: "3"
services:
# Signal
signal:
image: wiretrustee/signal:latest
restart: unless-stopped
volumes:
- wiretrustee-mgmt:/var/lib/wiretrustee
ports:
- 10000:10000
# # port and command for Let's Encrypt validation
# - 443:443
# command: ["--letsencrypt-domain", "<YOUR-DOMAIN>"]
# Management
management:
image: wiretrustee/management:latest
restart: unless-stopped
volumes:
- wiretrustee-mgmt:/var/lib/wiretrustee
- ./config.json:/etc/wiretrustee/config.json
ports:
- 33073:33073
# # port and command for Let's Encrypt validation
# - 443:443
# command: ["--letsencrypt-domain", "<YOUR-DOMAIN>"]
# Coturn
coturn:
image: coturn/coturn
restart: unless-stopped
domainname: stun.wiretrustee.com
volumes:
- ./turnserver.conf:/etc/turnserver.conf:ro
# - ./privkey.pem:/etc/coturn/private/privkey.pem:ro
# - ./cert.pem:/etc/coturn/certs/cert.pem:ro
network_mode: host
volumes:
wiretrustee-mgmt:
wiretrustee-signal:

View File

@@ -0,0 +1,62 @@
version: "3"
services:
#UI dashboard
dashboard:
image: wiretrustee/dashboard:main
restart: unless-stopped
ports:
- 80:80
- 443:443
environment:
- AUTH0_DOMAIN=$WIRETRUSTEE_AUTH0_DOMAIN
- AUTH0_CLIENT_ID=$WIRETRUSTEE_AUTH0_CLIENT_ID
- AUTH0_AUDIENCE=$WIRETRUSTEE_AUTH0_AUDIENCE
- WIRETRUSTEE_MGMT_API_ENDPOINT=https://$WIRETRUSTEE_DOMAIN:33071
- NGINX_SSL_PORT=443
- LETSENCRYPT_DOMAIN=$WIRETRUSTEE_DOMAIN
- LETSENCRYPT_EMAIL=$WIRETRUSTEE_LETSENCRYPT_EMAIL
volumes:
- /var/lib/wiretrustee/dashboard/letsencrypt:/etc/letsencrypt/
# Signal
signal:
image: wiretrustee/signal:latest
restart: unless-stopped
volumes:
- wiretrustee-signal:/var/lib/wiretrustee
# - /var/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", "$WIRETRUSTEE_DOMAIN", "--log-file", "console"]
# Management
management:
# image: wiretrustee/management:latest
image: wiretrustee/management:v0.1.0-beta.2-SNAPSHOT-39d450b-amd64
restart: unless-stopped
depends_on:
- dashboard
volumes:
- wiretrustee-mgmt:/var/lib/wiretrustee
- /var/lib/wiretrustee/dashboard/letsencrypt:/etc/letsencrypt:ro
- ./management.json:/etc/wiretrustee/management.json
# - /var/log/wiretrustee/management.log:/var/log/wiretrustee/management.log
ports:
- 33073:33073 #gRPC port
- 33071:33071 #HTTP port
# # port and command for Let's Encrypt validation
# - 443:443
# command: ["--letsencrypt-domain", "$WIRETRUSTEE_DOMAIN", "--log-file", "console"]
# Coturn
coturn:
image: coturn/coturn
restart: unless-stopped
domainname: <YOUR DOMAIN>
volumes:
- ./turnserver.conf:/etc/turnserver.conf:ro
# - ./privkey.pem:/etc/coturn/private/privkey.pem:ro
# - ./cert.pem:/etc/coturn/certs/cert.pem:ro
network_mode: host
volumes:
wiretrustee-mgmt:
wiretrustee-signal:

View File

@@ -0,0 +1,39 @@
{
"Stuns": [
{
"Proto": "udp",
"URI": "stun:$WIRETRUSTEE_DOMAIN:3478",
"Username": "",
"Password": null
}
],
"TURNConfig": {
"Turns": [
{
"Proto": "udp",
"URI": "turn:$WIRETRUSTEE_DOMAIN:3478",
"Username": "",
"Password": null
}
],
"CredentialsTTL": "12h",
"Secret": "secret",
"TimeBasedCredentials": false
},
"Signal": {
"Proto": "http",
"URI": "$WIRETRUSTEE_DOMAIN:10000",
"Username": "",
"Password": null
},
"Datadir": "",
"HttpConfig": {
"LetsEncryptDomain": "",
"CertFile":"/etc/letsencrypt/live/$WIRETRUSTEE_DOMAIN/fullchain.pem",
"CertKey":"/etc/letsencrypt/live/$WIRETRUSTEE_DOMAIN/privkey.pem",
"Address": "0.0.0.0:33071",
"AuthIssuer": "https://$WIRETRUSTEE_AUTH0_DOMAIN/",
"AuthAudience": "$WIRETRUSTEE_AUTH0_AUDIENCE",
"AuthKeysLocation": "https://$WIRETRUSTEE_AUTH0_DOMAIN/.well-known/jwks.json"
}
}

View File

@@ -0,0 +1,10 @@
# e.g. app.mydomain.com
WIRETRUSTEE_DOMAIN=""
# e.g. dev-24vkclam.us.auth0.com
WIRETRUSTEE_AUTH0_DOMAIN=""
# e.g. 61u3JMXRO0oOevc7gCkZLCwePQvT4lL0
WIRETRUSTEE_AUTH0_CLIENT_ID=""
# e.g. https://app.mydomain.com/
WIRETRUSTEE_AUTH0_AUDIENCE=""
# e.g. hello@mydomain.com
WIRETRUSTEE_LETSENCRYPT_EMAIL=""

View File

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

View File

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

View File

@@ -14,10 +14,12 @@ Flags:
-h, --help help for management
--letsencrypt-domain string a domain to issue Let's Encrypt certificate for. Enables TLS using Let's Encrypt. Will fetch and renew certificate, and run the server with TLS
--port int server port to listen on (default 33073)
--cert-file string Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect
--cert-key string Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect
Global Flags:
--config string Wiretrustee config file location to write new config to (default "/etc/wiretrustee/config.json")
--log-level string (default "info")
--log-file string sets Wiretrustee log path. If console is specified the the log will be output to stdout (default "/var/log/wiretrustee/management.log")
```
## Run Management service (Docker)

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package cmd
import (
"context"
"crypto/tls"
"flag"
"fmt"
"github.com/wiretrustee/wiretrustee/management/server"
@@ -25,6 +26,8 @@ var (
mgmtDataDir string
mgmtConfig string
mgmtLetsencryptDomain string
certFile string
certKey string
kaep = keepalive.EnforcementPolicy{
MinTime: 15 * time.Second,
@@ -43,6 +46,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,25 +67,37 @@ 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
var httpServer *http.Server
if config.HttpConfig.LetsEncryptDomain != "" {
//automatically generate a new certificate with Let's Encrypt
certManager := encryption.CreateCertManager(config.Datadir, config.HttpConfig.LetsEncryptDomain)
transportCredentials := credentials.NewTLS(certManager.TLSConfig())
opts = append(opts, grpc.Creds(transportCredentials))
httpServer = http.NewHttpsServer(config.HttpConfig, certManager, accountManager)
} else if config.HttpConfig.CertFile != "" && config.HttpConfig.CertKey != "" {
//use provided certificate
tlsConfig, err := loadTLSConfig(config.HttpConfig.CertFile, config.HttpConfig.CertKey)
if err != nil {
log.Fatal("cannot load TLS credentials: ", err)
}
transportCredentials := credentials.NewTLS(tlsConfig)
opts = append(opts, grpc.Creds(transportCredentials))
httpServer = http.NewHttpsServerWithTLSConfig(config.HttpConfig, tlsConfig, accountManager)
} else {
//start server without SSL
httpServer = http.NewHttpServer(config.HttpConfig, accountManager)
}
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)
}
@@ -131,14 +150,37 @@ func loadConfig() (*server.Config, error) {
config.Datadir = mgmtDataDir
}
if certKey != "" && certFile != "" {
config.HttpConfig.CertFile = certFile
config.HttpConfig.CertKey = certKey
}
return config, err
}
func loadTLSConfig(certFile string, certKey string) (*tls.Config, error) {
// Load server's certificate and private key
serverCert, err := tls.LoadX509KeyPair(certFile, certKey)
if err != nil {
return nil, err
}
// Create the credentials and return it
config := &tls.Config{
Certificates: []tls.Certificate{serverCert},
ClientAuth: tls.NoClientCert,
}
return config, nil
}
func init() {
mgmtCmd.Flags().IntVar(&mgmtPort, "port", 33073, "server port to listen on")
mgmtCmd.Flags().StringVar(&mgmtDataDir, "datadir", "/var/lib/wiretrustee/", "server data directory location")
mgmtCmd.Flags().StringVar(&mgmtConfig, "config", "/etc/wiretrustee/management.json", "Wiretrustee config file location. Config params specified via command line (e.g. datadir) have a precedence over configuration from this file")
mgmtCmd.Flags().StringVar(&mgmtLetsencryptDomain, "letsencrypt-domain", "", "a domain to issue Let's Encrypt certificate for. Enables TLS using Let's Encrypt. Will fetch and renew certificate, and run the server with TLS")
mgmtCmd.Flags().StringVar(&certFile, "cert-file", "", "Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
mgmtCmd.Flags().StringVar(&certKey, "cert-key", "", "Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
rootCmd.MarkFlagRequired("config") //nolint

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,9 @@
package server
import (
"github.com/wiretrustee/wiretrustee/util"
)
type Protocol string
const (
@@ -12,19 +16,31 @@ 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
Address string
//CertFile is the location of the certificate
CertFile string
//CertKey is the location of the certificate private key
CertKey string
Address string
// AuthAudience identifies the recipients that the JWT is intended for (aud in JWT)
AuthAudience string
// AuthIssuer identifies principal that issued the JWT.
@@ -39,5 +55,5 @@ type Host struct {
// URI e.g. turns://stun.wiretrustee.com:4430 or signal.wiretrustee.com:10000
URI string
Username string
Password []byte
Password string
}

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package http
import (
"context"
"crypto/tls"
"github.com/gorilla/mux"
"github.com/rs/cors"
log "github.com/sirupsen/logrus"
@@ -17,10 +18,11 @@ type Server struct {
server *http.Server
config *s.HttpServerConfig
certManager *autocert.Manager
tlsConfig *tls.Config
accountManager *s.AccountManager
}
// NewHttpsServer creates a new HTTPs server (with HTTPS support)
// NewHttpsServer creates a new HTTPs server (with HTTPS support) and a certManager that is responsible for generating and renewing Let's Encrypt certificate
// The listening address will be :443 no matter what was specified in s.HttpServerConfig.Address
func NewHttpsServer(config *s.HttpServerConfig, certManager *autocert.Manager, accountManager *s.AccountManager) *Server {
server := &http.Server{
@@ -32,6 +34,18 @@ func NewHttpsServer(config *s.HttpServerConfig, certManager *autocert.Manager, a
return &Server{server: server, config: config, certManager: certManager, accountManager: accountManager}
}
// NewHttpsServerWithTLSConfig creates a new HTTPs server with a provided tls.Config.
// Usually used when you already have a certificate
func NewHttpsServerWithTLSConfig(config *s.HttpServerConfig, tlsConfig *tls.Config, accountManager *s.AccountManager) *Server {
server := &http.Server{
Addr: config.Address,
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
}
return &Server{server: server, config: config, tlsConfig: tlsConfig, accountManager: accountManager}
}
// NewHttpServer creates a new HTTP server (without HTTPS)
func NewHttpServer(config *s.HttpServerConfig, accountManager *s.AccountManager) *Server {
return NewHttpsServer(config, nil, accountManager)
@@ -71,13 +85,26 @@ func (s *Server) Start() error {
if s.certManager != nil {
// if HTTPS is enabled we reuse the listener from the cert manager
listener := s.certManager.Listener()
log.Infof("http server listening on %s", listener.Addr())
log.Infof("HTTPs server listening on %s with Let's Encrypt autocert configured", listener.Addr())
if err = http.Serve(listener, s.certManager.HTTPHandler(r)); err != nil {
log.Errorf("failed to serve https server: %v", err)
return err
}
} else if s.tlsConfig != nil {
listener, err := tls.Listen("tcp", s.config.Address, s.tlsConfig)
if err != nil {
log.Errorf("failed to serve https server: %v", err)
return err
}
log.Infof("HTTPs server listening on %s", listener.Addr())
if err = http.Serve(listener, r); err != nil {
log.Errorf("failed to serve https server: %v", err)
return err
}
} else {
log.Infof("http server listening on %s", s.server.Addr)
log.Infof("HTTP server listening on %s", s.server.Addr)
if err = s.server.ListenAndServe(); err != nil {
log.Errorf("failed to serve http server: %v", err)
return err

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

37
util/duration.go Normal file
View File

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

39
util/log.go Normal file
View File

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