mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-16 15:26:40 +00:00
Compare commits
24 Commits
v0.3.4
...
revert-283
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c84ba73fb | ||
|
|
9ec4ea8e03 | ||
|
|
9c3cd1a5db | ||
|
|
347a668bd5 | ||
|
|
ef47385e38 | ||
|
|
3e46f38166 | ||
|
|
64e2e34dae | ||
|
|
8dd92f14bf | ||
|
|
071b03e790 | ||
|
|
3385ea6379 | ||
|
|
430e0415df | ||
|
|
b72ed91cb4 | ||
|
|
0b8387bd2c | ||
|
|
5d4c2643a3 | ||
|
|
69cda73bbb | ||
|
|
b29948b910 | ||
|
|
5f5cbf7e20 | ||
|
|
41c6af6b6f | ||
|
|
23fad49756 | ||
|
|
5546eba36a | ||
|
|
60a9da734f | ||
|
|
852c7c50c0 | ||
|
|
1c2c1a876b | ||
|
|
e5dcd4753e |
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -64,6 +64,6 @@ jobs:
|
||||
with:
|
||||
workflow: Sign windows bin and installer
|
||||
repo: wiretrustee/windows-sign-pipeline
|
||||
ref: v0.0.1
|
||||
ref: v0.0.2
|
||||
token: ${{ secrets.SIGN_GITHUB_TOKEN }}
|
||||
inputs: '{ "tag": "${{ github.ref }}" }'
|
||||
inputs: '{ "tag": "${{ github.ref }}" }'
|
||||
|
||||
59
README.md
59
README.md
@@ -5,12 +5,26 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/license-BSD--3-blue" />
|
||||
<img src="https://img.shields.io/docker/pulls/wiretrustee/management" />
|
||||
<a href="https://github.com/wiretrustee/wiretrustee/blob/main/LICENSE">
|
||||
<img src="https://img.shields.io/badge/license-BSD--3-blue" />
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/wiretrustee/wiretrustee/tags">
|
||||
<img src="https://img.shields.io/docker/pulls/wiretrustee/wiretrustee" />
|
||||
</a>
|
||||
<img src="https://badgen.net/badge/Open%20Source%3F/Yes%21/blue?icon=github" />
|
||||
<br>
|
||||
<a href="https://www.codacy.com/gh/wiretrustee/wiretrustee/dashboard?utm_source=github.com&utm_medium=referral&utm_content=wiretrustee/wiretrustee&utm_campaign=Badge_Grade"><img src="https://app.codacy.com/project/badge/Grade/d366de2c9d8b4cf982da27f8f5831809"/></a>
|
||||
<a href="https://goreportcard.com/report/wiretrustee/wiretrustee">
|
||||
<img src="https://goreportcard.com/badge/github.com/wiretrustee/wiretrustee?style=flat-square" />
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://join.slack.com/t/wiretrustee/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A">
|
||||
<img src="https://img.shields.io/badge/slack-@wiretrustee-red.svg?logo=slack"/>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<p align="center">
|
||||
<strong>
|
||||
Start using Wiretrustee at <a href="https://app.wiretrustee.com/">app.wiretrustee.com</a>
|
||||
@@ -179,46 +193,7 @@ For **Windows** systems:
|
||||
3. Repeat on other machines.
|
||||
|
||||
### 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 a significant amount of time to make it right. We focused on connectivity instead.
|
||||
It is worth mentioning that the dependency on 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).
|
||||
|
||||
> Run Coturn at your own risk, we are just providing an example, be sure to follow security best practices and to configure proper credentials as this service can be exploited and you may face large data transfer charges.
|
||||
|
||||
Also, if you have an SSL certificate for Coturn, you can modify the docker-compose.yml file to point to its files in your host machine and then switch the domain name to your own SSL domain. If you don't already have an SSL certificate, you can follow [Certbot's](https://certbot.eff.org/docs/intro.html) official documentation
|
||||
to generate one from [Let’s Encrypt](https://letsencrypt.org/), or, we found that the example provided by [BigBlueButton](https://docs.bigbluebutton.org/2.2/setup-turn-server.html#generating-tls-certificates) covers the basics to configure Coturn with Let's Encrypt certs.
|
||||
> The Wiretrustee Management service can generate and maintain the certificates automatically, all you need to do is run the service in a host with a public IP, configure a valid DNS record pointing to that IP and uncomment the 443 ports and command lines in the docker-compose.yml file.
|
||||
|
||||
Simple docker-composer execution:
|
||||
````shell
|
||||
cd infrastructure_files
|
||||
docker-compose up -d
|
||||
````
|
||||
You can check logs by running:
|
||||
````shell
|
||||
cd infrastructure_files
|
||||
docker-compose logs signal
|
||||
docker-compose logs management
|
||||
docker-compose logs coturn
|
||||
````
|
||||
If you need to stop the services, run the following:
|
||||
````shell
|
||||
cd infrastructure_files
|
||||
docker-compose down
|
||||
````
|
||||
See [Self-Hosting Guide](https://docs.wiretrustee.com/getting-started/self-hosting)
|
||||
|
||||
|
||||
### Legal
|
||||
|
||||
37
client/cmd/down.go
Normal file
37
client/cmd/down.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/client/proto"
|
||||
)
|
||||
|
||||
var downCmd = &cobra.Command{
|
||||
Use: "down",
|
||||
Short: "down wiretrustee connections",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
|
||||
defer cancel()
|
||||
|
||||
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
||||
if err != nil {
|
||||
log.Errorf("failed to connect to service CLI interface %v", err)
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
daemonClient := proto.NewDaemonServiceClient(conn)
|
||||
|
||||
if _, err := daemonClient.Down(ctx, &proto.DownRequest{}); err != nil {
|
||||
log.Errorf("call service down method: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -1,175 +1,66 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
"github.com/wiretrustee/wiretrustee/client/system"
|
||||
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"
|
||||
"github.com/wiretrustee/wiretrustee/client/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
loginCmd = &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "login to the Wiretrustee Management Service (first run)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars()
|
||||
var loginCmd = &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "login to the Wiretrustee Management Service (first run)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars()
|
||||
ctx := internal.CtxInitState(context.Background())
|
||||
|
||||
var backOff = &backoff.ExponentialBackOff{
|
||||
InitialInterval: time.Second,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: 2 * time.Second,
|
||||
MaxElapsedTime: time.Second * 10,
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
|
||||
loginOp := func() error {
|
||||
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := internal.GetConfig(managementURL, configPath, preSharedKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed getting config %s %v", configPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
//validate our peer's Wireguard PRIVATE key
|
||||
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
mgmTlsEnabled := false
|
||||
if config.ManagementURL.Scheme == "https" {
|
||||
mgmTlsEnabled = true
|
||||
}
|
||||
|
||||
log.Debugf("connecting to Management Service %s", config.ManagementURL.String())
|
||||
mgmClient, err := mgm.NewClient(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||
if err != nil {
|
||||
log.Errorf("failed connecting to Management Service %s %v", config.ManagementURL.String(), err)
|
||||
return err
|
||||
}
|
||||
log.Debugf("connected to management Service %s", config.ManagementURL.String())
|
||||
|
||||
serverKey, err := mgmClient.GetServerPublicKey()
|
||||
if err != nil {
|
||||
log.Errorf("failed while getting Management Service public key: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = loginPeer(*serverKey, mgmClient, setupKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed logging-in peer on Management Service : %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = mgmClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Management Service client: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := backoff.RetryNotify(loginOp, backOff, func(err error, duration time.Duration) {
|
||||
log.Warnf("retrying Login to the Management service in %v due to error %v", duration, err)
|
||||
})
|
||||
// workaround to run without service
|
||||
if logFile == "console" {
|
||||
config, err := internal.GetConfig(managementURL, configPath, preSharedKey)
|
||||
if err != nil {
|
||||
log.Errorf("exiting login retry loop due to unrecoverable error: %v", err)
|
||||
log.Errorf("get config file: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// loginPeer attempts to login to Management Service. If peer wasn't registered, tries the registration flow.
|
||||
func loginPeer(serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string) (*mgmProto.LoginResponse, error) {
|
||||
|
||||
loginResp, err := client.Login(serverPublicKey)
|
||||
if err != nil {
|
||||
if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
||||
log.Debugf("peer registration required")
|
||||
return registerPeer(serverPublicKey, client, setupKey)
|
||||
} else {
|
||||
return nil, err
|
||||
err = WithBackOff(func() error {
|
||||
return internal.Login(ctx, config, setupKey)
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("backoff cycle failed: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("peer has successfully logged-in to Management Service")
|
||||
if setupKey == "" {
|
||||
log.Error("setup key can't be empty")
|
||||
return fmt.Errorf("empty setup key")
|
||||
}
|
||||
|
||||
return loginResp, nil
|
||||
}
|
||||
|
||||
// registerPeer checks whether setupKey was provided via cmd line and if not then it prompts user to enter a key.
|
||||
// Otherwise tries to register with the provided setupKey via command line.
|
||||
func registerPeer(serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string) (*mgmProto.LoginResponse, error) {
|
||||
|
||||
var err error
|
||||
if setupKey == "" {
|
||||
setupKey, err = promptPeerSetupKey()
|
||||
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
||||
if err != nil {
|
||||
log.Errorf("failed getting setup key from user: %s", err)
|
||||
return nil, err
|
||||
log.Errorf("failed to connect to service CLI interface %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
validSetupKey, err := uuid.Parse(setupKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("sending peer registration request to Management Service")
|
||||
info := system.GetInfo()
|
||||
loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), info)
|
||||
if err != nil {
|
||||
log.Errorf("failed registering peer %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("peer has been successfully registered on Management Service")
|
||||
|
||||
return loginResp, nil
|
||||
}
|
||||
|
||||
// promptPeerSetupKey prompts user to enter Setup Key
|
||||
func promptPeerSetupKey() (string, error) {
|
||||
fmt.Print("Enter setup key: ")
|
||||
|
||||
s := bufio.NewScanner(os.Stdin)
|
||||
for s.Scan() {
|
||||
input := s.Text()
|
||||
if input != "" {
|
||||
return input, nil
|
||||
request := proto.LoginRequest{
|
||||
SetupKey: setupKey,
|
||||
PresharedKey: preSharedKey,
|
||||
ManagementUrl: managementURL,
|
||||
}
|
||||
fmt.Println("Specified key is empty, try again:")
|
||||
|
||||
}
|
||||
|
||||
return "", s.Err()
|
||||
client := proto.NewDaemonServiceClient(conn)
|
||||
err = WithBackOff(func() error {
|
||||
if _, err := client.Login(ctx, &request); err != nil {
|
||||
log.Errorf("try login: %v", err)
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("backoff cycle failed: %v", err)
|
||||
}
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var mgmAddr string
|
||||
@@ -25,12 +26,11 @@ func TestLogin_Start(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, listener := startManagement(config, t)
|
||||
_, listener := startManagement(t, config)
|
||||
mgmAddr = listener.Addr().String()
|
||||
}
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
|
||||
tempDir := t.TempDir()
|
||||
confPath := tempDir + "/config.json"
|
||||
mgmtURL := fmt.Sprintf("http://%s", mgmAddr)
|
||||
@@ -38,6 +38,8 @@ func TestLogin(t *testing.T) {
|
||||
"login",
|
||||
"--config",
|
||||
confPath,
|
||||
"--log-file",
|
||||
"console",
|
||||
"--setup-key",
|
||||
strings.ToUpper("a2c8e62b-38f5-4553-b31e-dd66c696cebb"),
|
||||
"--management-url",
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -19,6 +26,7 @@ var (
|
||||
logLevel string
|
||||
defaultLogFile string
|
||||
logFile string
|
||||
daemonAddr string
|
||||
managementURL string
|
||||
setupKey string
|
||||
preSharedKey string
|
||||
@@ -37,8 +45,8 @@ var (
|
||||
func Execute() error {
|
||||
return rootCmd.Execute()
|
||||
}
|
||||
func init() {
|
||||
|
||||
func init() {
|
||||
stopCh = make(chan int)
|
||||
cleanupCh = make(chan struct{})
|
||||
|
||||
@@ -49,6 +57,11 @@ func init() {
|
||||
defaultLogFile = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "client.log"
|
||||
}
|
||||
|
||||
defaultDaemonAddr := "unix:///var/run/wiretrustee.sock"
|
||||
if runtime.GOOS == "windows" {
|
||||
defaultDaemonAddr = "tcp://127.0.0.1:41731"
|
||||
}
|
||||
rootCmd.PersistentFlags().StringVar(&daemonAddr, "daemon-addr", defaultDaemonAddr, "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
|
||||
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")
|
||||
@@ -57,6 +70,8 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringVar(&preSharedKey, "preshared-key", "", "Sets Wireguard PreSharedKey property. If set, then only peers that have the same key can communicate.")
|
||||
rootCmd.AddCommand(serviceCmd)
|
||||
rootCmd.AddCommand(upCmd)
|
||||
rootCmd.AddCommand(downCmd)
|
||||
rootCmd.AddCommand(statusCmd)
|
||||
rootCmd.AddCommand(loginCmd)
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service
|
||||
@@ -79,7 +94,6 @@ func SetupCloseHandler() {
|
||||
func SetFlagsFromEnvVars() {
|
||||
flags := rootCmd.PersistentFlags()
|
||||
flags.VisitAll(func(f *pflag.Flag) {
|
||||
|
||||
envVar := FlagNameToEnvVar(f.Name)
|
||||
|
||||
if value, present := os.LookupEnv(envVar); present {
|
||||
@@ -99,3 +113,34 @@ func FlagNameToEnvVar(f string) string {
|
||||
upper := strings.ToUpper(parsed)
|
||||
return prefix + upper
|
||||
}
|
||||
|
||||
// DialClientGRPCServer returns client connection to the dameno server.
|
||||
func DialClientGRPCServer(ctx context.Context, addr string) (*grpc.ClientConn, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second*3)
|
||||
defer cancel()
|
||||
|
||||
return grpc.DialContext(
|
||||
ctx,
|
||||
strings.TrimPrefix(addr, "tcp://"),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithBlock(),
|
||||
)
|
||||
}
|
||||
|
||||
// WithBackOff execute function in backoff cycle.
|
||||
func WithBackOff(bf func() error) error {
|
||||
return backoff.RetryNotify(bf, CLIBackOffSettings, func(err error, duration time.Duration) {
|
||||
log.Warnf("retrying Login to the Management service in %v due to error %v", duration, err)
|
||||
})
|
||||
}
|
||||
|
||||
// CLIBackOffSettings is default backoff settings for CLI commands.
|
||||
var CLIBackOffSettings = &backoff.ExponentialBackOff{
|
||||
InitialInterval: time.Second,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: 10 * time.Second,
|
||||
MaxElapsedTime: 30 * time.Second,
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
|
||||
@@ -1,14 +1,30 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
)
|
||||
|
||||
type program struct {
|
||||
cmd *cobra.Command
|
||||
args []string
|
||||
ctx context.Context
|
||||
cmd *cobra.Command
|
||||
args []string
|
||||
serv *grpc.Server
|
||||
}
|
||||
|
||||
func newProgram(cmd *cobra.Command, args []string) *program {
|
||||
ctx := internal.CtxInitState(cmd.Context())
|
||||
return &program{
|
||||
ctx: ctx,
|
||||
cmd: cmd,
|
||||
args: args,
|
||||
}
|
||||
}
|
||||
|
||||
func newSVCConfig() *service.Config {
|
||||
@@ -28,9 +44,8 @@ func newSVC(prg *program, conf *service.Config) (service.Service, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
var (
|
||||
serviceCmd = &cobra.Command{
|
||||
Use: "service",
|
||||
Short: "manages wiretrustee service",
|
||||
}
|
||||
)
|
||||
var serviceCmd = &cobra.Command{
|
||||
Use: "service",
|
||||
Short: "manages wiretrustee service",
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,60 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wiretrustee/wiretrustee/client/proto"
|
||||
"github.com/wiretrustee/wiretrustee/client/server"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"time"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func (p *program) Start(service.Service) error {
|
||||
|
||||
func (p *program) Start(svc service.Service) error {
|
||||
// Start should not block. Do the actual work async.
|
||||
log.Info("starting service") //nolint
|
||||
go func() {
|
||||
err := runClient()
|
||||
if err != nil {
|
||||
log.Errorf("stopped Wiretrustee client app due to error: %v", err)
|
||||
// in any case, even if configuration does not exists we run daemon to serve CLI gRPC API.
|
||||
p.serv = grpc.NewServer()
|
||||
|
||||
split := strings.Split(daemonAddr, "://")
|
||||
switch split[0] {
|
||||
case "unix":
|
||||
// cleanup failed close
|
||||
stat, err := os.Stat(split[1])
|
||||
if err == nil && !stat.IsDir() {
|
||||
if err := os.Remove(split[1]); err != nil {
|
||||
log.Debugf("remove socket file: %v", err)
|
||||
}
|
||||
}
|
||||
case "tcp":
|
||||
default:
|
||||
log.Errorf("unsupported daemon address protocol: %v", split[0])
|
||||
return
|
||||
}
|
||||
|
||||
listen, err := net.Listen(split[0], split[1])
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen daemon interface: %v", err)
|
||||
}
|
||||
defer listen.Close()
|
||||
|
||||
serverInstance := server.New(p.ctx, managementURL, configPath, stopCh, cleanupCh)
|
||||
if err := serverInstance.Start(); err != nil {
|
||||
log.Fatalf("failed start daemon: %v", err)
|
||||
}
|
||||
proto.RegisterDaemonServiceServer(p.serv, serverInstance)
|
||||
|
||||
log.Printf("started daemon server: %v", split[1])
|
||||
if err := p.serv.Serve(listen); err != nil {
|
||||
log.Errorf("failed to serve daemon requests: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
@@ -27,6 +64,11 @@ func (p *program) Stop(service.Service) error {
|
||||
stopCh <- 1
|
||||
}()
|
||||
|
||||
// stop CLI daemon service
|
||||
if p.serv != nil {
|
||||
p.serv.GracefulStop()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-cleanupCh:
|
||||
case <-time.After(time.Second * 10):
|
||||
@@ -36,117 +78,104 @@ func (p *program) Stop(service.Service) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
runCmd = &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "runs wiretrustee as service",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
SetFlagsFromEnvVars()
|
||||
var runCmd = &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "runs wiretrustee as service",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
return
|
||||
}
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
SetupCloseHandler()
|
||||
SetupCloseHandler()
|
||||
|
||||
prg := &program{
|
||||
cmd: cmd,
|
||||
args: args,
|
||||
}
|
||||
s, err := newSVC(newProgram(cmd, args), newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
err = s.Run()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Printf("Wiretrustee service is running")
|
||||
},
|
||||
}
|
||||
|
||||
s, err := newSVC(prg, newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
err = s.Run()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Printf("Wiretrustee service is running")
|
||||
},
|
||||
}
|
||||
)
|
||||
var startCmd = &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "starts wiretrustee service",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
var (
|
||||
startCmd = &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "starts wiretrustee service",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars()
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
return err
|
||||
}
|
||||
s, err := newSVC(newProgram(cmd, args), newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return err
|
||||
}
|
||||
err = s.Start()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return err
|
||||
}
|
||||
cmd.Println("Wiretrustee service has been started")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
return err
|
||||
}
|
||||
s, err := newSVC(&program{}, newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return err
|
||||
}
|
||||
err = s.Start()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return err
|
||||
}
|
||||
cmd.Println("Wiretrustee service has been started")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
)
|
||||
var stopCmd = &cobra.Command{
|
||||
Use: "stop",
|
||||
Short: "stops wiretrustee service",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
var (
|
||||
stopCmd = &cobra.Command{
|
||||
Use: "stop",
|
||||
Short: "stops wiretrustee service",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
SetFlagsFromEnvVars()
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
}
|
||||
s, err := newSVC(newProgram(cmd, args), newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
err = s.Stop()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Println("Wiretrustee service has been stopped")
|
||||
},
|
||||
}
|
||||
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
}
|
||||
s, err := newSVC(&program{}, newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
err = s.Stop()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Println("Wiretrustee service has been stopped")
|
||||
},
|
||||
}
|
||||
)
|
||||
var restartCmd = &cobra.Command{
|
||||
Use: "restart",
|
||||
Short: "restarts wiretrustee service",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
var (
|
||||
restartCmd = &cobra.Command{
|
||||
Use: "restart",
|
||||
Short: "restarts wiretrustee service",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
}
|
||||
s, err := newSVC(&program{}, newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
err = s.Restart()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Println("Wiretrustee service has been restarted")
|
||||
},
|
||||
}
|
||||
)
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
}
|
||||
s, err := newSVC(newProgram(cmd, args), newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
err = s.Restart()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Println("Wiretrustee service has been restarted")
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,69 +1,67 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"runtime"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
installCmd = &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "installs wiretrustee service",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars()
|
||||
var installCmd = &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "installs wiretrustee service",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
svcConfig := newSVCConfig()
|
||||
svcConfig := newSVCConfig()
|
||||
|
||||
svcConfig.Arguments = []string{
|
||||
"service",
|
||||
"run",
|
||||
"--config",
|
||||
configPath,
|
||||
"--log-level",
|
||||
logLevel,
|
||||
}
|
||||
svcConfig.Arguments = []string{
|
||||
"service",
|
||||
"run",
|
||||
"--config",
|
||||
configPath,
|
||||
"--log-level",
|
||||
logLevel,
|
||||
}
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
// Respected only by systemd systems
|
||||
svcConfig.Dependencies = []string{"After=network.target syslog.target"}
|
||||
}
|
||||
if runtime.GOOS == "linux" {
|
||||
// Respected only by systemd systems
|
||||
svcConfig.Dependencies = []string{"After=network.target syslog.target"}
|
||||
}
|
||||
|
||||
s, err := newSVC(&program{}, svcConfig)
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return err
|
||||
}
|
||||
s, err := newSVC(newProgram(cmd, args), svcConfig)
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.Install()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return err
|
||||
}
|
||||
cmd.Println("Wiretrustee service has been installed")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
)
|
||||
err = s.Install()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return err
|
||||
}
|
||||
cmd.Println("Wiretrustee service has been installed")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
uninstallCmd = &cobra.Command{
|
||||
Use: "uninstall",
|
||||
Short: "uninstalls wiretrustee service from system",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
SetFlagsFromEnvVars()
|
||||
var uninstallCmd = &cobra.Command{
|
||||
Use: "uninstall",
|
||||
Short: "uninstalls wiretrustee service from system",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
s, err := newSVC(&program{}, newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
s, err := newSVC(newProgram(cmd, args), newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = s.Uninstall()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Println("Wiretrustee has been uninstalled")
|
||||
},
|
||||
}
|
||||
|
||||
err = s.Uninstall()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Println("Wiretrustee has been uninstalled")
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
37
client/cmd/status.go
Normal file
37
client/cmd/status.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
"github.com/wiretrustee/wiretrustee/client/proto"
|
||||
)
|
||||
|
||||
var statusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "status of the Wiretrustee Service",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars()
|
||||
ctx := internal.CtxInitState(context.Background())
|
||||
|
||||
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
||||
if err != nil {
|
||||
log.Errorf("failed to connect to service CLI interface %v", err)
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
resp, err := proto.NewDaemonServiceClient(conn).Status(cmd.Context(), &proto.StatusRequest{})
|
||||
if err != nil {
|
||||
log.Errorf("status failed: %v", status.Convert(err).Message())
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("status: %v", resp.Status)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -1,13 +1,18 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
clientProto "github.com/wiretrustee/wiretrustee/client/proto"
|
||||
client "github.com/wiretrustee/wiretrustee/client/server"
|
||||
mgmtProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||
sigProto "github.com/wiretrustee/wiretrustee/signal/proto"
|
||||
sig "github.com/wiretrustee/wiretrustee/signal/server"
|
||||
"google.golang.org/grpc"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func startSignal(t *testing.T) (*grpc.Server, net.Listener) {
|
||||
@@ -26,7 +31,7 @@ func startSignal(t *testing.T) (*grpc.Server, net.Listener) {
|
||||
return s, lis
|
||||
}
|
||||
|
||||
func startManagement(config *mgmt.Config, t *testing.T) (*grpc.Server, net.Listener) {
|
||||
func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Listener) {
|
||||
lis, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -48,9 +53,40 @@ func startManagement(config *mgmt.Config, t *testing.T) (*grpc.Server, net.Liste
|
||||
go func() {
|
||||
if err := s.Serve(lis); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
return s, lis
|
||||
}
|
||||
|
||||
func startClientDaemon(
|
||||
t *testing.T, ctx context.Context, managementURL, configPath string,
|
||||
stopCh chan int, cleanupCh chan<- struct{},
|
||||
) (*grpc.Server, net.Listener) {
|
||||
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s := grpc.NewServer()
|
||||
|
||||
server := client.New(
|
||||
ctx,
|
||||
managementURL,
|
||||
configPath,
|
||||
stopCh,
|
||||
cleanupCh,
|
||||
)
|
||||
if err := server.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
clientProto.RegisterDaemonServiceServer(s, server)
|
||||
go func() {
|
||||
if err := s.Serve(lis); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
return s, lis
|
||||
}
|
||||
|
||||
278
client/cmd/up.go
278
client/cmd/up.go
@@ -1,238 +1,78 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/kardianos/service"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
mgm "github.com/wiretrustee/wiretrustee/management/client"
|
||||
mgmProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
signal "github.com/wiretrustee/wiretrustee/signal/client"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"github.com/wiretrustee/wiretrustee/client/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
upCmd = &cobra.Command{
|
||||
Use: "up",
|
||||
Short: "install, login and start wiretrustee client",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars()
|
||||
var upCmd = &cobra.Command{
|
||||
Use: "up",
|
||||
Short: "install, login and start wiretrustee client",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars()
|
||||
ctx := internal.CtxInitState(cmd.Context())
|
||||
|
||||
err := loginCmd.RunE(cmd, args)
|
||||
// workaround to run without service
|
||||
if logFile == "console" {
|
||||
config, err := internal.GetConfig(managementURL, configPath, preSharedKey)
|
||||
if err != nil {
|
||||
log.Errorf("get config file: %v", err)
|
||||
return err
|
||||
}
|
||||
if logFile == "console" {
|
||||
SetupCloseHandler()
|
||||
return runClient()
|
||||
}
|
||||
|
||||
s, err := newSVC(&program{}, newSVCConfig())
|
||||
err = WithBackOff(func() error {
|
||||
return internal.Login(ctx, config, setupKey)
|
||||
})
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
log.Errorf("backoff cycle failed: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
srvStatus, err := s.Status()
|
||||
if err != nil {
|
||||
if err == service.ErrNotInstalled {
|
||||
log.Infof("%s. Installing it now", err.Error())
|
||||
e := installCmd.RunE(cmd, args)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
} else {
|
||||
log.Warnf("failed retrieving service status: %v", err)
|
||||
}
|
||||
}
|
||||
if srvStatus == service.StatusRunning {
|
||||
stopCmd.Run(cmd, args)
|
||||
}
|
||||
return startCmd.RunE(cmd, args)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// createEngineConfig converts configuration received from Management Service to EngineConfig
|
||||
func createEngineConfig(key wgtypes.Key, config *internal.Config, peerConfig *mgmProto.PeerConfig) (*internal.EngineConfig, error) {
|
||||
iFaceBlackList := make(map[string]struct{})
|
||||
for i := 0; i < len(config.IFaceBlackList); i += 2 {
|
||||
iFaceBlackList[config.IFaceBlackList[i]] = struct{}{}
|
||||
}
|
||||
|
||||
engineConf := &internal.EngineConfig{
|
||||
WgIfaceName: config.WgIface,
|
||||
WgAddr: peerConfig.Address,
|
||||
IFaceBlackList: iFaceBlackList,
|
||||
WgPrivateKey: key,
|
||||
WgPort: internal.WgPort,
|
||||
}
|
||||
|
||||
if config.PreSharedKey != "" {
|
||||
preSharedKey, err := wgtypes.ParseKey(config.PreSharedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
SetupCloseHandler()
|
||||
return internal.RunClient(ctx, config, stopCh, cleanupCh)
|
||||
}
|
||||
engineConf.PreSharedKey = &preSharedKey
|
||||
}
|
||||
|
||||
return engineConf, nil
|
||||
}
|
||||
|
||||
// connectToSignal creates Signal Service client and established a connection
|
||||
func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig, ourPrivateKey wgtypes.Key) (*signal.GrpcClient, error) {
|
||||
var sigTLSEnabled bool
|
||||
if wtConfig.Signal.Protocol == mgmProto.HostConfig_HTTPS {
|
||||
sigTLSEnabled = true
|
||||
} else {
|
||||
sigTLSEnabled = false
|
||||
}
|
||||
|
||||
signalClient, err := signal.NewClient(ctx, wtConfig.Signal.Uri, ourPrivateKey, sigTLSEnabled)
|
||||
if err != nil {
|
||||
log.Errorf("error while connecting to the Signal Exchange Service %s: %s", wtConfig.Signal.Uri, err)
|
||||
return nil, status.Errorf(codes.FailedPrecondition, "failed connecting to Signal Service : %s", err)
|
||||
}
|
||||
|
||||
return signalClient, nil
|
||||
}
|
||||
|
||||
// connectToManagement creates Management Services client, establishes a connection, logs-in and gets a global Wiretrustee config (signal, turn, stun hosts, etc)
|
||||
func connectToManagement(ctx context.Context, managementAddr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*mgm.GrpcClient, *mgmProto.LoginResponse, error) {
|
||||
log.Debugf("connecting to management server %s", managementAddr)
|
||||
client, err := mgm.NewClient(ctx, managementAddr, ourPrivateKey, tlsEnabled)
|
||||
if err != nil {
|
||||
return nil, nil, status.Errorf(codes.FailedPrecondition, "failed connecting to Management Service : %s", err)
|
||||
}
|
||||
log.Debugf("connected to management server %s", managementAddr)
|
||||
|
||||
serverPublicKey, err := client.GetServerPublicKey()
|
||||
if err != nil {
|
||||
return nil, nil, status.Errorf(codes.FailedPrecondition, "failed while getting Management Service public key: %s", err)
|
||||
}
|
||||
|
||||
loginResp, err := client.Login(*serverPublicKey)
|
||||
if err != nil {
|
||||
if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
||||
log.Error("peer registration required. Please run wiretrustee login command first")
|
||||
return nil, nil, err
|
||||
} else {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("peer logged in to Management Service %s", managementAddr)
|
||||
|
||||
return client, loginResp, nil
|
||||
}
|
||||
|
||||
func runClient() error {
|
||||
var backOff = &backoff.ExponentialBackOff{
|
||||
InitialInterval: time.Second,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: 10 * time.Second,
|
||||
MaxElapsedTime: 24 * 3 * time.Hour, //stop the client after 3 days trying (must be a huge problem, e.g permission denied)
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
|
||||
operation := func() error {
|
||||
|
||||
config, err := internal.ReadConfig(managementURL, configPath)
|
||||
if err != nil {
|
||||
log.Errorf("failed reading config %s %v", configPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
//validate our peer's Wireguard PRIVATE key
|
||||
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
mgmTlsEnabled := false
|
||||
if config.ManagementURL.Scheme == "https" {
|
||||
mgmTlsEnabled = true
|
||||
}
|
||||
|
||||
// connect (just a connection, no stream yet) and login to Management Service to get an initial global Wiretrustee config
|
||||
mgmClient, loginResp, err := connectToManagement(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// with the global Wiretrustee config in hand connect (just a connection, no stream yet) Signal
|
||||
signalClient, err := connectToSignal(ctx, loginResp.GetWiretrusteeConfig(), myPrivateKey)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
peerConfig := loginResp.GetPeerConfig()
|
||||
|
||||
engineConfig, err := createEngineConfig(myPrivateKey, config, peerConfig)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
engine := internal.NewEngine(signalClient, mgmClient, engineConfig, cancel, ctx)
|
||||
err = engine.Start()
|
||||
if err != nil {
|
||||
log.Errorf("error while starting Wiretrustee Connection Engine: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Print("Wiretrustee engine started, my IP is: ", peerConfig.Address)
|
||||
|
||||
select {
|
||||
case <-stopCh:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
backOff.Reset()
|
||||
|
||||
err = mgmClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Management Service client %v", err)
|
||||
return err
|
||||
}
|
||||
err = signalClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Signal Service client %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = engine.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("failed stopping engine %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
cleanupCh <- struct{}{}
|
||||
}()
|
||||
|
||||
log.Info("stopped Wiretrustee client")
|
||||
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
err := backoff.Retry(operation, backOff)
|
||||
if err != nil {
|
||||
log.Errorf("exiting client retry loop due to unrecoverable error: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
||||
if err != nil {
|
||||
log.Errorf("failed to connect to service CLI interface %v", err)
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
daemonClient := proto.NewDaemonServiceClient(conn)
|
||||
|
||||
loginRequest := proto.LoginRequest{
|
||||
SetupKey: setupKey,
|
||||
PresharedKey: preSharedKey,
|
||||
ManagementUrl: managementURL,
|
||||
}
|
||||
err = WithBackOff(func() error {
|
||||
_, err := daemonClient.Login(ctx, &loginRequest)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("backoff cycle failed: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
status, err := daemonClient.Status(ctx, &proto.StatusRequest{})
|
||||
if err != nil {
|
||||
log.Errorf("get status: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if status.Status != string(internal.StatusIdle) {
|
||||
log.Warnf("already connected")
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := daemonClient.Up(ctx, &proto.UpRequest{}); err != nil {
|
||||
log.Errorf("call service up method: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
98
client/cmd/up_daemon_test.go
Normal file
98
client/cmd/up_daemon_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
)
|
||||
|
||||
func TestUpDaemon_Start(t *testing.T) {
|
||||
config := &mgmt.Config{}
|
||||
_, err := util.ReadJson("../testdata/management.json", config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testDir := t.TempDir()
|
||||
config.Datadir = testDir
|
||||
err = util.CopyFileContents("../testdata/store.json", filepath.Join(testDir, "store.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, signalLis := startSignal(t)
|
||||
signalAddr = signalLis.Addr().String()
|
||||
config.Signal.URI = signalAddr
|
||||
|
||||
_, mgmLis := startManagement(t, config)
|
||||
mgmAddr = mgmLis.Addr().String()
|
||||
}
|
||||
|
||||
func TestUpDaemon(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
confPath := tempDir + "/config.json"
|
||||
|
||||
stopCh = make(chan int, 1)
|
||||
cleanupCh = make(chan struct{}, 1)
|
||||
|
||||
ctx := internal.CtxInitState(context.Background())
|
||||
state := internal.CtxGetState(ctx)
|
||||
|
||||
_, cliLis := startClientDaemon(t, ctx, "http://"+mgmAddr, confPath, stopCh, cleanupCh)
|
||||
|
||||
cliAddr = cliLis.Addr().String()
|
||||
|
||||
daemonAddr = "tcp://" + cliAddr
|
||||
rootCmd.SetArgs([]string{
|
||||
"login",
|
||||
"--daemon-addr", "tcp://" + cliAddr,
|
||||
"--setup-key", "A2C8E62B-38F5-4553-B31E-DD66C696CEBB",
|
||||
"--log-file", "",
|
||||
})
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
t.Errorf("expected no error while running up command, got %v", err)
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Second * 3)
|
||||
if status, err := state.Status(); err != nil && status != internal.StatusIdle {
|
||||
t.Errorf("wrong status after login: %s, %v", internal.StatusIdle, err)
|
||||
return
|
||||
}
|
||||
|
||||
rootCmd.SetArgs([]string{
|
||||
"up",
|
||||
"--daemon-addr", "tcp://" + cliAddr,
|
||||
"--log-file", "",
|
||||
})
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
t.Errorf("expected no error while running up command, got %v", err)
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Second * 3)
|
||||
if status, err := state.Status(); err != nil && status != internal.StatusConnected {
|
||||
t.Errorf("wrong status after connect: %s, %v", status, err)
|
||||
return
|
||||
}
|
||||
|
||||
rootCmd.SetArgs([]string{
|
||||
"status",
|
||||
"--daemon-addr", "tcp://" + cliAddr,
|
||||
})
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
t.Errorf("expected no error while running up command, got %v", err)
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Second * 3)
|
||||
|
||||
rootCmd.SetErr(nil)
|
||||
rootCmd.SetArgs([]string{"down", "--daemon-addr", "tcp://" + cliAddr})
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
t.Errorf("expected no error while running up command, got %v", err)
|
||||
return
|
||||
}
|
||||
// we can't check status here, because context already canceled
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
)
|
||||
|
||||
var signalAddr string
|
||||
var (
|
||||
signalAddr string
|
||||
cliAddr string
|
||||
)
|
||||
|
||||
func TestUp_Start(t *testing.T) {
|
||||
config := &mgmt.Config{}
|
||||
@@ -29,15 +33,11 @@ func TestUp_Start(t *testing.T) {
|
||||
signalAddr = signalLis.Addr().String()
|
||||
config.Signal.URI = signalAddr
|
||||
|
||||
_, mgmLis := startManagement(config, t)
|
||||
_, mgmLis := startManagement(t, config)
|
||||
mgmAddr = mgmLis.Addr().String()
|
||||
|
||||
}
|
||||
|
||||
func TestUp(t *testing.T) {
|
||||
|
||||
//defer iface.Close("wt0")
|
||||
|
||||
tempDir := t.TempDir()
|
||||
confPath := tempDir + "/config.json"
|
||||
mgmtURL, err := url.Parse("http://" + mgmAddr)
|
||||
@@ -58,12 +58,13 @@ func TestUp(t *testing.T) {
|
||||
"--log-file",
|
||||
"console",
|
||||
})
|
||||
|
||||
go func() {
|
||||
err = rootCmd.Execute()
|
||||
if err != nil {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
t.Errorf("expected no error while running up command, got %v", err)
|
||||
}
|
||||
}()
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
timeout := 15 * time.Second
|
||||
timeoutChannel := time.After(timeout)
|
||||
|
||||
210
client/internal/connect.go
Normal file
210
client/internal/connect.go
Normal file
@@ -0,0 +1,210 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"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/cenkalti/backoff/v4"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// RunClient with main logic.
|
||||
func RunClient(
|
||||
ctx context.Context, config *Config, stopCh <-chan int, cleanupCh chan<- struct{},
|
||||
) error {
|
||||
backOff := &backoff.ExponentialBackOff{
|
||||
InitialInterval: time.Second,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: 10 * time.Second,
|
||||
MaxElapsedTime: 24 * 3 * time.Hour, // stop the client after 3 days trying (must be a huge problem, e.g permission denied)
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
|
||||
state := CtxGetState(ctx)
|
||||
defer state.Set(StatusIdle)
|
||||
|
||||
wrapErr := state.Wrap
|
||||
operation := func() error {
|
||||
// if context cancelled we not start new backoff cycle
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
|
||||
state.Set(StatusConnecting)
|
||||
// validate our peer's Wireguard PRIVATE key
|
||||
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
var mgmTlsEnabled bool
|
||||
if config.ManagementURL.Scheme == "https" {
|
||||
mgmTlsEnabled = true
|
||||
}
|
||||
|
||||
// connect (just a connection, no stream yet) and login to Management Service to get an initial global Wiretrustee config
|
||||
mgmClient, loginResp, err := connectToManagement(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
// with the global Wiretrustee config in hand connect (just a connection, no stream yet) Signal
|
||||
signalClient, err := connectToSignal(ctx, loginResp.GetWiretrusteeConfig(), myPrivateKey)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
peerConfig := loginResp.GetPeerConfig()
|
||||
|
||||
engineConfig, err := createEngineConfig(myPrivateKey, config, peerConfig)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
engine := NewEngine(ctx, cancel, signalClient, mgmClient, engineConfig)
|
||||
err = engine.Start()
|
||||
if err != nil {
|
||||
log.Errorf("error while starting Wiretrustee Connection Engine: %s", err)
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
log.Print("Wiretrustee engine started, my IP is: ", peerConfig.Address)
|
||||
state.Set(StatusConnected)
|
||||
|
||||
select {
|
||||
case <-stopCh:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
backOff.Reset()
|
||||
|
||||
err = mgmClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Management Service client %v", err)
|
||||
return wrapErr(err)
|
||||
}
|
||||
err = signalClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Signal Service client %v", err)
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
err = engine.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("failed stopping engine %v", err)
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
cleanupCh <- struct{}{}
|
||||
}()
|
||||
|
||||
log.Info("stopped Wiretrustee client")
|
||||
|
||||
if _, err := state.Status(); err == ErrResetConnection {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := backoff.Retry(operation, backOff)
|
||||
if err != nil {
|
||||
log.Errorf("exiting client retry loop due to unrecoverable error: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// createEngineConfig converts configuration received from Management Service to EngineConfig
|
||||
func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.PeerConfig) (*EngineConfig, error) {
|
||||
iFaceBlackList := make(map[string]struct{})
|
||||
for i := 0; i < len(config.IFaceBlackList); i += 2 {
|
||||
iFaceBlackList[config.IFaceBlackList[i]] = struct{}{}
|
||||
}
|
||||
|
||||
engineConf := &EngineConfig{
|
||||
WgIfaceName: config.WgIface,
|
||||
WgAddr: peerConfig.Address,
|
||||
IFaceBlackList: iFaceBlackList,
|
||||
WgPrivateKey: key,
|
||||
WgPort: iface.DefaultWgPort,
|
||||
}
|
||||
|
||||
if config.PreSharedKey != "" {
|
||||
preSharedKey, err := wgtypes.ParseKey(config.PreSharedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
engineConf.PreSharedKey = &preSharedKey
|
||||
}
|
||||
|
||||
return engineConf, nil
|
||||
}
|
||||
|
||||
// connectToSignal creates Signal Service client and established a connection
|
||||
func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig, ourPrivateKey wgtypes.Key) (*signal.GrpcClient, error) {
|
||||
var sigTLSEnabled bool
|
||||
if wtConfig.Signal.Protocol == mgmProto.HostConfig_HTTPS {
|
||||
sigTLSEnabled = true
|
||||
} else {
|
||||
sigTLSEnabled = false
|
||||
}
|
||||
|
||||
signalClient, err := signal.NewClient(ctx, wtConfig.Signal.Uri, ourPrivateKey, sigTLSEnabled)
|
||||
if err != nil {
|
||||
log.Errorf("error while connecting to the Signal Exchange Service %s: %s", wtConfig.Signal.Uri, err)
|
||||
return nil, status.Errorf(codes.FailedPrecondition, "failed connecting to Signal Service : %s", err)
|
||||
}
|
||||
|
||||
return signalClient, nil
|
||||
}
|
||||
|
||||
// connectToManagement creates Management Services client, establishes a connection, logs-in and gets a global Wiretrustee config (signal, turn, stun hosts, etc)
|
||||
func connectToManagement(ctx context.Context, managementAddr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*mgm.GrpcClient, *mgmProto.LoginResponse, error) {
|
||||
log.Debugf("connecting to management server %s", managementAddr)
|
||||
client, err := mgm.NewClient(ctx, managementAddr, ourPrivateKey, tlsEnabled)
|
||||
if err != nil {
|
||||
return nil, nil, status.Errorf(codes.FailedPrecondition, "failed connecting to Management Service : %s", err)
|
||||
}
|
||||
log.Debugf("connected to management server %s", managementAddr)
|
||||
|
||||
serverPublicKey, err := client.GetServerPublicKey()
|
||||
if err != nil {
|
||||
return nil, nil, status.Errorf(codes.FailedPrecondition, "failed while getting Management Service public key: %s", err)
|
||||
}
|
||||
|
||||
loginResp, err := client.Login(*serverPublicKey)
|
||||
if err != nil {
|
||||
if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
||||
log.Error("peer registration required. Please run wiretrustee login command first")
|
||||
return nil, nil, err
|
||||
} else {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("peer logged in to Management Service %s", managementAddr)
|
||||
|
||||
return client, loginResp, nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -22,25 +23,37 @@ import (
|
||||
)
|
||||
|
||||
// PeerConnectionTimeoutMax is a timeout of an initial connection attempt to a remote peer.
|
||||
// E.g. this peer will wait PeerConnectionTimeoutMax for the remote peer to respond, if not successful then it will retry the connection attempt.
|
||||
// E.g. this peer will wait PeerConnectionTimeoutMax for the remote peer to respond,
|
||||
// if not successful then it will retry the connection attempt.
|
||||
// Todo pass timeout at EnginConfig
|
||||
const PeerConnectionTimeoutMax = 45000 //ms
|
||||
const PeerConnectionTimeoutMin = 30000 //ms
|
||||
const (
|
||||
PeerConnectionTimeoutMax = 45000 // ms
|
||||
PeerConnectionTimeoutMin = 30000 // ms
|
||||
)
|
||||
|
||||
const WgPort = 51820
|
||||
var ErrResetConnection = fmt.Errorf("reset connection")
|
||||
|
||||
// EngineConfig is a config for the Engine
|
||||
type EngineConfig struct {
|
||||
WgPort int
|
||||
WgIfaceName 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)
|
||||
WgPrivateKey wgtypes.Key
|
||||
|
||||
// IFaceBlackList is a list of network interfaces to ignore when discovering connection candidates (ICE related)
|
||||
IFaceBlackList map[string]struct{}
|
||||
|
||||
PreSharedKey *wgtypes.Key
|
||||
|
||||
// UDPMuxPort default value 0 - the system will pick an available port
|
||||
UDPMuxPort int
|
||||
|
||||
// UDPMuxSrflxPort default value 0 - the system will pick an available port
|
||||
UDPMuxSrflxPort int
|
||||
}
|
||||
|
||||
// Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers.
|
||||
@@ -67,6 +80,11 @@ type Engine struct {
|
||||
|
||||
wgInterface iface.WGIface
|
||||
|
||||
udpMux ice.UDPMux
|
||||
udpMuxSrflx ice.UniversalUDPMux
|
||||
udpMuxConn *net.UDPConn
|
||||
udpMuxConnSrflx *net.UDPConn
|
||||
|
||||
// networkSerial is the latest Serial (state ID) of the network sent by the Management service
|
||||
networkSerial uint64
|
||||
}
|
||||
@@ -78,8 +96,13 @@ type Peer struct {
|
||||
}
|
||||
|
||||
// NewEngine creates a new Connection Engine
|
||||
func NewEngine(signalClient signal.Client, mgmClient mgm.Client, config *EngineConfig, cancel context.CancelFunc, ctx context.Context) *Engine {
|
||||
func NewEngine(
|
||||
ctx context.Context, cancel context.CancelFunc,
|
||||
signalClient signal.Client, mgmClient mgm.Client, config *EngineConfig,
|
||||
) *Engine {
|
||||
return &Engine{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
signal: signalClient,
|
||||
mgmClient: mgmClient,
|
||||
peerConns: map[string]*peer.Conn{},
|
||||
@@ -87,8 +110,6 @@ func NewEngine(signalClient signal.Client, mgmClient mgm.Client, config *EngineC
|
||||
config: config,
|
||||
STUNs: []*ice.URL{},
|
||||
TURNs: []*ice.URL{},
|
||||
cancel: cancel,
|
||||
ctx: ctx,
|
||||
networkSerial: 0,
|
||||
}
|
||||
}
|
||||
@@ -111,6 +132,30 @@ func (e *Engine) Stop() error {
|
||||
}
|
||||
}
|
||||
|
||||
if e.udpMux != nil {
|
||||
if err := e.udpMux.Close(); err != nil {
|
||||
log.Debugf("close udp mux: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if e.udpMuxSrflx != nil {
|
||||
if err := e.udpMuxSrflx.Close(); err != nil {
|
||||
log.Debugf("close server reflexive udp mux: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if e.udpMuxConn != nil {
|
||||
if err := e.udpMuxConn.Close(); err != nil {
|
||||
log.Debugf("close udp mux connection: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if e.udpMuxConnSrflx != nil {
|
||||
if err := e.udpMuxConnSrflx.Close(); err != nil {
|
||||
log.Debugf("close server reflexive udp mux connection: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("stopped Wiretrustee Engine")
|
||||
|
||||
return nil
|
||||
@@ -134,6 +179,21 @@ func (e *Engine) Start() error {
|
||||
return err
|
||||
}
|
||||
|
||||
e.udpMuxConn, err = net.ListenUDP("udp4", &net.UDPAddr{Port: e.config.UDPMuxPort})
|
||||
if err != nil {
|
||||
log.Errorf("failed listening on UDP port %d: [%s]", e.config.UDPMuxPort, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
e.udpMuxConnSrflx, err = net.ListenUDP("udp4", &net.UDPAddr{Port: e.config.UDPMuxSrflxPort})
|
||||
if err != nil {
|
||||
log.Errorf("failed listening on UDP port %d: [%s]", e.config.UDPMuxSrflxPort, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
e.udpMux = ice.NewUDPMuxDefault(ice.UDPMuxParams{UDPConn: e.udpMuxConn})
|
||||
e.udpMuxSrflx = ice.NewUniversalUDPMuxDefault(ice.UniversalUDPMuxParams{UDPConn: e.udpMuxConnSrflx})
|
||||
|
||||
err = e.wgInterface.Create()
|
||||
if err != nil {
|
||||
log.Errorf("failed creating tunnel interface %s: [%s]", wgIfaceName, err.Error())
|
||||
@@ -154,7 +214,6 @@ func (e *Engine) Start() error {
|
||||
|
||||
// removePeers finds and removes peers that do not exist anymore in the network map received from the Management Service
|
||||
func (e *Engine) removePeers(peersUpdate []*mgmProto.RemotePeerConfig) error {
|
||||
|
||||
currentPeers := make([]string, 0, len(e.peerConns))
|
||||
for p := range e.peerConns {
|
||||
currentPeers = append(currentPeers, p)
|
||||
@@ -209,7 +268,6 @@ func (e *Engine) removePeer(peerKey string) error {
|
||||
|
||||
// GetPeerConnectionStatus returns a connection Status or nil if peer connection wasn't found
|
||||
func (e *Engine) GetPeerConnectionStatus(peerKey string) peer.ConnStatus {
|
||||
|
||||
conn, exists := e.peerConns[peerKey]
|
||||
if exists && conn != nil {
|
||||
return conn.Status()
|
||||
@@ -217,6 +275,7 @@ func (e *Engine) GetPeerConnectionStatus(peerKey string) peer.ConnStatus {
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func (e *Engine) GetPeers() []string {
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
@@ -254,7 +313,7 @@ func signalCandidate(candidate ice.Candidate, myKey wgtypes.Key, remoteKey wgtyp
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed signaling candidate to the remote peer %s %s", remoteKey.String(), err)
|
||||
//todo ??
|
||||
// todo ??
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -262,7 +321,6 @@ func signalCandidate(candidate ice.Candidate, myKey wgtypes.Key, remoteKey wgtyp
|
||||
}
|
||||
|
||||
func signalAuth(uFrag string, pwd string, myKey wgtypes.Key, remoteKey wgtypes.Key, s signal.Client, isAnswer bool) error {
|
||||
|
||||
var t sProto.Body_Type
|
||||
if isAnswer {
|
||||
t = sProto.Body_ANSWER
|
||||
@@ -272,7 +330,8 @@ func signalAuth(uFrag string, pwd string, myKey wgtypes.Key, remoteKey wgtypes.K
|
||||
|
||||
msg, err := signal.MarshalCredential(myKey, remoteKey, &signal.Credential{
|
||||
UFrag: uFrag,
|
||||
Pwd: pwd}, t)
|
||||
Pwd: pwd,
|
||||
}, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -299,7 +358,7 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
|
||||
return err
|
||||
}
|
||||
|
||||
//todo update signal
|
||||
// todo update signal
|
||||
}
|
||||
|
||||
if update.GetNetworkMap() != nil {
|
||||
@@ -311,7 +370,6 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// receiveManagementEvents connects to the Management Service event stream to receive updates from the management service
|
||||
@@ -324,6 +382,7 @@ func (e *Engine) receiveManagementEvents() {
|
||||
if err != nil {
|
||||
// happens if management is unavailable for a long time.
|
||||
// We want to cancel the operation of the whole client
|
||||
_ = CtxGetState(e.ctx).Wrap(ErrResetConnection)
|
||||
e.cancel()
|
||||
return
|
||||
}
|
||||
@@ -371,7 +430,6 @@ func (e *Engine) updateTURNs(turns []*mgmProto.ProtectedHostConfig) error {
|
||||
}
|
||||
|
||||
func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
|
||||
|
||||
serial := networkMap.GetSerial()
|
||||
if e.networkSerial > serial {
|
||||
log.Debugf("received outdated NetworkMap with serial %d, ignoring", serial)
|
||||
@@ -455,7 +513,6 @@ func (e Engine) peerExists(peerKey string) bool {
|
||||
}
|
||||
|
||||
func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, error) {
|
||||
|
||||
var stunTurn []*ice.URL
|
||||
stunTurn = append(stunTurn, e.STUNs...)
|
||||
stunTurn = append(stunTurn, e.TURNs...)
|
||||
@@ -481,6 +538,8 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er
|
||||
StunTurn: stunTurn,
|
||||
InterfaceBlackList: interfaceBlacklist,
|
||||
Timeout: timeout,
|
||||
UDPMux: e.udpMux,
|
||||
UDPMuxSrflx: e.udpMuxSrflx,
|
||||
ProxyConfig: proxyConfig,
|
||||
}
|
||||
|
||||
@@ -515,11 +574,9 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er
|
||||
|
||||
// receiveSignalEvents connects to the Signal Service event stream to negotiate connection with remote peers
|
||||
func (e *Engine) receiveSignalEvents() {
|
||||
|
||||
go func() {
|
||||
// connect to a stream of messages coming from the signal server
|
||||
err := e.signal.Receive(func(msg *sProto.Message) error {
|
||||
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
|
||||
@@ -561,6 +618,7 @@ func (e *Engine) receiveSignalEvents() {
|
||||
if err != nil {
|
||||
// happens if signal is unavailable for a long time.
|
||||
// We want to cancel the operation of the whole client
|
||||
_ = CtxGetState(e.ctx).Wrap(ErrResetConnection)
|
||||
e.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ var (
|
||||
)
|
||||
|
||||
func TestEngine_UpdateNetworkMap(t *testing.T) {
|
||||
|
||||
// test setup
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
@@ -51,12 +50,12 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
engine := NewEngine(&signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||
WgIfaceName: "utun100",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
}, cancel, ctx)
|
||||
})
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
@@ -157,7 +156,6 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, c := range []testCase{case1, case2, case3, case4, case5} {
|
||||
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
err = engine.updateNetworkMap(c.networkMap)
|
||||
if err != nil {
|
||||
@@ -179,13 +177,10 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEngine_Sync(t *testing.T) {
|
||||
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -199,7 +194,6 @@ func TestEngine_Sync(t *testing.T) {
|
||||
updates := make(chan *mgmtProto.SyncResponse)
|
||||
defer close(updates)
|
||||
syncFunc := func(msgHandler func(msg *mgmtProto.SyncResponse) error) error {
|
||||
|
||||
for msg := range updates {
|
||||
err := msgHandler(msg)
|
||||
if err != nil {
|
||||
@@ -209,12 +203,12 @@ func TestEngine_Sync(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
|
||||
engine := NewEngine(&signal.MockClient{}, &mgmt.MockClient{SyncFunc: syncFunc}, &EngineConfig{
|
||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{SyncFunc: syncFunc}, &EngineConfig{
|
||||
WgIfaceName: "utun100",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
}, cancel, ctx)
|
||||
})
|
||||
|
||||
defer func() {
|
||||
err := engine.Stop()
|
||||
@@ -256,6 +250,7 @@ func TestEngine_Sync(t *testing.T) {
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Fatalf("timeout while waiting for test to finish")
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -263,12 +258,10 @@ func TestEngine_Sync(t *testing.T) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEngine_MultiplePeers(t *testing.T) {
|
||||
|
||||
//log.SetLevel(log.DebugLevel)
|
||||
// log.SetLevel(log.DebugLevel)
|
||||
|
||||
dir := t.TempDir()
|
||||
|
||||
@@ -284,8 +277,9 @@ func TestEngine_MultiplePeers(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(CtxInitState(context.Background()))
|
||||
defer cancel()
|
||||
|
||||
sport := 10010
|
||||
sigServer, err := startSignal(sport)
|
||||
if err != nil {
|
||||
@@ -365,7 +359,6 @@ func TestEngine_MultiplePeers(t *testing.T) {
|
||||
}
|
||||
|
||||
func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey string, i int, mport int, sport int) (*Engine, error) {
|
||||
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -397,14 +390,15 @@ func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey strin
|
||||
ifaceName = fmt.Sprintf("wt%d", i)
|
||||
}
|
||||
|
||||
wgPort := 33100 + i
|
||||
conf := &EngineConfig{
|
||||
WgIfaceName: ifaceName,
|
||||
WgAddr: resp.PeerConfig.Address,
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100 + i,
|
||||
WgPort: wgPort,
|
||||
}
|
||||
|
||||
return NewEngine(signalClient, mgmtClient, conf, cancel, ctx), nil
|
||||
return NewEngine(ctx, cancel, signalClient, mgmtClient, conf), nil
|
||||
}
|
||||
|
||||
func startSignal(port int) (*grpc.Server, error) {
|
||||
@@ -427,7 +421,6 @@ func startSignal(port int) (*grpc.Server, error) {
|
||||
}
|
||||
|
||||
func startManagement(port int, dataDir string) (*grpc.Server, error) {
|
||||
|
||||
config := &server.Config{
|
||||
Stuns: []*server.Host{},
|
||||
TURNConfig: &server.TURNConfig{},
|
||||
|
||||
118
client/internal/login.go
Normal file
118
client/internal/login.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/client/system"
|
||||
mgm "github.com/wiretrustee/wiretrustee/management/client"
|
||||
mgmProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func Login(ctx context.Context, config *Config, setupKey string) error {
|
||||
backOff := &backoff.ExponentialBackOff{
|
||||
InitialInterval: time.Second,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: 2 * time.Second,
|
||||
MaxElapsedTime: time.Second * 10,
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
|
||||
// validate our peer's Wireguard PRIVATE key
|
||||
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
var mgmTlsEnabled bool
|
||||
if config.ManagementURL.Scheme == "https" {
|
||||
mgmTlsEnabled = true
|
||||
}
|
||||
|
||||
loginOp := func() error {
|
||||
log.Debugf("connecting to Management Service %s", config.ManagementURL.String())
|
||||
mgmClient, err := mgm.NewClient(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||
if err != nil {
|
||||
log.Errorf("failed connecting to Management Service %s %v", config.ManagementURL.String(), err)
|
||||
return err
|
||||
}
|
||||
log.Debugf("connected to management Service %s", config.ManagementURL.String())
|
||||
|
||||
serverKey, err := mgmClient.GetServerPublicKey()
|
||||
if err != nil {
|
||||
log.Errorf("failed while getting Management Service public key: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = loginPeer(*serverKey, mgmClient, setupKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed logging-in peer on Management Service : %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = mgmClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Management Service client: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err = backoff.RetryNotify(loginOp, backOff, func(err error, duration time.Duration) {
|
||||
log.Warnf("retrying Login to the Management service in %v due to error %v", duration, err)
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("exiting login retry loop due to unrecoverable error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// loginPeer attempts to login to Management Service. If peer wasn't registered, tries the registration flow.
|
||||
func loginPeer(serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string) (*mgmProto.LoginResponse, error) {
|
||||
loginResp, err := client.Login(serverPublicKey)
|
||||
if err != nil {
|
||||
if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
||||
log.Debugf("peer registration required")
|
||||
return registerPeer(serverPublicKey, client, setupKey)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("peer has successfully logged-in to Management Service")
|
||||
|
||||
return loginResp, nil
|
||||
}
|
||||
|
||||
// registerPeer checks whether setupKey was provided via cmd line and if not then it prompts user to enter a key.
|
||||
// Otherwise tries to register with the provided setupKey via command line.
|
||||
func registerPeer(serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string) (*mgmProto.LoginResponse, error) {
|
||||
validSetupKey, err := uuid.Parse(setupKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("sending peer registration request to Management Service")
|
||||
info := system.GetInfo()
|
||||
loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), info)
|
||||
if err != nil {
|
||||
log.Errorf("failed registering peer %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("peer has been successfully registered on Management Service")
|
||||
|
||||
return loginResp, nil
|
||||
}
|
||||
@@ -2,12 +2,15 @@ package peer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/pion/ice/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal/proxy"
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/ice/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal/proxy"
|
||||
)
|
||||
|
||||
// ConnConfig is a peer Connection configuration
|
||||
@@ -28,6 +31,9 @@ type ConnConfig struct {
|
||||
Timeout time.Duration
|
||||
|
||||
ProxyConfig proxy.Config
|
||||
|
||||
UDPMux ice.UDPMux
|
||||
UDPMuxSrflx ice.UniversalUDPMux
|
||||
}
|
||||
|
||||
// IceCredentials ICE protocol credentials struct
|
||||
@@ -83,11 +89,20 @@ func interfaceFilter(blackList []string) func(string) bool {
|
||||
}
|
||||
}
|
||||
return func(iFace string) bool {
|
||||
if len(blackListMap) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
_, ok := blackListMap[iFace]
|
||||
return !ok
|
||||
if ok {
|
||||
return false
|
||||
}
|
||||
// look for unlisted Wireguard interfaces
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
log.Debugf("trying to create a wgctrl client failed with: %v", err)
|
||||
}
|
||||
defer wg.Close()
|
||||
|
||||
_, err = wg.Device(iFace)
|
||||
return err != nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +119,8 @@ func (conn *Conn) reCreateAgent() error {
|
||||
CandidateTypes: []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive, ice.CandidateTypeRelay},
|
||||
FailedTimeout: &failedTimeout,
|
||||
InterfaceFilter: interfaceFilter(conn.config.InterfaceBlackList),
|
||||
UDPMux: conn.config.UDPMux,
|
||||
UDPMuxSrflx: conn.config.UDPMuxSrflx,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -174,7 +191,7 @@ func (conn *Conn) Open() error {
|
||||
|
||||
log.Debugf("received connection confirmation from peer %s", conn.config.Key)
|
||||
|
||||
//at this point we received offer/answer and we are ready to gather candidates
|
||||
// at this point we received offer/answer and we are ready to gather candidates
|
||||
conn.mu.Lock()
|
||||
conn.status = StatusConnecting
|
||||
conn.ctx, conn.notifyDisconnected = context.WithCancel(context.Background())
|
||||
@@ -206,7 +223,14 @@ func (conn *Conn) Open() error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("connected to peer %s [laddr <-> raddr] [%s <-> %s]", conn.config.Key, remoteConn.LocalAddr().String(), remoteConn.RemoteAddr().String())
|
||||
if conn.proxy.Type() == proxy.TypeNoProxy {
|
||||
host, _, _ := net.SplitHostPort(remoteConn.LocalAddr().String())
|
||||
rhost, _, _ := net.SplitHostPort(remoteConn.RemoteAddr().String())
|
||||
// direct Wireguard connection
|
||||
log.Infof("directly connected to peer %s [laddr <-> raddr] [%s:%d <-> %s:%d]", conn.config.Key, host, iface.DefaultWgPort, rhost, iface.DefaultWgPort)
|
||||
} else {
|
||||
log.Infof("connected to peer %s [laddr <-> raddr] [%s <-> %s]", conn.config.Key, remoteConn.LocalAddr().String(), remoteConn.RemoteAddr().String())
|
||||
}
|
||||
|
||||
// wait until connection disconnected or has been closed externally (upper layer, e.g. engine)
|
||||
select {
|
||||
@@ -219,16 +243,65 @@ func (conn *Conn) Open() error {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 using ICE layer.
|
||||
func shouldUseProxy(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
|
||||
}
|
||||
|
||||
// IsPublicIP indicates whether IP is public or not.
|
||||
func IsPublicIP(ip net.IP) bool {
|
||||
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsPrivate() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// startProxy starts proxying traffic from/to local Wireguard and sets connection status to StatusConnected
|
||||
func (conn *Conn) startProxy(remoteConn net.Conn) error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
conn.proxy = proxy.NewWireguardProxy(conn.config.ProxyConfig)
|
||||
err := conn.proxy.Start(remoteConn)
|
||||
var pair *ice.CandidatePair
|
||||
pair, err := conn.agent.GetSelectedCandidatePair()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
useProxy := shouldUseProxy(pair)
|
||||
var p proxy.Proxy
|
||||
if useProxy {
|
||||
p = proxy.NewWireguardProxy(conn.config.ProxyConfig)
|
||||
} else {
|
||||
p = proxy.NewNoProxy(conn.config.ProxyConfig)
|
||||
}
|
||||
conn.proxy = p
|
||||
err = p.Start(remoteConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.status = StatusConnected
|
||||
|
||||
return nil
|
||||
@@ -287,7 +360,7 @@ func (conn *Conn) SetSignalCandidate(handler func(candidate ice.Candidate) error
|
||||
// and then signals them to the remote peer
|
||||
func (conn *Conn) onICECandidate(candidate ice.Candidate) {
|
||||
if candidate != nil {
|
||||
//log.Debugf("discovered local candidate %s", candidate.String())
|
||||
// log.Debugf("discovered local candidate %s", candidate.String())
|
||||
go func() {
|
||||
err := conn.signalCandidate(candidate)
|
||||
if err != nil {
|
||||
@@ -298,8 +371,8 @@ func (conn *Conn) onICECandidate(candidate ice.Candidate) {
|
||||
}
|
||||
|
||||
func (conn *Conn) onICESelectedCandidatePair(c1 ice.Candidate, c2 ice.Candidate) {
|
||||
log.Debugf("selected candidate pair [local <-> remote] -> [%s <-> %s], peer %s", conn.config.Key,
|
||||
c1.String(), c2.String())
|
||||
log.Debugf("selected candidate pair [local <-> remote] -> [%s <-> %s], peer %s", c1.String(), c2.String(),
|
||||
conn.config.Key)
|
||||
}
|
||||
|
||||
// onICEConnectionStateChange registers callback of an ICE Agent to track connection state
|
||||
@@ -386,7 +459,7 @@ func (conn *Conn) OnRemoteOffer(remoteAuth IceCredentials) bool {
|
||||
return true
|
||||
default:
|
||||
log.Debugf("OnRemoteOffer skipping message from peer %s on status %s because is not ready", conn.config.Key, conn.status.String())
|
||||
//connection might not be ready yet to receive so we ignore the message
|
||||
// connection might not be ready yet to receive so we ignore the message
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -400,7 +473,7 @@ func (conn *Conn) OnRemoteAnswer(remoteAuth IceCredentials) bool {
|
||||
case conn.remoteAnswerCh <- remoteAuth:
|
||||
return true
|
||||
default:
|
||||
//connection might not be ready yet to receive so we ignore the message
|
||||
// connection might not be ready yet to receive so we ignore the message
|
||||
log.Debugf("OnRemoteAnswer skipping message from peer %s on status %s because is not ready", conn.config.Key, conn.status.String())
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -66,3 +66,7 @@ func (p *DummyProxy) Start(remoteConn net.Conn) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DummyProxy) Type() Type {
|
||||
return TypeDummy
|
||||
}
|
||||
|
||||
52
client/internal/proxy/noproxy.go
Normal file
52
client/internal/proxy/noproxy.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
"net"
|
||||
)
|
||||
|
||||
// NoProxy is used when there is no need for a proxy between ICE and Wireguard.
|
||||
// This is possible in either of these cases:
|
||||
// - peers are in the same local network
|
||||
// - one of the peers has a public static IP (host)
|
||||
// NoProxy will just update remote peer with a remote host and fixed Wireguard port (r.g. 51820).
|
||||
// In order NoProxy to work, Wireguard port has to be fixed for the time being.
|
||||
type NoProxy struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
func NewNoProxy(config Config) *NoProxy {
|
||||
return &NoProxy{config: config}
|
||||
}
|
||||
|
||||
func (p *NoProxy) Close() error {
|
||||
err := p.config.WgInterface.RemovePeer(p.config.RemoteKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start just updates Wireguard peer with the remote IP and default Wireguard port
|
||||
func (p *NoProxy) Start(remoteConn net.Conn) error {
|
||||
|
||||
log.Debugf("using NoProxy while connecting to peer %s", p.config.RemoteKey)
|
||||
addr, err := net.ResolveUDPAddr("udp", remoteConn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addr.Port = iface.DefaultWgPort
|
||||
err = p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive,
|
||||
addr, p.config.PreSharedKey)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *NoProxy) Type() Type {
|
||||
return TypeNoProxy
|
||||
}
|
||||
@@ -10,6 +10,14 @@ import (
|
||||
|
||||
const DefaultWgKeepAlive = 25 * time.Second
|
||||
|
||||
type Type string
|
||||
|
||||
const (
|
||||
TypeNoProxy Type = "NoProxy"
|
||||
TypeWireguard Type = "Wireguard"
|
||||
TypeDummy Type = "Dummy"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
WgListenAddr string
|
||||
RemoteKey string
|
||||
@@ -22,4 +30,5 @@ type Proxy interface {
|
||||
io.Closer
|
||||
// Start creates a local remoteConn and starts proxying data from/to remoteConn
|
||||
Start(remoteConn net.Conn) error
|
||||
Type() Type
|
||||
}
|
||||
|
||||
@@ -122,3 +122,7 @@ func (p *WireguardProxy) proxyToLocal() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *WireguardProxy) Type() Type {
|
||||
return TypeWireguard
|
||||
}
|
||||
|
||||
67
client/internal/state.go
Normal file
67
client/internal/state.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type StatusType string
|
||||
|
||||
const (
|
||||
StatusIdle StatusType = "Idle"
|
||||
|
||||
StatusConnecting StatusType = "Connecting"
|
||||
StatusConnected StatusType = "Connected"
|
||||
)
|
||||
|
||||
// CtxInitState setup context state into the context tree.
|
||||
//
|
||||
// This function should be used to initialize context before
|
||||
// CtxGetState will be executed.
|
||||
func CtxInitState(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, stateCtx, &contextState{
|
||||
status: StatusIdle,
|
||||
})
|
||||
}
|
||||
|
||||
// CtxGetState object to get/update state/errors of process.
|
||||
func CtxGetState(ctx context.Context) *contextState {
|
||||
return ctx.Value(stateCtx).(*contextState)
|
||||
}
|
||||
|
||||
type contextState struct {
|
||||
err error
|
||||
status StatusType
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (c *contextState) Set(update StatusType) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
c.status = update
|
||||
c.err = nil
|
||||
}
|
||||
|
||||
func (c *contextState) Status() (StatusType, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
if c.err != nil {
|
||||
return "", c.err
|
||||
}
|
||||
|
||||
return c.status, nil
|
||||
}
|
||||
|
||||
func (c *contextState) Wrap(err error) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
c.err = err
|
||||
return err
|
||||
}
|
||||
|
||||
type stateKey int
|
||||
|
||||
var stateCtx stateKey
|
||||
566
client/proto/daemon.pb.go
Normal file
566
client/proto/daemon.pb.go
Normal file
@@ -0,0 +1,566 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.17.3
|
||||
// source: daemon.proto
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
_ "google.golang.org/protobuf/types/descriptorpb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type LoginRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// setupKey wiretrustee setup key.
|
||||
SetupKey string `protobuf:"bytes,1,opt,name=setupKey,proto3" json:"setupKey,omitempty"`
|
||||
// presharedKey for wireguard setup.
|
||||
PresharedKey string `protobuf:"bytes,2,opt,name=presharedKey,proto3" json:"presharedKey,omitempty"`
|
||||
// managementUrl to authenticate.
|
||||
ManagementUrl string `protobuf:"bytes,3,opt,name=managementUrl,proto3" json:"managementUrl,omitempty"`
|
||||
}
|
||||
|
||||
func (x *LoginRequest) Reset() {
|
||||
*x = LoginRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_daemon_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *LoginRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*LoginRequest) ProtoMessage() {}
|
||||
|
||||
func (x *LoginRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead.
|
||||
func (*LoginRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *LoginRequest) GetSetupKey() string {
|
||||
if x != nil {
|
||||
return x.SetupKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *LoginRequest) GetPresharedKey() string {
|
||||
if x != nil {
|
||||
return x.PresharedKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *LoginRequest) GetManagementUrl() string {
|
||||
if x != nil {
|
||||
return x.ManagementUrl
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *LoginResponse) Reset() {
|
||||
*x = LoginResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_daemon_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *LoginResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*LoginResponse) ProtoMessage() {}
|
||||
|
||||
func (x *LoginResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead.
|
||||
func (*LoginResponse) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
type UpRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *UpRequest) Reset() {
|
||||
*x = UpRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_daemon_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *UpRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*UpRequest) ProtoMessage() {}
|
||||
|
||||
func (x *UpRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use UpRequest.ProtoReflect.Descriptor instead.
|
||||
func (*UpRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
type UpResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *UpResponse) Reset() {
|
||||
*x = UpResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_daemon_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *UpResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*UpResponse) ProtoMessage() {}
|
||||
|
||||
func (x *UpResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use UpResponse.ProtoReflect.Descriptor instead.
|
||||
func (*UpResponse) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
type StatusRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *StatusRequest) Reset() {
|
||||
*x = StatusRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_daemon_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *StatusRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*StatusRequest) ProtoMessage() {}
|
||||
|
||||
func (x *StatusRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use StatusRequest.ProtoReflect.Descriptor instead.
|
||||
func (*StatusRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
type StatusResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// status of the server.
|
||||
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
|
||||
}
|
||||
|
||||
func (x *StatusResponse) Reset() {
|
||||
*x = StatusResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_daemon_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *StatusResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*StatusResponse) ProtoMessage() {}
|
||||
|
||||
func (x *StatusResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[5]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use StatusResponse.ProtoReflect.Descriptor instead.
|
||||
func (*StatusResponse) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *StatusResponse) GetStatus() string {
|
||||
if x != nil {
|
||||
return x.Status
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type DownRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *DownRequest) Reset() {
|
||||
*x = DownRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_daemon_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DownRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DownRequest) ProtoMessage() {}
|
||||
|
||||
func (x *DownRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[6]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DownRequest.ProtoReflect.Descriptor instead.
|
||||
func (*DownRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
type DownResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *DownResponse) Reset() {
|
||||
*x = DownResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_daemon_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DownResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DownResponse) ProtoMessage() {}
|
||||
|
||||
func (x *DownResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[7]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DownResponse.ProtoReflect.Descriptor instead.
|
||||
func (*DownResponse) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
var File_daemon_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_daemon_proto_rawDesc = []byte{
|
||||
0x0a, 0x0c, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
|
||||
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x74, 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, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x73, 0x68, 0x61, 0x72, 0x65,
|
||||
0x64, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x73,
|
||||
0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61,
|
||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x22, 0x0f,
|
||||
0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a,
|
||||
0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x74,
|
||||
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x28, 0x0a, 0x0e, 0x53,
|
||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a,
|
||||
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73,
|
||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe6, 0x01, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53,
|
||||
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12,
|
||||
0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c,
|
||||
0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d,
|
||||
0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
|
||||
0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a,
|
||||
0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
|
||||
0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16,
|
||||
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e,
|
||||
0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44,
|
||||
0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a,
|
||||
0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_daemon_proto_rawDescOnce sync.Once
|
||||
file_daemon_proto_rawDescData = file_daemon_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_daemon_proto_rawDescGZIP() []byte {
|
||||
file_daemon_proto_rawDescOnce.Do(func() {
|
||||
file_daemon_proto_rawDescData = protoimpl.X.CompressGZIP(file_daemon_proto_rawDescData)
|
||||
})
|
||||
return file_daemon_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_daemon_proto_goTypes = []interface{}{
|
||||
(*LoginRequest)(nil), // 0: daemon.LoginRequest
|
||||
(*LoginResponse)(nil), // 1: daemon.LoginResponse
|
||||
(*UpRequest)(nil), // 2: daemon.UpRequest
|
||||
(*UpResponse)(nil), // 3: daemon.UpResponse
|
||||
(*StatusRequest)(nil), // 4: daemon.StatusRequest
|
||||
(*StatusResponse)(nil), // 5: daemon.StatusResponse
|
||||
(*DownRequest)(nil), // 6: daemon.DownRequest
|
||||
(*DownResponse)(nil), // 7: daemon.DownResponse
|
||||
}
|
||||
var file_daemon_proto_depIdxs = []int32{
|
||||
0, // 0: daemon.DaemonService.Login:input_type -> daemon.LoginRequest
|
||||
2, // 1: daemon.DaemonService.Up:input_type -> daemon.UpRequest
|
||||
4, // 2: daemon.DaemonService.Status:input_type -> daemon.StatusRequest
|
||||
6, // 3: daemon.DaemonService.Down:input_type -> daemon.DownRequest
|
||||
1, // 4: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
|
||||
3, // 5: daemon.DaemonService.Up:output_type -> daemon.UpResponse
|
||||
5, // 6: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
|
||||
7, // 7: daemon.DaemonService.Down:output_type -> daemon.DownResponse
|
||||
4, // [4:8] is the sub-list for method output_type
|
||||
0, // [0:4] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_daemon_proto_init() }
|
||||
func file_daemon_proto_init() {
|
||||
if File_daemon_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_daemon_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*LoginRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_daemon_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*LoginResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_daemon_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*UpRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_daemon_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*UpResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_daemon_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*StatusRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_daemon_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*StatusResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_daemon_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DownRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_daemon_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DownResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_daemon_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 8,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_daemon_proto_goTypes,
|
||||
DependencyIndexes: file_daemon_proto_depIdxs,
|
||||
MessageInfos: file_daemon_proto_msgTypes,
|
||||
}.Build()
|
||||
File_daemon_proto = out.File
|
||||
file_daemon_proto_rawDesc = nil
|
||||
file_daemon_proto_goTypes = nil
|
||||
file_daemon_proto_depIdxs = nil
|
||||
}
|
||||
49
client/proto/daemon.proto
Normal file
49
client/proto/daemon.proto
Normal file
@@ -0,0 +1,49 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
option go_package = "/proto";
|
||||
|
||||
package daemon;
|
||||
|
||||
service DaemonService {
|
||||
// Login uses setup key to prepare configuration for the daemon.
|
||||
rpc Login(LoginRequest) returns (LoginResponse) {}
|
||||
|
||||
// Up starts engine work in the daemon.
|
||||
rpc Up(UpRequest) returns (UpResponse) {}
|
||||
|
||||
// Status of the service.
|
||||
rpc Status(StatusRequest) returns (StatusResponse) {}
|
||||
|
||||
// Down engine work in the daemon.
|
||||
rpc Down(DownRequest) returns (DownResponse) {}
|
||||
};
|
||||
|
||||
message LoginRequest {
|
||||
// setupKey wiretrustee setup key.
|
||||
string setupKey = 1;
|
||||
|
||||
// presharedKey for wireguard setup.
|
||||
string presharedKey = 2;
|
||||
|
||||
// managementUrl to authenticate.
|
||||
string managementUrl = 3;
|
||||
}
|
||||
|
||||
message LoginResponse {}
|
||||
|
||||
message UpRequest {}
|
||||
|
||||
message UpResponse {}
|
||||
|
||||
message StatusRequest{}
|
||||
|
||||
message StatusResponse{
|
||||
// status of the server.
|
||||
string status = 1;
|
||||
}
|
||||
|
||||
message DownRequest {}
|
||||
|
||||
message DownResponse {}
|
||||
217
client/proto/daemon_grpc.pb.go
Normal file
217
client/proto/daemon_grpc.pb.go
Normal file
@@ -0,0 +1,217 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// DaemonServiceClient is the client API for DaemonService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type DaemonServiceClient interface {
|
||||
// Login uses setup key to prepare configuration for the daemon.
|
||||
Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error)
|
||||
// Up starts engine work in the daemon.
|
||||
Up(ctx context.Context, in *UpRequest, opts ...grpc.CallOption) (*UpResponse, error)
|
||||
// Status of the service.
|
||||
Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error)
|
||||
// Down engine work in the daemon.
|
||||
Down(ctx context.Context, in *DownRequest, opts ...grpc.CallOption) (*DownResponse, error)
|
||||
}
|
||||
|
||||
type daemonServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewDaemonServiceClient(cc grpc.ClientConnInterface) DaemonServiceClient {
|
||||
return &daemonServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *daemonServiceClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) {
|
||||
out := new(LoginResponse)
|
||||
err := c.cc.Invoke(ctx, "/daemon.DaemonService/Login", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *daemonServiceClient) Up(ctx context.Context, in *UpRequest, opts ...grpc.CallOption) (*UpResponse, error) {
|
||||
out := new(UpResponse)
|
||||
err := c.cc.Invoke(ctx, "/daemon.DaemonService/Up", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *daemonServiceClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) {
|
||||
out := new(StatusResponse)
|
||||
err := c.cc.Invoke(ctx, "/daemon.DaemonService/Status", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *daemonServiceClient) Down(ctx context.Context, in *DownRequest, opts ...grpc.CallOption) (*DownResponse, error) {
|
||||
out := new(DownResponse)
|
||||
err := c.cc.Invoke(ctx, "/daemon.DaemonService/Down", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DaemonServiceServer is the server API for DaemonService service.
|
||||
// All implementations must embed UnimplementedDaemonServiceServer
|
||||
// for forward compatibility
|
||||
type DaemonServiceServer interface {
|
||||
// Login uses setup key to prepare configuration for the daemon.
|
||||
Login(context.Context, *LoginRequest) (*LoginResponse, error)
|
||||
// Up starts engine work in the daemon.
|
||||
Up(context.Context, *UpRequest) (*UpResponse, error)
|
||||
// Status of the service.
|
||||
Status(context.Context, *StatusRequest) (*StatusResponse, error)
|
||||
// Down engine work in the daemon.
|
||||
Down(context.Context, *DownRequest) (*DownResponse, error)
|
||||
mustEmbedUnimplementedDaemonServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedDaemonServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedDaemonServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedDaemonServiceServer) Login(context.Context, *LoginRequest) (*LoginResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Login not implemented")
|
||||
}
|
||||
func (UnimplementedDaemonServiceServer) Up(context.Context, *UpRequest) (*UpResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Up not implemented")
|
||||
}
|
||||
func (UnimplementedDaemonServiceServer) Status(context.Context, *StatusRequest) (*StatusResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Status not implemented")
|
||||
}
|
||||
func (UnimplementedDaemonServiceServer) Down(context.Context, *DownRequest) (*DownResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Down not implemented")
|
||||
}
|
||||
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
|
||||
|
||||
// UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to DaemonServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeDaemonServiceServer interface {
|
||||
mustEmbedUnimplementedDaemonServiceServer()
|
||||
}
|
||||
|
||||
func RegisterDaemonServiceServer(s grpc.ServiceRegistrar, srv DaemonServiceServer) {
|
||||
s.RegisterService(&DaemonService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _DaemonService_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(LoginRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DaemonServiceServer).Login(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/daemon.DaemonService/Login",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DaemonServiceServer).Login(ctx, req.(*LoginRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DaemonService_Up_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DaemonServiceServer).Up(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/daemon.DaemonService/Up",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DaemonServiceServer).Up(ctx, req.(*UpRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DaemonService_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(StatusRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DaemonServiceServer).Status(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/daemon.DaemonService/Status",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DaemonServiceServer).Status(ctx, req.(*StatusRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DaemonService_Down_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DownRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DaemonServiceServer).Down(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/daemon.DaemonService/Down",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DaemonServiceServer).Down(ctx, req.(*DownRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var DaemonService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "daemon.DaemonService",
|
||||
HandlerType: (*DaemonServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Login",
|
||||
Handler: _DaemonService_Login_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Up",
|
||||
Handler: _DaemonService_Up_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Status",
|
||||
Handler: _DaemonService_Status_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Down",
|
||||
Handler: _DaemonService_Down_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "daemon.proto",
|
||||
}
|
||||
4
client/proto/generate.sh
Executable file
4
client/proto/generate.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
|
||||
protoc -I proto/ proto/daemon.proto --go_out=. --go-grpc_out=.
|
||||
166
client/server/server.go
Normal file
166
client/server/server.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
"github.com/wiretrustee/wiretrustee/client/proto"
|
||||
)
|
||||
|
||||
// Server for service control.
|
||||
type Server struct {
|
||||
rootCtx context.Context
|
||||
actCancel context.CancelFunc
|
||||
|
||||
managementURL string
|
||||
configPath string
|
||||
stopCh chan int
|
||||
cleanupCh chan<- struct{}
|
||||
|
||||
mutex sync.Mutex
|
||||
config *internal.Config
|
||||
proto.UnimplementedDaemonServiceServer
|
||||
}
|
||||
|
||||
// New server instance constructor.
|
||||
func New(
|
||||
ctx context.Context, managementURL, configPath string,
|
||||
stopCh chan int, cleanupCh chan<- struct{},
|
||||
) *Server {
|
||||
return &Server{
|
||||
rootCtx: ctx,
|
||||
managementURL: managementURL,
|
||||
configPath: configPath,
|
||||
stopCh: stopCh,
|
||||
cleanupCh: cleanupCh,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
state := internal.CtxGetState(s.rootCtx)
|
||||
|
||||
// if current state contains any error, return it
|
||||
// in all other cases we can continue execution only if status is idle and up command was
|
||||
// not in the progress or already successfully estabilished connection.
|
||||
status, err := state.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if status != internal.StatusIdle {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(s.rootCtx)
|
||||
s.actCancel = cancel
|
||||
|
||||
// if configuration exists, we just start connections.
|
||||
config, err := internal.ReadConfig(s.managementURL, s.configPath)
|
||||
if err != nil {
|
||||
log.Warnf("no config file, skip connection stage: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := internal.RunClient(ctx, config, s.stopCh, s.cleanupCh); err != nil {
|
||||
log.Errorf("init connections: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Login uses setup key to prepare configuration for the daemon.
|
||||
func (s *Server) Login(_ context.Context, msg *proto.LoginRequest) (*proto.LoginResponse, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
managementURL := s.managementURL
|
||||
if msg.ManagementUrl != "" {
|
||||
managementURL = msg.ManagementUrl
|
||||
}
|
||||
|
||||
config, err := internal.GetConfig(managementURL, s.configPath, msg.PresharedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.config = config
|
||||
|
||||
// login operation uses backoff scheme to connect to management API
|
||||
// we don't wait for result and return response immediately.
|
||||
if err := internal.Login(s.rootCtx, s.config, msg.SetupKey); err != nil {
|
||||
log.Errorf("failed login: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &proto.LoginResponse{}, nil
|
||||
}
|
||||
|
||||
// Up starts engine work in the daemon.
|
||||
func (s *Server) Up(_ context.Context, msg *proto.UpRequest) (*proto.UpResponse, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
state := internal.CtxGetState(s.rootCtx)
|
||||
|
||||
// if current state contains any error, return it
|
||||
// in all other cases we can continue execution only if status is idle and up command was
|
||||
// not in the progress or already successfully estabilished connection.
|
||||
status, err := state.Status()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status != internal.StatusIdle {
|
||||
return nil, fmt.Errorf("up already in progress: current status %s", status)
|
||||
}
|
||||
|
||||
// it should be nill here, but .
|
||||
if s.actCancel != nil {
|
||||
s.actCancel()
|
||||
}
|
||||
ctx, cancel := context.WithCancel(s.rootCtx)
|
||||
s.actCancel = cancel
|
||||
|
||||
if s.config == nil {
|
||||
return nil, fmt.Errorf("config is not defined, please call login command first")
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := internal.RunClient(ctx, s.config, s.stopCh, s.cleanupCh); err != nil {
|
||||
log.Errorf("run client connection: %v", state.Wrap(err))
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
return &proto.UpResponse{}, nil
|
||||
}
|
||||
|
||||
// Down dengine work in the daemon.
|
||||
func (s *Server) Down(ctx context.Context, msg *proto.DownRequest) (*proto.DownResponse, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if s.actCancel == nil {
|
||||
return nil, fmt.Errorf("service is not up")
|
||||
}
|
||||
s.actCancel()
|
||||
|
||||
return &proto.DownResponse{}, nil
|
||||
}
|
||||
|
||||
// Status starts engine work in the daemon.
|
||||
func (s *Server) Status(ctx context.Context, msg *proto.StatusRequest) (*proto.StatusResponse, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
status, err := internal.CtxGetState(s.rootCtx).Status()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &proto.StatusResponse{Status: string(status)}, nil
|
||||
}
|
||||
96
client/ui/client_ui.go
Normal file
96
client/ui/client_ui.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/getlantern/systray"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
"github.com/wiretrustee/wiretrustee/client/proto"
|
||||
"github.com/wiretrustee/wiretrustee/client/ui/config"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
func main() {
|
||||
systray.Run(onReady, nil)
|
||||
}
|
||||
|
||||
// TODO: implementation for SSO Logins
|
||||
func onReady() {
|
||||
wtIcon, err := ioutil.ReadFile("wiretrustee.ico")
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
if wtIcon != nil {
|
||||
systray.SetTemplateIcon(wtIcon, wtIcon)
|
||||
}
|
||||
|
||||
go func() {
|
||||
up := systray.AddMenuItem("Up", "Up")
|
||||
down := systray.AddMenuItem("Down", "Down")
|
||||
|
||||
mUrl := systray.AddMenuItem("Open UI", "wiretrustee website")
|
||||
systray.AddSeparator()
|
||||
|
||||
mQuitOrig := systray.AddMenuItem("Quit", "Quit the whole app")
|
||||
go func() {
|
||||
<-mQuitOrig.ClickedCh
|
||||
fmt.Println("Requesting quit")
|
||||
systray.Quit()
|
||||
fmt.Println("Finished quitting")
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-mUrl.ClickedCh:
|
||||
open.Run("https://app.wiretrustee.com")
|
||||
case <-up.ClickedCh:
|
||||
upCmdExec()
|
||||
case <-down.ClickedCh:
|
||||
fmt.Println("Clicked down")
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func handleUp() {
|
||||
// This is where
|
||||
}
|
||||
|
||||
func upCmdExec() {
|
||||
log.Println("executing up command..")
|
||||
cfg := config.Config()
|
||||
|
||||
ctx := internal.CtxInitState(context.Background())
|
||||
|
||||
conn, err := grpc.DialContext(ctx, cfg.DaemonAddr(),
|
||||
grpc.WithTimeout(5*time.Second),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithBlock())
|
||||
if err != nil {
|
||||
log.Errorf("failed to connect to service CLI interface; %v", err)
|
||||
return
|
||||
}
|
||||
daemonClient := proto.NewDaemonServiceClient(conn)
|
||||
|
||||
status, err := daemonClient.Status(ctx, &proto.StatusRequest{})
|
||||
if err != nil {
|
||||
log.Errorf("get status: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if status.Status != string(internal.StatusIdle) {
|
||||
log.Warnf("already connected")
|
||||
return
|
||||
}
|
||||
_, err = daemonClient.Up(ctx, &proto.UpRequest{})
|
||||
if err != nil {
|
||||
log.Errorf("Failed to start up client; %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
41
client/ui/config/config.go
Normal file
41
client/ui/config/config.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type ClientConfig struct {
|
||||
configPath string
|
||||
logFile string
|
||||
daemonAddr string
|
||||
}
|
||||
|
||||
// We are creating this package to extract utility functions from the cmd package
|
||||
// reading and parsing the configurations for the client should be done here
|
||||
func Config() *ClientConfig {
|
||||
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"
|
||||
}
|
||||
|
||||
defaultDaemonAddr := "unix:///var/run/wiretrustee.sock"
|
||||
if runtime.GOOS == "windows" {
|
||||
defaultDaemonAddr = "tcp://127.0.0.1:41731"
|
||||
}
|
||||
return &ClientConfig{
|
||||
configPath: defaultConfigPath,
|
||||
logFile: defaultLogFile,
|
||||
daemonAddr: defaultDaemonAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClientConfig) DaemonAddr() string {
|
||||
return c.daemonAddr
|
||||
}
|
||||
|
||||
func (c *ClientConfig) LogFile() string {
|
||||
return c.logFile
|
||||
}
|
||||
@@ -6,7 +6,7 @@ a 3rd party open-source STUN/TURN service [Coturn](https://github.com/coturn/cot
|
||||
|
||||
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.
|
||||
We focused on connectivity instead. It also offers an always free plan that should be ok for most users as its limits are high enough for most teams.
|
||||
|
||||
If you would like to learn more about the architecture please refer to the [Wiretrustee Architecture section](architecture.md).
|
||||
|
||||
@@ -17,10 +17,11 @@ If you would like to learn more about the architecture please refer to the [Wire
|
||||
### Requirements
|
||||
|
||||
- Virtual machine offered by any cloud provider (e.g., AWS, DigitalOcean, Hetzner, Google Cloud, Azure ...).
|
||||
- Any Linux OS.
|
||||
- Any Unix 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, 10000, 3478``` (Dashboard, Management HTTP API, Management gRpc API, Signal gRpc, Coturn STUN/TURN respectively) on your server.
|
||||
- Wiretrustee Open ports ```443, 33071, 33073, 10000``` (Dashboard, Management HTTP API, Management gRpc API, Signal gRpc) on your server.
|
||||
- Coturn is used for relay using the STUN/TURN protocols. It requires a listening port, ```UDP 3478```, and range of ports,```UDP 49152-65535```, for dynamic relay connections.
|
||||
- Maybe a cup of coffee or tea :)
|
||||
|
||||
### Step-by-step guide
|
||||
@@ -41,7 +42,7 @@ For this tutorial we will be using domain ```test.wiretrustee.com``` which point
|
||||
```
|
||||
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.
|
||||
To simplify the setup we have prepared a script to substitute required properties in the [turnserver.conf.tmpl](../infrastructure_files/turnserver.conf.tmpl),[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:
|
||||
|
||||
@@ -57,8 +58,9 @@ For this tutorial we will be using domain ```test.wiretrustee.com``` which point
|
||||
# e.g. hello@mydomain.com
|
||||
WIRETRUSTEE_LETSENCRYPT_EMAIL=""
|
||||
```
|
||||
> Other options are available, but they are automatically updated.
|
||||
|
||||
Please follow the steps to get the values.
|
||||
Please follow the steps to get the values.
|
||||
|
||||
4. Configure ```WIRETRUSTEE_AUTH0_DOMAIN``` ```WIRETRUSTEE_AUTH0_CLIENT_ID``` ```WIRETRUSTEE_AUTH0_AUDIENCE``` properties.
|
||||
|
||||
@@ -94,3 +96,9 @@ For this tutorial we will be using domain ```test.wiretrustee.com``` which point
|
||||
docker-compose logs management
|
||||
docker-compose logs coturn
|
||||
docker-compose logs dashboard
|
||||
|
||||
10. Once the server is running, you can access the dashboard by https://$WIRETRUSTEE_DOMAIN
|
||||
11. Adding a peer will require you to enter the management URL by following the steps in the page https://$WIRETRUSTEE_DOMAIN/add-peer and in the 3rd step:
|
||||
```shell
|
||||
sudo wiretrustee up --setup-key <PASTE-SETUP-KEY> --management-url https://$WIRETRUSTEE_DOMAIN:33073
|
||||
```
|
||||
22
go.mod
22
go.mod
@@ -17,7 +17,7 @@ require (
|
||||
github.com/spf13/cobra v1.3.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/vishvananda/netlink v1.1.0
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
|
||||
golang.zx2c4.com/wireguard v0.0.0-20211209221555-9c9e7e272434
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
|
||||
@@ -28,8 +28,10 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/getlantern/systray v1.2.0
|
||||
github.com/magiconair/properties v1.8.5
|
||||
github.com/rs/xid v1.3.0
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
github.com/stretchr/testify v1.7.0
|
||||
)
|
||||
|
||||
@@ -37,6 +39,13 @@ require (
|
||||
github.com/BurntSushi/toml v0.4.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
||||
@@ -44,18 +53,19 @@ require (
|
||||
github.com/mdlayher/netlink v1.4.2 // indirect
|
||||
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/pion/dtls/v2 v2.0.12 // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||
github.com/pion/dtls/v2 v2.1.2 // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/mdns v0.0.5 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/stun v0.3.5 // indirect
|
||||
github.com/pion/transport v0.12.3 // indirect
|
||||
github.com/pion/turn/v2 v2.0.5 // indirect
|
||||
github.com/pion/transport v0.13.0 // indirect
|
||||
github.com/pion/turn/v2 v2.0.7 // indirect
|
||||
github.com/pion/udp v0.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9 // indirect
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 // indirect
|
||||
golang.org/x/tools v0.1.8 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
@@ -67,3 +77,5 @@ require (
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
honnef.co/go/tools v0.2.2 // indirect
|
||||
)
|
||||
|
||||
replace github.com/pion/ice/v2 => github.com/wiretrustee/ice/v2 v2.1.21-0.20220218121004-dc81faead4bb
|
||||
|
||||
46
go.sum
46
go.sum
@@ -116,6 +116,20 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||
github.com/getlantern/systray v1.2.0 h1:MsAdOcmOnm4V+r3HFONDszdZeoj7E3q2dEvsPdsxXtI=
|
||||
github.com/getlantern/systray v1.2.0/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
|
||||
@@ -128,6 +142,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
@@ -353,13 +368,13 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pion/dtls/v2 v2.0.12 h1:QMSvNht7FM/XDXij3Ic90SCbl5yL7kppeI4ghfF4in8=
|
||||
github.com/pion/dtls/v2 v2.0.12/go.mod h1:5Pe3QJI0Ajsx+uCfxREeewGFlKYBzLrXe9ku7Y0oRXM=
|
||||
github.com/pion/ice/v2 v2.1.17 h1:z7aBWgs85AEeRgtj0bHnCrShzaGnZ/RS4pMoRmbYxtY=
|
||||
github.com/pion/ice/v2 v2.1.17/go.mod h1:M0MJ/tBR3IyDcaJv49hAiHEzaVBqWCV/MuWqIffBsrw=
|
||||
github.com/pion/dtls/v2 v2.1.2 h1:22Q1Jk9L++Yo7BIf9130MonNPfPVb+YgdYLeyQotuAA=
|
||||
github.com/pion/dtls/v2 v2.1.2/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
||||
@@ -368,12 +383,11 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
|
||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/transport v0.12.3 h1:vdBfvfU/0Wq8kd2yhUMSDB/x+O4Z9MYVl2fJ5BT4JZw=
|
||||
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
||||
github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
|
||||
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
|
||||
github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY=
|
||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
||||
github.com/pion/turn/v2 v2.0.7 h1:SZhc00WDovK6czaN1RSiHqbwANtIO6wfZQsU0m0KNE8=
|
||||
github.com/pion/turn/v2 v2.0.7/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -409,6 +423,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
@@ -436,6 +452,8 @@ github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJ
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/wiretrustee/ice/v2 v2.1.21-0.20220218121004-dc81faead4bb h1:CU1/+CEeCPvYXgfAyqTJXSQSf6hW3wsWM6Dfz6HkHEQ=
|
||||
github.com/wiretrustee/ice/v2 v2.1.21-0.20220218121004-dc81faead4bb/go.mod h1:XT1Nrb4OxbVFPffbQMbq4PaeEkpRLVzdphh3fjrw7DY=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -469,10 +487,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE=
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -570,8 +587,8 @@ golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9 h1:kmreh1vGI63l2FxOAYS3Yv6ATsi7lSTuwNSVbGfJV9I=
|
||||
golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -694,6 +711,7 @@ golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
const (
|
||||
DefaultMTU = 1280
|
||||
DefaultWgPort = 51820
|
||||
)
|
||||
|
||||
// WGIface represents a interface instance
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build linux || windows
|
||||
// +build linux windows
|
||||
|
||||
package iface
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package iface
|
||||
|
||||
@@ -1,7 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
unset $(grep -v '^#' ./setup.env | sed -E 's/(.*)=.*/\1/' | xargs)
|
||||
export $(grep -v '^#' ./setup.env | xargs)
|
||||
source setup.env
|
||||
|
||||
if [[ "x-$WIRETRUSTEE_DOMAIN" == "x-" ]]
|
||||
then
|
||||
echo WIRETRUSTEE_DOMAIN is not set, please update your setup.env file
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# local development or tests
|
||||
if [[ $WIRETRUSTEE_DOMAIN == "localhost" || $WIRETRUSTEE_DOMAIN == "127.0.0.1" ]]
|
||||
then
|
||||
export WIRETRUSTEE_MGMT_API_ENDPOINT=http://$WIRETRUSTEE_DOMAIN:$WIRETRUSTEE_MGMT_API_PORT
|
||||
unset WIRETRUSTEE_MGMT_API_CERT_FILE
|
||||
unset WIRETRUSTEE_MGMT_API_CERT_KEY_FILE
|
||||
fi
|
||||
|
||||
# if not provided, we generate a turn password
|
||||
if [[ "x-$TURN_PASSWORD" == "x-" ]]
|
||||
then
|
||||
export TURN_PASSWORD=$(openssl rand -base64 32|sed 's/=//g')
|
||||
fi
|
||||
|
||||
envsubst < docker-compose.yml.tmpl > docker-compose.yml
|
||||
envsubst < management.json.tmpl > management.json
|
||||
envsubst < turnserver.conf.tmpl > turnserver.conf
|
||||
@@ -11,19 +11,18 @@ services:
|
||||
- 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
|
||||
- WIRETRUSTEE_MGMT_API_ENDPOINT=$WIRETRUSTEE_MGMT_API_ENDPOINT
|
||||
- NGINX_SSL_PORT=443
|
||||
- LETSENCRYPT_DOMAIN=$WIRETRUSTEE_DOMAIN
|
||||
- LETSENCRYPT_EMAIL=$WIRETRUSTEE_LETSENCRYPT_EMAIL
|
||||
volumes:
|
||||
- /var/lib/wiretrustee/dashboard/letsencrypt:/etc/letsencrypt/
|
||||
- wiretrustee-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
|
||||
@@ -37,12 +36,11 @@ services:
|
||||
- dashboard
|
||||
volumes:
|
||||
- wiretrustee-mgmt:/var/lib/wiretrustee
|
||||
- /var/lib/wiretrustee/dashboard/letsencrypt:/etc/letsencrypt:ro
|
||||
- wiretrustee-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
|
||||
- $WIRETRUSTEE_MGMT_API_PORT:33071 #API port
|
||||
# # port and command for Let's Encrypt validation
|
||||
# - 443:443
|
||||
# command: ["--letsencrypt-domain", "$WIRETRUSTEE_DOMAIN", "--log-file", "console"]
|
||||
@@ -50,7 +48,7 @@ services:
|
||||
coturn:
|
||||
image: coturn/coturn
|
||||
restart: unless-stopped
|
||||
domainname: <YOUR DOMAIN>
|
||||
domainname: $WIRETRUSTEE_DOMAIN
|
||||
volumes:
|
||||
- ./turnserver.conf:/etc/turnserver.conf:ro
|
||||
# - ./privkey.pem:/etc/coturn/private/privkey.pem:ro
|
||||
@@ -58,4 +56,5 @@ services:
|
||||
network_mode: host
|
||||
volumes:
|
||||
wiretrustee-mgmt:
|
||||
wiretrustee-signal:
|
||||
wiretrustee-signal:
|
||||
wiretrustee-letsencrypt:
|
||||
@@ -12,8 +12,8 @@
|
||||
{
|
||||
"Proto": "udp",
|
||||
"URI": "turn:$WIRETRUSTEE_DOMAIN:3478",
|
||||
"Username": "",
|
||||
"Password": null
|
||||
"Username": "$TURN_USER",
|
||||
"Password": "$TURN_PASSWORD"
|
||||
}
|
||||
],
|
||||
"CredentialsTTL": "12h",
|
||||
@@ -28,22 +28,14 @@
|
||||
},
|
||||
"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",
|
||||
"Address": "0.0.0.0:$WIRETRUSTEE_MGMT_API_PORT",
|
||||
"AuthIssuer": "https://$WIRETRUSTEE_AUTH0_DOMAIN/",
|
||||
"AuthAudience": "$WIRETRUSTEE_AUTH0_AUDIENCE",
|
||||
"AuthKeysLocation": "https://$WIRETRUSTEE_AUTH0_DOMAIN/.well-known/jwks.json"
|
||||
"AuthKeysLocation": "https://$WIRETRUSTEE_AUTH0_DOMAIN/.well-known/jwks.json",
|
||||
"CertFile":"$WIRETRUSTEE_MGMT_API_CERT_FILE",
|
||||
"CertKey":"$WIRETRUSTEE_MGMT_API_CERT_KEY_FILE"
|
||||
},
|
||||
"IdpManagerConfig": {
|
||||
"Manager": "none",
|
||||
"Auth0ClientCredentials": {
|
||||
"Audience": "<PASTE YOUR AUTH0 AUDIENCE HERE>",
|
||||
"AuthIssuer": "<PASTE YOUR AUTH0 Auth Issuer HERE>",
|
||||
"ClientId": "<PASTE YOUR AUTH0 Application Client ID HERE>",
|
||||
"ClientSecret": "<PASTE YOUR AUTH0 Application Client Secret HERE>",
|
||||
"GrantType": "client_credentials"
|
||||
}
|
||||
"Manager": "none"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
# e.g. app.mydomain.com
|
||||
# Dashboard domain and auth0 configuration
|
||||
|
||||
# Dashboard domain. e.g. app.mydomain.com
|
||||
WIRETRUSTEE_DOMAIN=""
|
||||
# e.g. dev-24vkclam.us.auth0.com
|
||||
WIRETRUSTEE_AUTH0_DOMAIN=""
|
||||
@@ -8,3 +10,42 @@ WIRETRUSTEE_AUTH0_CLIENT_ID=""
|
||||
WIRETRUSTEE_AUTH0_AUDIENCE=""
|
||||
# e.g. hello@mydomain.com
|
||||
WIRETRUSTEE_LETSENCRYPT_EMAIL=""
|
||||
|
||||
## From this point, most settings are being done automatically, but you can edit if you need some customization
|
||||
|
||||
# Management API
|
||||
|
||||
# Management API port
|
||||
WIRETRUSTEE_MGMT_API_PORT=33071
|
||||
# Management API endpoint address, used by the Dashboard
|
||||
WIRETRUSTEE_MGMT_API_ENDPOINT=https://$WIRETRUSTEE_DOMAIN:$WIRETRUSTEE_MGMT_API_PORT
|
||||
# Management Certficate file path. These are generated by the Dashboard container
|
||||
WIRETRUSTEE_MGMT_API_CERT_FILE="/etc/letsencrypt/live/$WIRETRUSTEE_DOMAIN/fullchain.pem"
|
||||
# Management Certficate key file path.
|
||||
WIRETRUSTEE_MGMT_API_CERT_KEY_FILE="/etc/letsencrypt/live/$WIRETRUSTEE_DOMAIN/privkey.pem"
|
||||
|
||||
# Turn credentials
|
||||
|
||||
# User
|
||||
TURN_USER=self
|
||||
# Password. If empty, the configure.sh will generate one with openssl
|
||||
TURN_PASSWORD=
|
||||
# Min port
|
||||
TURN_MIN_PORT=49152
|
||||
# Max port
|
||||
TURN_MAX_PORT=65535
|
||||
|
||||
# exports
|
||||
export WIRETRUSTEE_DOMAIN
|
||||
export WIRETRUSTEE_AUTH0_DOMAIN
|
||||
export WIRETRUSTEE_AUTH0_CLIENT_ID
|
||||
export WIRETRUSTEE_AUTH0_AUDIENCE
|
||||
export WIRETRUSTEE_LETSENCRYPT_EMAIL
|
||||
export WIRETRUSTEE_MGMT_API_PORT
|
||||
export WIRETRUSTEE_MGMT_API_ENDPOINT
|
||||
export WIRETRUSTEE_MGMT_API_CERT_FILE
|
||||
export WIRETRUSTEE_MGMT_API_CERT_KEY_FILE
|
||||
export TURN_USER
|
||||
export TURN_PASSWORD
|
||||
export TURN_MIN_PORT
|
||||
export TURN_MAX_PORT
|
||||
|
||||
@@ -154,12 +154,12 @@ tls-listening-port=5349
|
||||
# Lower and upper bounds of the UDP relay endpoints:
|
||||
# (default values are 49152 and 65535)
|
||||
#
|
||||
min-port=49152
|
||||
max-port=65535
|
||||
min-port=$TURN_MIN_PORT
|
||||
max-port=$TURN_MAX_PORT
|
||||
|
||||
# Uncomment to run TURN server in 'normal' 'moderate' verbose mode.
|
||||
# By default the verbose mode is off.
|
||||
verbose
|
||||
#verbose
|
||||
|
||||
# Uncomment to run TURN server in 'extra' verbose mode.
|
||||
# This mode is very annoying and produces lots of output.
|
||||
@@ -249,7 +249,7 @@ lt-cred-mech
|
||||
#user=username1:key1
|
||||
#user=username2:key2
|
||||
# OR:
|
||||
user=username1:password1
|
||||
user=$TURN_USER:$TURN_PASSWORD
|
||||
#user=username2:password2
|
||||
#
|
||||
# Keys must be generated by turnadmin utility. The key value depends
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"github.com/wiretrustee/wiretrustee/management/proto"
|
||||
mgmtProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/management/server/mock_server"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc"
|
||||
@@ -25,7 +27,7 @@ import (
|
||||
|
||||
var tested *GrpcClient
|
||||
var serverAddr string
|
||||
var mgmtMockServer *mgmt.ManagementServiceServerMock
|
||||
var mgmtMockServer *mock_server.ManagementServiceServerMock
|
||||
var serverKey wgtypes.Key
|
||||
|
||||
const ValidKey = "A2C8E62B-38F5-4553-B31E-DD66C696CEBB"
|
||||
@@ -100,7 +102,7 @@ func startMockManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mgmtMockServer = &mgmt.ManagementServiceServerMock{
|
||||
mgmtMockServer = &mock_server.ManagementServiceServerMock{
|
||||
GetServerKeyFunc: func(context.Context, *proto.Empty) (*proto.ServerKeyResponse, error) {
|
||||
response := &proto.ServerKeyResponse{
|
||||
Key: serverKey.PublicKey().String(),
|
||||
|
||||
@@ -29,7 +29,6 @@ type GrpcClient struct {
|
||||
|
||||
// NewClient creates a new client to Management service
|
||||
func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*GrpcClient, error) {
|
||||
|
||||
transportOption := grpc.WithTransportCredentials(insecure.NewCredentials())
|
||||
|
||||
if tlsEnabled {
|
||||
@@ -47,7 +46,6 @@ func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsE
|
||||
Time: 15 * time.Second,
|
||||
Timeout: 10 * time.Second,
|
||||
}))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("failed creating connection to Management Service %v", err)
|
||||
return nil, err
|
||||
@@ -68,14 +66,14 @@ func (c *GrpcClient) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
//defaultBackoff is a basic backoff mechanism for general issues
|
||||
// defaultBackoff is a basic backoff mechanism for general issues
|
||||
func defaultBackoff(ctx context.Context) backoff.BackOff {
|
||||
return backoff.WithContext(&backoff.ExponentialBackOff{
|
||||
InitialInterval: 800 * time.Millisecond,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: 10 * time.Second,
|
||||
MaxElapsedTime: 12 * time.Hour, //stop after 12 hours of trying, the error will be propagated to the general retry of the client
|
||||
MaxElapsedTime: 12 * time.Hour, // stop after 12 hours of trying, the error will be propagated to the general retry of the client
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}, ctx)
|
||||
@@ -90,11 +88,9 @@ func (c *GrpcClient) ready() bool {
|
||||
// Sync wraps the real client's Sync endpoint call and takes care of retries and encryption/decryption of messages
|
||||
// Blocking request. The result will be sent via msgHandler callback function
|
||||
func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error {
|
||||
|
||||
var backOff = defaultBackoff(c.ctx)
|
||||
backOff := defaultBackoff(c.ctx)
|
||||
|
||||
operation := func() error {
|
||||
|
||||
log.Debugf("management connection state %v", c.conn.GetState())
|
||||
|
||||
if !c.ready() {
|
||||
@@ -215,7 +211,6 @@ func (c *GrpcClient) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*pro
|
||||
WgPubKey: c.key.PublicKey().String(),
|
||||
Body: loginReq,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -69,10 +69,15 @@ var (
|
||||
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
||||
}
|
||||
peersUpdateManager := server.NewPeersUpdateManager()
|
||||
idpManager, err := idp.NewManager(*config.IdpManagerConfig)
|
||||
if err != nil {
|
||||
log.Fatalln("failed retrieving a new idp manager with err: ", err)
|
||||
|
||||
var idpManager idp.Manager
|
||||
if config.IdpManagerConfig != nil {
|
||||
idpManager, err = idp.NewManager(*config.IdpManagerConfig)
|
||||
if err != nil {
|
||||
log.Fatalln("failed retrieving a new idp manager with err: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
accountManager := server.NewManager(store, peersUpdateManager, idpManager)
|
||||
|
||||
var opts []grpc.ServerOption
|
||||
|
||||
@@ -4,13 +4,41 @@ import (
|
||||
"github.com/rs/xid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/management/server/idp"
|
||||
"github.com/wiretrustee/wiretrustee/management/server/jwtclaims"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type AccountManager struct {
|
||||
const (
|
||||
PublicCategory = "public"
|
||||
PrivateCategory = "private"
|
||||
UnknownCategory = "unknown"
|
||||
)
|
||||
|
||||
type AccountManager interface {
|
||||
GetOrCreateAccountByUser(userId, domain string) (*Account, error)
|
||||
GetAccountByUser(userId string) (*Account, error)
|
||||
AddSetupKey(accountId string, keyName string, keyType SetupKeyType, expiresIn *util.Duration) (*SetupKey, error)
|
||||
RevokeSetupKey(accountId string, keyId string) (*SetupKey, error)
|
||||
RenameSetupKey(accountId string, keyId string, newName string) (*SetupKey, error)
|
||||
GetAccountById(accountId string) (*Account, error)
|
||||
GetAccountByUserOrAccountId(userId, accountId, domain string) (*Account, error)
|
||||
GetAccountWithAuthorizationClaims(claims jwtclaims.AuthorizationClaims) (*Account, error)
|
||||
AccountExists(accountId string) (*bool, error)
|
||||
AddAccount(accountId, userId, domain string) (*Account, error)
|
||||
GetPeer(peerKey string) (*Peer, error)
|
||||
MarkPeerConnected(peerKey string, connected bool) error
|
||||
RenamePeer(accountId string, peerKey string, newName string) (*Peer, error)
|
||||
DeletePeer(accountId string, peerKey string) (*Peer, error)
|
||||
GetPeerByIP(accountId string, peerIP string) (*Peer, error)
|
||||
GetNetworkMap(peerKey string) (*NetworkMap, error)
|
||||
AddPeer(setupKey string, peer *Peer) (*Peer, error)
|
||||
}
|
||||
|
||||
type DefaultAccountManager struct {
|
||||
Store Store
|
||||
// mutex to synchronise account operations (e.g. generating Peer IP address inside the Network)
|
||||
mux sync.Mutex
|
||||
@@ -22,12 +50,14 @@ type AccountManager struct {
|
||||
type Account struct {
|
||||
Id string
|
||||
// User.Id it was created by
|
||||
CreatedBy string
|
||||
Domain string
|
||||
SetupKeys map[string]*SetupKey
|
||||
Network *Network
|
||||
Peers map[string]*Peer
|
||||
Users map[string]*User
|
||||
CreatedBy string
|
||||
Domain string
|
||||
DomainCategory string
|
||||
IsDomainPrimaryAccount bool
|
||||
SetupKeys map[string]*SetupKey
|
||||
Network *Network
|
||||
Peers map[string]*Peer
|
||||
Users map[string]*User
|
||||
}
|
||||
|
||||
// NewAccount creates a new Account with a generated ID and generated default setup keys
|
||||
@@ -62,9 +92,9 @@ func (a *Account) Copy() *Account {
|
||||
}
|
||||
}
|
||||
|
||||
// NewManager creates a new AccountManager with a provided Store
|
||||
func NewManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager) *AccountManager {
|
||||
return &AccountManager{
|
||||
// NewManager creates a new DefaultAccountManager with a provided Store
|
||||
func NewManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager) *DefaultAccountManager {
|
||||
return &DefaultAccountManager{
|
||||
Store: store,
|
||||
mux: sync.Mutex{},
|
||||
peersUpdateManager: peersUpdateManager,
|
||||
@@ -73,7 +103,7 @@ func NewManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager
|
||||
}
|
||||
|
||||
//AddSetupKey generates a new setup key with a given name and type, and adds it to the specified account
|
||||
func (am *AccountManager) AddSetupKey(accountId string, keyName string, keyType SetupKeyType, expiresIn *util.Duration) (*SetupKey, error) {
|
||||
func (am *DefaultAccountManager) AddSetupKey(accountId string, keyName string, keyType SetupKeyType, expiresIn *util.Duration) (*SetupKey, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
@@ -99,7 +129,7 @@ func (am *AccountManager) AddSetupKey(accountId string, keyName string, keyType
|
||||
}
|
||||
|
||||
//RevokeSetupKey marks SetupKey as revoked - becomes not valid anymore
|
||||
func (am *AccountManager) RevokeSetupKey(accountId string, keyId string) (*SetupKey, error) {
|
||||
func (am *DefaultAccountManager) RevokeSetupKey(accountId string, keyId string) (*SetupKey, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
@@ -125,7 +155,7 @@ func (am *AccountManager) RevokeSetupKey(accountId string, keyId string) (*Setup
|
||||
}
|
||||
|
||||
//RenameSetupKey renames existing setup key of the specified account.
|
||||
func (am *AccountManager) RenameSetupKey(accountId string, keyId string, newName string) (*SetupKey, error) {
|
||||
func (am *DefaultAccountManager) RenameSetupKey(accountId string, keyId string, newName string) (*SetupKey, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
@@ -151,7 +181,7 @@ func (am *AccountManager) RenameSetupKey(accountId string, keyId string, newName
|
||||
}
|
||||
|
||||
//GetAccountById returns an existing account using its ID or error (NotFound) if doesn't exist
|
||||
func (am *AccountManager) GetAccountById(accountId string) (*Account, error) {
|
||||
func (am *DefaultAccountManager) GetAccountById(accountId string) (*Account, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
@@ -165,7 +195,7 @@ func (am *AccountManager) GetAccountById(accountId string) (*Account, error) {
|
||||
|
||||
//GetAccountByUserOrAccountId look for an account by user or account Id, if no account is provided and
|
||||
// user id doesn't have an account associated with it, one account is created
|
||||
func (am *AccountManager) GetAccountByUserOrAccountId(userId, accountId, domain string) (*Account, error) {
|
||||
func (am *DefaultAccountManager) GetAccountByUserOrAccountId(userId, accountId, domain string) (*Account, error) {
|
||||
|
||||
if accountId != "" {
|
||||
return am.GetAccountById(accountId)
|
||||
@@ -174,12 +204,9 @@ func (am *AccountManager) GetAccountByUserOrAccountId(userId, accountId, domain
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "account not found using user id: %s", userId)
|
||||
}
|
||||
// update idp manager app metadata
|
||||
if am.idpManager != nil {
|
||||
err = am.idpManager.UpdateUserAppMetadata(userId, idp.AppMetadata{WTAccountId: account.Id})
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "updating user's app metadata failed with: %v", err)
|
||||
}
|
||||
err = am.updateIDPMetadata(userId, account.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
@@ -187,8 +214,139 @@ func (am *AccountManager) GetAccountByUserOrAccountId(userId, accountId, domain
|
||||
return nil, status.Errorf(codes.NotFound, "no valid user or account Id provided")
|
||||
}
|
||||
|
||||
// updateIDPMetadata update user's app metadata in idp manager
|
||||
func (am *DefaultAccountManager) updateIDPMetadata(userId, accountID string) error {
|
||||
if am.idpManager != nil {
|
||||
err := am.idpManager.UpdateUserAppMetadata(userId, idp.AppMetadata{WTAccountId: accountID})
|
||||
if err != nil {
|
||||
return status.Errorf(codes.Internal, "updating user's app metadata failed with: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateAccountDomainAttributes updates the account domain attributes and then, saves the account
|
||||
func (am *DefaultAccountManager) updateAccountDomainAttributes(account *Account, claims jwtclaims.AuthorizationClaims, primaryDomain bool) error {
|
||||
account.IsDomainPrimaryAccount = primaryDomain
|
||||
account.Domain = strings.ToLower(claims.Domain)
|
||||
account.DomainCategory = claims.DomainCategory
|
||||
err := am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return status.Errorf(codes.Internal, "failed saving updated account")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleExistingUserAccount handles existing User accounts and update its domain attributes.
|
||||
//
|
||||
//
|
||||
// If there is no primary domain account yet, we set the account as primary for the domain. Otherwise,
|
||||
// we compare the account's ID with the domain account ID, and if they don't match, we set the account as
|
||||
// non-primary account for the domain. We don't merge accounts at this stage, because of cases when a domain
|
||||
// was previously unclassified or classified as public so N users that logged int that time, has they own account
|
||||
// and peers that shouldn't be lost.
|
||||
func (am *DefaultAccountManager) handleExistingUserAccount(existingAcc *Account, domainAcc *Account, claims jwtclaims.AuthorizationClaims) error {
|
||||
var err error
|
||||
|
||||
if domainAcc == nil || existingAcc.Id != domainAcc.Id {
|
||||
err = am.updateAccountDomainAttributes(existingAcc, claims, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// we should register the account ID to this user's metadata in our IDP manager
|
||||
err = am.updateIDPMetadata(claims.UserId, existingAcc.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleNewUserAccount validates if there is an existing primary account for the domain, if so it adds the new user to that account,
|
||||
// otherwise it will create a new account and make it primary account for the domain.
|
||||
func (am *DefaultAccountManager) handleNewUserAccount(domainAcc *Account, claims jwtclaims.AuthorizationClaims) (*Account, error) {
|
||||
var (
|
||||
account *Account
|
||||
primaryAccount bool
|
||||
)
|
||||
lowerDomain := strings.ToLower(claims.Domain)
|
||||
// if domain already has a primary account, add regular user
|
||||
if domainAcc != nil {
|
||||
account = domainAcc
|
||||
account.Users[claims.UserId] = NewRegularUser(claims.UserId)
|
||||
primaryAccount = false
|
||||
} else {
|
||||
account = NewAccount(claims.UserId, lowerDomain)
|
||||
account.Users[claims.UserId] = NewAdminUser(claims.UserId)
|
||||
primaryAccount = true
|
||||
}
|
||||
|
||||
err := am.updateAccountDomainAttributes(account, claims, primaryAccount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = am.updateIDPMetadata(claims.UserId, account.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// GetAccountWithAuthorizationClaims retrievs an account using JWT Claims.
|
||||
// if domain is of the PrivateCategory category, it will evaluate
|
||||
// if account is new, existing or if there is another account with the same domain
|
||||
//
|
||||
// Use cases:
|
||||
//
|
||||
// New user + New account + New domain -> create account, user role = admin (if private domain, index domain)
|
||||
//
|
||||
// New user + New account + Existing Private Domain -> add user to the existing account, user role = regular (not admin)
|
||||
//
|
||||
// New user + New account + Existing Public Domain -> create account, user role = admin
|
||||
//
|
||||
// Existing user + Existing account + Existing Domain -> Nothing changes (if private, index domain)
|
||||
//
|
||||
// Existing user + Existing account + Existing Indexed Domain -> Nothing changes
|
||||
//
|
||||
// Existing user + Existing account + Existing domain reclassified Domain as private -> Nothing changes (index domain)
|
||||
func (am *DefaultAccountManager) GetAccountWithAuthorizationClaims(claims jwtclaims.AuthorizationClaims) (*Account, error) {
|
||||
// if Account ID is part of the claims
|
||||
// it means that we've already classified the domain and user has an account
|
||||
if claims.DomainCategory != PrivateCategory || claims.AccountId != "" {
|
||||
return am.GetAccountByUserOrAccountId(claims.UserId, claims.AccountId, claims.Domain)
|
||||
}
|
||||
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
// We checked if the domain has a primary account already
|
||||
domainAccount, err := am.Store.GetAccountByPrivateDomain(claims.Domain)
|
||||
accStatus, _ := status.FromError(err)
|
||||
if accStatus.Code() != codes.OK && accStatus.Code() != codes.NotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
account, err := am.Store.GetUserAccount(claims.UserId)
|
||||
if err == nil {
|
||||
err = am.handleExistingUserAccount(account, domainAccount, claims)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return account, nil
|
||||
} else if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound {
|
||||
return am.handleNewUserAccount(domainAccount, claims)
|
||||
} else {
|
||||
// other error
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
//AccountExists checks whether account exists (returns true) or not (returns false)
|
||||
func (am *AccountManager) AccountExists(accountId string) (*bool, error) {
|
||||
func (am *DefaultAccountManager) AccountExists(accountId string) (*bool, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
@@ -208,7 +366,7 @@ func (am *AccountManager) AccountExists(accountId string) (*bool, error) {
|
||||
}
|
||||
|
||||
// AddAccount generates a new Account with a provided accountId and userId, saves to the Store
|
||||
func (am *AccountManager) AddAccount(accountId, userId, domain string) (*Account, error) {
|
||||
func (am *DefaultAccountManager) AddAccount(accountId, userId, domain string) (*Account, error) {
|
||||
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
@@ -217,7 +375,7 @@ func (am *AccountManager) AddAccount(accountId, userId, domain string) (*Account
|
||||
|
||||
}
|
||||
|
||||
func (am *AccountManager) createAccount(accountId, userId, domain string) (*Account, error) {
|
||||
func (am *DefaultAccountManager) createAccount(accountId, userId, domain string) (*Account, error) {
|
||||
account := newAccountWithId(accountId, userId, domain)
|
||||
|
||||
err := am.Store.SaveAccount(account)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wiretrustee/wiretrustee/management/server/jwtclaims"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"net"
|
||||
"testing"
|
||||
@@ -32,6 +34,154 @@ func TestAccountManager_GetOrCreateAccountByUser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
||||
|
||||
type initUserParams jwtclaims.AuthorizationClaims
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
inputClaims jwtclaims.AuthorizationClaims
|
||||
inputInitUserParams initUserParams
|
||||
inputUpdateAttrs bool
|
||||
testingFunc require.ComparisonAssertionFunc
|
||||
expectedMSG string
|
||||
expectedUserRole UserRole
|
||||
}
|
||||
|
||||
var (
|
||||
publicDomain = "public.com"
|
||||
privateDomain = "private.com"
|
||||
unknownDomain = "unknown.com"
|
||||
)
|
||||
|
||||
defaultInitAccount := initUserParams{
|
||||
Domain: publicDomain,
|
||||
UserId: "defaultUser",
|
||||
}
|
||||
|
||||
testCase1 := test{
|
||||
name: "New User With Public Domain",
|
||||
inputClaims: jwtclaims.AuthorizationClaims{
|
||||
Domain: publicDomain,
|
||||
UserId: "pub-domain-user",
|
||||
DomainCategory: PublicCategory,
|
||||
},
|
||||
inputInitUserParams: defaultInitAccount,
|
||||
testingFunc: require.NotEqual,
|
||||
expectedMSG: "account IDs shouldn't match",
|
||||
expectedUserRole: UserRoleAdmin,
|
||||
}
|
||||
|
||||
initUnknown := defaultInitAccount
|
||||
initUnknown.DomainCategory = UnknownCategory
|
||||
initUnknown.Domain = unknownDomain
|
||||
|
||||
testCase2 := test{
|
||||
name: "New User With Unknown Domain",
|
||||
inputClaims: jwtclaims.AuthorizationClaims{
|
||||
Domain: unknownDomain,
|
||||
UserId: "unknown-domain-user",
|
||||
DomainCategory: UnknownCategory,
|
||||
},
|
||||
inputInitUserParams: initUnknown,
|
||||
testingFunc: require.NotEqual,
|
||||
expectedMSG: "account IDs shouldn't match",
|
||||
expectedUserRole: UserRoleAdmin,
|
||||
}
|
||||
|
||||
testCase3 := test{
|
||||
name: "New User With Private Domain",
|
||||
inputClaims: jwtclaims.AuthorizationClaims{
|
||||
Domain: privateDomain,
|
||||
UserId: "pvt-domain-user",
|
||||
DomainCategory: PrivateCategory,
|
||||
},
|
||||
inputInitUserParams: defaultInitAccount,
|
||||
testingFunc: require.NotEqual,
|
||||
expectedMSG: "account IDs shouldn't match",
|
||||
expectedUserRole: UserRoleAdmin,
|
||||
}
|
||||
|
||||
privateInitAccount := defaultInitAccount
|
||||
privateInitAccount.Domain = privateDomain
|
||||
privateInitAccount.DomainCategory = PrivateCategory
|
||||
|
||||
testCase4 := test{
|
||||
name: "New Regular User With Existing Private Domain",
|
||||
inputClaims: jwtclaims.AuthorizationClaims{
|
||||
Domain: privateDomain,
|
||||
UserId: "pvt-domain-user",
|
||||
DomainCategory: PrivateCategory,
|
||||
},
|
||||
inputUpdateAttrs: true,
|
||||
inputInitUserParams: privateInitAccount,
|
||||
testingFunc: require.Equal,
|
||||
expectedMSG: "account IDs should match",
|
||||
expectedUserRole: UserRoleUser,
|
||||
}
|
||||
|
||||
testCase5 := test{
|
||||
name: "Existing User With Existing Reclassified Private Domain",
|
||||
inputClaims: jwtclaims.AuthorizationClaims{
|
||||
Domain: defaultInitAccount.Domain,
|
||||
UserId: defaultInitAccount.UserId,
|
||||
DomainCategory: PrivateCategory,
|
||||
},
|
||||
inputInitUserParams: defaultInitAccount,
|
||||
testingFunc: require.Equal,
|
||||
expectedMSG: "account IDs should match",
|
||||
expectedUserRole: UserRoleAdmin,
|
||||
}
|
||||
|
||||
for _, testCase := range []test{testCase1, testCase2, testCase3, testCase4, testCase5} {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
|
||||
manager, err := createManager(t)
|
||||
require.NoError(t, err, "unable to create account manager")
|
||||
|
||||
initAccount, err := manager.GetAccountByUserOrAccountId(testCase.inputInitUserParams.UserId, testCase.inputInitUserParams.AccountId, testCase.inputInitUserParams.Domain)
|
||||
require.NoError(t, err, "create init user failed")
|
||||
|
||||
if testCase.inputUpdateAttrs {
|
||||
err = manager.updateAccountDomainAttributes(initAccount, jwtclaims.AuthorizationClaims{UserId: testCase.inputInitUserParams.UserId, Domain: testCase.inputInitUserParams.Domain, DomainCategory: testCase.inputInitUserParams.DomainCategory}, true)
|
||||
require.NoError(t, err, "update init user failed")
|
||||
}
|
||||
|
||||
account, err := manager.GetAccountWithAuthorizationClaims(testCase.inputClaims)
|
||||
require.NoError(t, err, "support function failed")
|
||||
|
||||
testCase.testingFunc(t, initAccount.Id, account.Id, testCase.expectedMSG)
|
||||
|
||||
require.EqualValues(t, testCase.expectedUserRole, account.Users[testCase.inputClaims.UserId].Role, "user role should match")
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestAccountManager_PrivateAccount(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
userId := "test_user"
|
||||
account, err := manager.GetOrCreateAccountByUser(userId, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if account == nil {
|
||||
t.Fatalf("expected to create an account for a user %s", userId)
|
||||
}
|
||||
|
||||
account, err = manager.GetAccountByUser(userId)
|
||||
if err != nil {
|
||||
t.Errorf("expected to get existing account after creation, no account was found for a user %s", userId)
|
||||
}
|
||||
|
||||
if account != nil && account.Users[userId] == nil {
|
||||
t.Fatalf("expected to create an account for a user %s but no user was found after creation udner the account %s", userId, account.Id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountManager_SetOrUpdateDomain(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
@@ -320,7 +470,7 @@ func TestAccountManager_DeletePeer(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func createManager(t *testing.T) (*AccountManager, error) {
|
||||
func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
store, err := createStore(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -17,10 +17,11 @@ const storeFileName = "store.json"
|
||||
|
||||
// FileStore represents an account storage backed by a file persisted to disk
|
||||
type FileStore struct {
|
||||
Accounts map[string]*Account
|
||||
SetupKeyId2AccountId map[string]string `json:"-"`
|
||||
PeerKeyId2AccountId map[string]string `json:"-"`
|
||||
UserId2AccountId map[string]string `json:"-"`
|
||||
Accounts map[string]*Account
|
||||
SetupKeyId2AccountId map[string]string `json:"-"`
|
||||
PeerKeyId2AccountId map[string]string `json:"-"`
|
||||
UserId2AccountId map[string]string `json:"-"`
|
||||
PrivateDomain2AccountId map[string]string `json:"-"`
|
||||
|
||||
// mutex to synchronise Store read/write operations
|
||||
mux sync.Mutex `json:"-"`
|
||||
@@ -42,12 +43,13 @@ func restore(file string) (*FileStore, error) {
|
||||
if _, err := os.Stat(file); os.IsNotExist(err) {
|
||||
// create a new FileStore if previously didn't exist (e.g. first run)
|
||||
s := &FileStore{
|
||||
Accounts: make(map[string]*Account),
|
||||
mux: sync.Mutex{},
|
||||
SetupKeyId2AccountId: make(map[string]string),
|
||||
PeerKeyId2AccountId: make(map[string]string),
|
||||
UserId2AccountId: make(map[string]string),
|
||||
storeFile: file,
|
||||
Accounts: make(map[string]*Account),
|
||||
mux: sync.Mutex{},
|
||||
SetupKeyId2AccountId: make(map[string]string),
|
||||
PeerKeyId2AccountId: make(map[string]string),
|
||||
UserId2AccountId: make(map[string]string),
|
||||
PrivateDomain2AccountId: make(map[string]string),
|
||||
storeFile: file,
|
||||
}
|
||||
|
||||
err = s.persist(file)
|
||||
@@ -68,6 +70,7 @@ func restore(file string) (*FileStore, error) {
|
||||
store.SetupKeyId2AccountId = make(map[string]string)
|
||||
store.PeerKeyId2AccountId = make(map[string]string)
|
||||
store.UserId2AccountId = make(map[string]string)
|
||||
store.PrivateDomain2AccountId = make(map[string]string)
|
||||
for accountId, account := range store.Accounts {
|
||||
for setupKeyId := range account.SetupKeys {
|
||||
store.SetupKeyId2AccountId[strings.ToUpper(setupKeyId)] = accountId
|
||||
@@ -78,6 +81,12 @@ func restore(file string) (*FileStore, error) {
|
||||
for _, user := range account.Users {
|
||||
store.UserId2AccountId[user.Id] = accountId
|
||||
}
|
||||
for _, user := range account.Users {
|
||||
store.UserId2AccountId[user.Id] = accountId
|
||||
}
|
||||
if account.Domain != "" && account.DomainCategory == PrivateCategory && account.IsDomainPrimaryAccount {
|
||||
store.PrivateDomain2AccountId[account.Domain] = accountId
|
||||
}
|
||||
}
|
||||
|
||||
return store, nil
|
||||
@@ -178,6 +187,10 @@ func (s *FileStore) SaveAccount(account *Account) error {
|
||||
s.UserId2AccountId[user.Id] = account.Id
|
||||
}
|
||||
|
||||
if account.DomainCategory == PrivateCategory && account.IsDomainPrimaryAccount {
|
||||
s.PrivateDomain2AccountId[account.Domain] = account.Id
|
||||
}
|
||||
|
||||
err := s.persist(s.storeFile)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -186,6 +199,21 @@ func (s *FileStore) SaveAccount(account *Account) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FileStore) GetAccountByPrivateDomain(domain string) (*Account, error) {
|
||||
|
||||
accountId, accountIdFound := s.PrivateDomain2AccountId[strings.ToLower(domain)]
|
||||
if !accountIdFound {
|
||||
return nil, status.Errorf(codes.NotFound, "provided domain is not registered or is not private")
|
||||
}
|
||||
|
||||
account, err := s.GetAccount(accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (s *FileStore) GetAccountBySetupKey(setupKey string) (*Account, error) {
|
||||
|
||||
accountId, accountIdFound := s.SetupKeyId2AccountId[strings.ToUpper(setupKey)]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"net"
|
||||
"path/filepath"
|
||||
@@ -131,34 +132,45 @@ func TestRestore(t *testing.T) {
|
||||
}
|
||||
|
||||
account := store.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"]
|
||||
if account == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing account bf1c8084-ba50-4ce7-9439-34653001fc3b")
|
||||
|
||||
require.NotNil(t, account, "failed to restore a FileStore file - missing account bf1c8084-ba50-4ce7-9439-34653001fc3b")
|
||||
|
||||
require.NotNil(t, account.Users["edafee4e-63fb-11ec-90d6-0242ac120003"], "failed to restore a FileStore file - missing Account User edafee4e-63fb-11ec-90d6-0242ac120003")
|
||||
|
||||
require.NotNil(t, account.Users["f4f6d672-63fb-11ec-90d6-0242ac120003"], "failed to restore a FileStore file - missing Account User f4f6d672-63fb-11ec-90d6-0242ac120003")
|
||||
|
||||
require.NotNil(t, account.Network, "failed to restore a FileStore file - missing Account Network")
|
||||
|
||||
require.NotNil(t, account.SetupKeys["A2C8E62B-38F5-4553-B31E-DD66C696CEBB"], "failed to restore a FileStore file - missing Account SetupKey A2C8E62B-38F5-4553-B31E-DD66C696CEBB")
|
||||
|
||||
require.Len(t, store.UserId2AccountId, 2, "failed to restore a FileStore wrong UserId2AccountId mapping length")
|
||||
|
||||
require.Len(t, store.SetupKeyId2AccountId, 1, "failed to restore a FileStore wrong SetupKeyId2AccountId mapping length")
|
||||
|
||||
require.Len(t, store.PrivateDomain2AccountId, 1, "failed to restore a FileStore wrong PrivateDomain2AccountId mapping length")
|
||||
}
|
||||
|
||||
func TestGetAccountByPrivateDomain(t *testing.T) {
|
||||
storeDir := t.TempDir()
|
||||
|
||||
err := util.CopyFileContents("testdata/store.json", filepath.Join(storeDir, "store.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if account != nil && account.Users["edafee4e-63fb-11ec-90d6-0242ac120003"] == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing Account User edafee4e-63fb-11ec-90d6-0242ac120003")
|
||||
store, err := NewStore(storeDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if account != nil && account.Users["f4f6d672-63fb-11ec-90d6-0242ac120003"] == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing Account User f4f6d672-63fb-11ec-90d6-0242ac120003")
|
||||
}
|
||||
existingDomain := "test.com"
|
||||
|
||||
if account != nil && account.Network == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing Account Network")
|
||||
}
|
||||
|
||||
if account != nil && account.SetupKeys["A2C8E62B-38F5-4553-B31E-DD66C696CEBB"] == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing Account SetupKey A2C8E62B-38F5-4553-B31E-DD66C696CEBB")
|
||||
}
|
||||
|
||||
if len(store.UserId2AccountId) != 2 {
|
||||
t.Errorf("failed to restore a FileStore wrong UserId2AccountId mapping")
|
||||
}
|
||||
|
||||
if len(store.SetupKeyId2AccountId) != 1 {
|
||||
t.Errorf("failed to restore a FileStore wrong SetupKeyId2AccountId mapping")
|
||||
}
|
||||
account, err := store.GetAccountByPrivateDomain(existingDomain)
|
||||
require.NoError(t, err, "should found account")
|
||||
require.Equal(t, existingDomain, account.Domain, "domains should match")
|
||||
|
||||
_, err = store.GetAccountByPrivateDomain("missing-domain.com")
|
||||
require.Error(t, err, "should return error on domain lookup")
|
||||
}
|
||||
|
||||
func newStore(t *testing.T) *FileStore {
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
// Server an instance of a Management server
|
||||
type Server struct {
|
||||
accountManager *AccountManager
|
||||
accountManager AccountManager
|
||||
wgKey wgtypes.Key
|
||||
proto.UnimplementedManagementServiceServer
|
||||
peersUpdateManager *PeersUpdateManager
|
||||
@@ -28,7 +28,7 @@ type Server struct {
|
||||
const AllowedIPsFormat = "%s/32"
|
||||
|
||||
// NewServer creates a new Management server
|
||||
func NewServer(config *Config, accountManager *AccountManager, peersUpdateManager *PeersUpdateManager, turnCredentialsManager TURNCredentialsManager) (*Server, error) {
|
||||
func NewServer(config *Config, accountManager AccountManager, peersUpdateManager *PeersUpdateManager, turnCredentialsManager TURNCredentialsManager) (*Server, error) {
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -158,7 +158,7 @@ func (s *Server) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Pe
|
||||
return nil, status.Errorf(codes.NotFound, "provided setup key doesn't exists")
|
||||
}
|
||||
|
||||
//todo move to AccountManager the code below
|
||||
//todo move to DefaultAccountManager the code below
|
||||
networkMap, err := s.accountManager.GetNetworkMap(peer.Key)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "internal server error")
|
||||
|
||||
@@ -3,17 +3,20 @@ package handler
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/wiretrustee/wiretrustee/management/server/jwtclaims"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/management/server"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
//Peers is a handler that returns peers of the account
|
||||
type Peers struct {
|
||||
accountManager *server.AccountManager
|
||||
accountManager server.AccountManager
|
||||
authAudience string
|
||||
jwtExtractor jwtclaims.ClaimsExtractor
|
||||
}
|
||||
|
||||
//PeerResponse is a response sent to the client
|
||||
@@ -31,10 +34,11 @@ type PeerRequest struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func NewPeers(accountManager *server.AccountManager, authAudience string) *Peers {
|
||||
func NewPeers(accountManager server.AccountManager, authAudience string) *Peers {
|
||||
return &Peers{
|
||||
accountManager: accountManager,
|
||||
authAudience: authAudience,
|
||||
jwtExtractor: *jwtclaims.NewClaimsExtractor(nil),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +58,7 @@ func (h *Peers) updatePeer(accountId string, peer *server.Peer, w http.ResponseW
|
||||
}
|
||||
writeJSONObject(w, toPeerResponse(peer))
|
||||
}
|
||||
|
||||
func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
|
||||
_, err := h.accountManager.DeletePeer(accountId, peer.Key)
|
||||
if err != nil {
|
||||
@@ -65,9 +70,9 @@ func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseW
|
||||
}
|
||||
|
||||
func (h *Peers) getPeerAccount(r *http.Request) (*server.Account, error) {
|
||||
jwtClaims := extractClaimsFromRequestContext(r, h.authAudience)
|
||||
jwtClaims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
|
||||
account, err := h.accountManager.GetAccountByUserOrAccountId(jwtClaims.UserId, jwtClaims.AccountId, jwtClaims.Domain)
|
||||
account, err := h.accountManager.GetAccountWithAuthorizationClaims(jwtClaims)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed getting account of a user %s: %v", jwtClaims.UserId, err)
|
||||
}
|
||||
|
||||
108
management/server/http/handler/peers_test.go
Normal file
108
management/server/http/handler/peers_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/wiretrustee/wiretrustee/management/server/jwtclaims"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/magiconair/properties/assert"
|
||||
"github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/management/server/mock_server"
|
||||
)
|
||||
|
||||
func initTestMetaData(peer ...*server.Peer) *Peers {
|
||||
return &Peers{
|
||||
accountManager: &mock_server.MockAccountManager{
|
||||
GetAccountWithAuthorizationClaimsFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
||||
return &server.Account{
|
||||
Id: claims.AccountId,
|
||||
Domain: "hotmail.com",
|
||||
Peers: map[string]*server.Peer{
|
||||
"test_peer": peer[0],
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
authAudience: "",
|
||||
jwtExtractor: jwtclaims.ClaimsExtractor{
|
||||
ExtractClaimsFromRequestContext: func(r *http.Request, authAudiance string) jwtclaims.AuthorizationClaims {
|
||||
return jwtclaims.AuthorizationClaims{
|
||||
UserId: "test_user",
|
||||
Domain: "hotmail.com",
|
||||
AccountId: "test_id",
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Tests the GetPeers endpoint reachable in the route /api/peers
|
||||
// Use the metadata generated by initTestMetaData() to check for values
|
||||
func TestGetPeers(t *testing.T) {
|
||||
var tt = []struct {
|
||||
name string
|
||||
expectedStatus int
|
||||
requestType string
|
||||
requestPath string
|
||||
requestBody io.Reader
|
||||
}{
|
||||
{name: "GetPeersMetaData", requestType: http.MethodGet, requestPath: "/api/peers/", expectedStatus: http.StatusOK},
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
peer := &server.Peer{
|
||||
Key: "key",
|
||||
SetupKey: "setupkey",
|
||||
IP: net.ParseIP("100.64.0.1"),
|
||||
Status: &server.PeerStatus{},
|
||||
Name: "PeerName",
|
||||
Meta: server.PeerSystemMeta{
|
||||
Hostname: "hostname",
|
||||
GoOS: "GoOS",
|
||||
Kernel: "kernel",
|
||||
Core: "core",
|
||||
Platform: "platform",
|
||||
OS: "OS",
|
||||
WtVersion: "development",
|
||||
},
|
||||
}
|
||||
|
||||
p := initTestMetaData(peer)
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
|
||||
|
||||
p.GetPeers(rr, req)
|
||||
|
||||
res := rr.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
if status := rr.Code; status != tc.expectedStatus {
|
||||
t.Fatalf("handler returned wrong status code: got %v want %v",
|
||||
status, http.StatusOK)
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("I don't know what I expected; %v", err)
|
||||
}
|
||||
|
||||
respBody := []*PeerResponse{}
|
||||
err = json.Unmarshal(content, &respBody)
|
||||
if err != nil {
|
||||
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||
}
|
||||
|
||||
got := respBody[0]
|
||||
assert.Equal(t, got.Name, peer.Name)
|
||||
assert.Equal(t, got.Version, peer.Meta.WtVersion)
|
||||
assert.Equal(t, got.IP, peer.IP.String())
|
||||
assert.Equal(t, got.OS, "OS core")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,19 +3,21 @@ package handler
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/wiretrustee/wiretrustee/management/server/jwtclaims"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SetupKeys is a handler that returns a list of setup keys of the account
|
||||
type SetupKeys struct {
|
||||
accountManager *server.AccountManager
|
||||
accountManager server.AccountManager
|
||||
authAudience string
|
||||
}
|
||||
|
||||
@@ -41,7 +43,7 @@ type SetupKeyRequest struct {
|
||||
Revoked bool
|
||||
}
|
||||
|
||||
func NewSetupKeysHandler(accountManager *server.AccountManager, authAudience string) *SetupKeys {
|
||||
func NewSetupKeysHandler(accountManager server.AccountManager, authAudience string) *SetupKeys {
|
||||
return &SetupKeys{
|
||||
accountManager: accountManager,
|
||||
authAudience: authAudience,
|
||||
@@ -121,9 +123,10 @@ func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.R
|
||||
}
|
||||
|
||||
func (h *SetupKeys) getSetupKeyAccount(r *http.Request) (*server.Account, error) {
|
||||
jwtClaims := extractClaimsFromRequestContext(r, h.authAudience)
|
||||
extractor := jwtclaims.NewClaimsExtractor(nil)
|
||||
jwtClaims := extractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
|
||||
account, err := h.accountManager.GetAccountByUserOrAccountId(jwtClaims.UserId, jwtClaims.AccountId, jwtClaims.Domain)
|
||||
account, err := h.accountManager.GetAccountWithAuthorizationClaims(jwtClaims)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed getting account of a user %s: %v", jwtClaims.UserId, err)
|
||||
}
|
||||
|
||||
@@ -3,38 +3,13 @@ package handler
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// JWTClaims stores information from JWTs
|
||||
type JWTClaims struct {
|
||||
UserId string
|
||||
AccountId string
|
||||
Domain string
|
||||
}
|
||||
|
||||
// extractClaimsFromRequestContext extracts claims from the request context previously filled by the JWT token (after auth)
|
||||
func extractClaimsFromRequestContext(r *http.Request, authAudiance string) JWTClaims {
|
||||
token := r.Context().Value("user").(*jwt.Token)
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
jwtClaims := JWTClaims{}
|
||||
jwtClaims.UserId = claims["sub"].(string)
|
||||
accountIdClaim, ok := claims[authAudiance+"wt_account_id"]
|
||||
if ok {
|
||||
jwtClaims.AccountId = accountIdClaim.(string)
|
||||
}
|
||||
domainClaim, ok := claims[authAudiance+"wt_user_domain"]
|
||||
if ok {
|
||||
jwtClaims.Domain = domainClaim.(string)
|
||||
}
|
||||
return jwtClaims
|
||||
}
|
||||
|
||||
//writeJSONObject simply writes object to the HTTP reponse in JSON format
|
||||
func writeJSONObject(w http.ResponseWriter, obj interface{}) {
|
||||
w.WriteHeader(200)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
err := json.NewEncoder(w).Encode(obj)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,6 +3,9 @@ package http
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/cors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -10,8 +13,6 @@ import (
|
||||
"github.com/wiretrustee/wiretrustee/management/server/http/handler"
|
||||
"github.com/wiretrustee/wiretrustee/management/server/http/middleware"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
@@ -19,12 +20,12 @@ type Server struct {
|
||||
config *s.HttpServerConfig
|
||||
certManager *autocert.Manager
|
||||
tlsConfig *tls.Config
|
||||
accountManager *s.AccountManager
|
||||
accountManager s.AccountManager
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func NewHttpsServer(config *s.HttpServerConfig, certManager *autocert.Manager, accountManager s.AccountManager) *Server {
|
||||
server := &http.Server{
|
||||
Addr: config.Address,
|
||||
WriteTimeout: time.Second * 15,
|
||||
@@ -36,7 +37,7 @@ func NewHttpsServer(config *s.HttpServerConfig, certManager *autocert.Manager, a
|
||||
|
||||
// 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 {
|
||||
func NewHttpsServerWithTLSConfig(config *s.HttpServerConfig, tlsConfig *tls.Config, accountManager s.AccountManager) *Server {
|
||||
server := &http.Server{
|
||||
Addr: config.Address,
|
||||
WriteTimeout: time.Second * 15,
|
||||
@@ -47,7 +48,7 @@ func NewHttpsServerWithTLSConfig(config *s.HttpServerConfig, tlsConfig *tls.Conf
|
||||
}
|
||||
|
||||
// NewHttpServer creates a new HTTP server (without HTTPS)
|
||||
func NewHttpServer(config *s.HttpServerConfig, accountManager *s.AccountManager) *Server {
|
||||
func NewHttpServer(config *s.HttpServerConfig, accountManager s.AccountManager) *Server {
|
||||
return NewHttpsServer(config, nil, accountManager)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@ package idp
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type mockHTTPClient struct {
|
||||
|
||||
9
management/server/jwtclaims/claims.go
Normal file
9
management/server/jwtclaims/claims.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package jwtclaims
|
||||
|
||||
// AuthorizationClaims stores authorization information from JWTs
|
||||
type AuthorizationClaims struct {
|
||||
UserId string
|
||||
AccountId string
|
||||
Domain string
|
||||
DomainCategory string
|
||||
}
|
||||
56
management/server/jwtclaims/extractor.go
Normal file
56
management/server/jwtclaims/extractor.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package jwtclaims
|
||||
|
||||
import (
|
||||
"github.com/golang-jwt/jwt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
TokenUserProperty = "user"
|
||||
AccountIDSuffix = "wt_account_id"
|
||||
DomainIDSuffix = "wt_account_domain"
|
||||
DomainCategorySuffix = "wt_account_domain_category"
|
||||
UserIDClaim = "sub"
|
||||
)
|
||||
|
||||
// Extract function type
|
||||
type ExtractClaims func(r *http.Request, authAudiance string) AuthorizationClaims
|
||||
|
||||
// ClaimsExtractor struct that holds the extract function
|
||||
type ClaimsExtractor struct {
|
||||
ExtractClaimsFromRequestContext ExtractClaims
|
||||
}
|
||||
|
||||
// NewClaimsExtractor returns an extractor, and if provided with a function with ExtractClaims signature,
|
||||
// then it will use that logic. Uses ExtractClaimsFromRequestContext by default
|
||||
func NewClaimsExtractor(e ExtractClaims) *ClaimsExtractor {
|
||||
var extractFunc ExtractClaims
|
||||
if extractFunc = e; extractFunc == nil {
|
||||
extractFunc = ExtractClaimsFromRequestContext
|
||||
}
|
||||
|
||||
return &ClaimsExtractor{
|
||||
ExtractClaimsFromRequestContext: extractFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractClaimsFromRequestContext extracts claims from the request context previously filled by the JWT token (after auth)
|
||||
func ExtractClaimsFromRequestContext(r *http.Request, authAudiance string) AuthorizationClaims {
|
||||
token := r.Context().Value(TokenUserProperty).(*jwt.Token)
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
jwtClaims := AuthorizationClaims{}
|
||||
jwtClaims.UserId = claims[UserIDClaim].(string)
|
||||
accountIdClaim, ok := claims[authAudiance+AccountIDSuffix]
|
||||
if ok {
|
||||
jwtClaims.AccountId = accountIdClaim.(string)
|
||||
}
|
||||
domainClaim, ok := claims[authAudiance+DomainIDSuffix]
|
||||
if ok {
|
||||
jwtClaims.Domain = domainClaim.(string)
|
||||
}
|
||||
domainCategoryClaim, ok := claims[authAudiance+DomainCategorySuffix]
|
||||
if ok {
|
||||
jwtClaims.DomainCategory = domainCategoryClaim.(string)
|
||||
}
|
||||
return jwtClaims
|
||||
}
|
||||
110
management/server/jwtclaims/extractor_test.go
Normal file
110
management/server/jwtclaims/extractor_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package jwtclaims
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func newTestRequestWithJWT(t *testing.T, claims AuthorizationClaims, audiance string) *http.Request {
|
||||
claimMaps := jwt.MapClaims{}
|
||||
if claims.UserId != "" {
|
||||
claimMaps[UserIDClaim] = claims.UserId
|
||||
}
|
||||
if claims.AccountId != "" {
|
||||
claimMaps[audiance+AccountIDSuffix] = claims.AccountId
|
||||
}
|
||||
if claims.Domain != "" {
|
||||
claimMaps[audiance+DomainIDSuffix] = claims.Domain
|
||||
}
|
||||
if claims.DomainCategory != "" {
|
||||
claimMaps[audiance+DomainCategorySuffix] = claims.DomainCategory
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claimMaps)
|
||||
r, err := http.NewRequest(http.MethodGet, "http://localhost", nil)
|
||||
require.NoError(t, err, "creating testing request failed")
|
||||
testRequest := r.WithContext(context.WithValue(r.Context(), TokenUserProperty, token)) //nolint
|
||||
|
||||
return testRequest
|
||||
}
|
||||
|
||||
func TestExtractClaimsFromRequestContext(t *testing.T) {
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
inputAuthorizationClaims AuthorizationClaims
|
||||
inputAudiance string
|
||||
testingFunc require.ComparisonAssertionFunc
|
||||
expectedMSG string
|
||||
}
|
||||
|
||||
testCase1 := test{
|
||||
name: "All Claim Fields",
|
||||
inputAudiance: "https://login/",
|
||||
inputAuthorizationClaims: AuthorizationClaims{
|
||||
UserId: "test",
|
||||
Domain: "test.com",
|
||||
AccountId: "testAcc",
|
||||
DomainCategory: "public",
|
||||
},
|
||||
testingFunc: require.EqualValues,
|
||||
expectedMSG: "extracted claims should match input claims",
|
||||
}
|
||||
|
||||
testCase2 := test{
|
||||
name: "Domain Is Empty",
|
||||
inputAudiance: "https://login/",
|
||||
inputAuthorizationClaims: AuthorizationClaims{
|
||||
UserId: "test",
|
||||
AccountId: "testAcc",
|
||||
},
|
||||
testingFunc: require.EqualValues,
|
||||
expectedMSG: "extracted claims should match input claims",
|
||||
}
|
||||
|
||||
testCase3 := test{
|
||||
name: "Account ID Is Empty",
|
||||
inputAudiance: "https://login/",
|
||||
inputAuthorizationClaims: AuthorizationClaims{
|
||||
UserId: "test",
|
||||
Domain: "test.com",
|
||||
},
|
||||
testingFunc: require.EqualValues,
|
||||
expectedMSG: "extracted claims should match input claims",
|
||||
}
|
||||
|
||||
testCase4 := test{
|
||||
name: "Category Is Empty",
|
||||
inputAudiance: "https://login/",
|
||||
inputAuthorizationClaims: AuthorizationClaims{
|
||||
UserId: "test",
|
||||
Domain: "test.com",
|
||||
AccountId: "testAcc",
|
||||
},
|
||||
testingFunc: require.EqualValues,
|
||||
expectedMSG: "extracted claims should match input claims",
|
||||
}
|
||||
|
||||
testCase5 := test{
|
||||
name: "Only User ID Is set",
|
||||
inputAudiance: "https://login/",
|
||||
inputAuthorizationClaims: AuthorizationClaims{
|
||||
UserId: "test",
|
||||
},
|
||||
testingFunc: require.EqualValues,
|
||||
expectedMSG: "extracted claims should match input claims",
|
||||
}
|
||||
|
||||
for _, testCase := range []test{testCase1, testCase2, testCase3, testCase4, testCase5} {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
|
||||
request := newTestRequestWithJWT(t, testCase.inputAuthorizationClaims, testCase.inputAudiance)
|
||||
|
||||
extractedClaims := ExtractClaimsFromRequestContext(request, testCase.inputAudiance)
|
||||
|
||||
testCase.testingFunc(t, testCase.inputAuthorizationClaims, extractedClaims, testCase.expectedMSG)
|
||||
})
|
||||
}
|
||||
}
|
||||
148
management/server/mock_server/account_mock.go
Normal file
148
management/server/mock_server/account_mock.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package mock_server
|
||||
|
||||
import (
|
||||
"github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/management/server/jwtclaims"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type MockAccountManager struct {
|
||||
GetOrCreateAccountByUserFunc func(userId, domain string) (*server.Account, error)
|
||||
GetAccountByUserFunc func(userId string) (*server.Account, error)
|
||||
AddSetupKeyFunc func(accountId string, keyName string, keyType server.SetupKeyType, expiresIn *util.Duration) (*server.SetupKey, error)
|
||||
RevokeSetupKeyFunc func(accountId string, keyId string) (*server.SetupKey, error)
|
||||
RenameSetupKeyFunc func(accountId string, keyId string, newName string) (*server.SetupKey, error)
|
||||
GetAccountByIdFunc func(accountId string) (*server.Account, error)
|
||||
GetAccountByUserOrAccountIdFunc func(userId, accountId, domain string) (*server.Account, error)
|
||||
GetAccountWithAuthorizationClaimsFunc func(claims jwtclaims.AuthorizationClaims) (*server.Account, error)
|
||||
AccountExistsFunc func(accountId string) (*bool, error)
|
||||
AddAccountFunc func(accountId, userId, domain string) (*server.Account, error)
|
||||
GetPeerFunc func(peerKey string) (*server.Peer, error)
|
||||
MarkPeerConnectedFunc func(peerKey string, connected bool) error
|
||||
RenamePeerFunc func(accountId string, peerKey string, newName string) (*server.Peer, error)
|
||||
DeletePeerFunc func(accountId string, peerKey string) (*server.Peer, error)
|
||||
GetPeerByIPFunc func(accountId string, peerIP string) (*server.Peer, error)
|
||||
GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error)
|
||||
AddPeerFunc func(setupKey string, peer *server.Peer) (*server.Peer, error)
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) GetOrCreateAccountByUser(userId, domain string) (*server.Account, error) {
|
||||
if am.GetOrCreateAccountByUserFunc != nil {
|
||||
return am.GetOrCreateAccountByUserFunc(userId, domain)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetOrCreateAccountByUser not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) GetAccountByUser(userId string) (*server.Account, error) {
|
||||
if am.GetAccountByUserFunc != nil {
|
||||
return am.GetAccountByUserFunc(userId)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccountByUser not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) AddSetupKey(accountId string, keyName string, keyType server.SetupKeyType, expiresIn *util.Duration) (*server.SetupKey, error) {
|
||||
if am.AddSetupKeyFunc != nil {
|
||||
return am.AddSetupKeyFunc(accountId, keyName, keyType, expiresIn)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddSetupKey not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) RevokeSetupKey(accountId string, keyId string) (*server.SetupKey, error) {
|
||||
if am.RevokeSetupKeyFunc != nil {
|
||||
return am.RevokeSetupKeyFunc(accountId, keyId)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RevokeSetupKey not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) RenameSetupKey(accountId string, keyId string, newName string) (*server.SetupKey, error) {
|
||||
if am.RenameSetupKeyFunc != nil {
|
||||
return am.RenameSetupKeyFunc(accountId, keyId, newName)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RenameSetupKey not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) GetAccountById(accountId string) (*server.Account, error) {
|
||||
if am.GetAccountByIdFunc != nil {
|
||||
return am.GetAccountByIdFunc(accountId)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccountById not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) GetAccountByUserOrAccountId(userId, accountId, domain string) (*server.Account, error) {
|
||||
if am.GetAccountByUserOrAccountIdFunc != nil {
|
||||
return am.GetAccountByUserOrAccountIdFunc(userId, accountId, domain)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccountByUserOrAccountId not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) GetAccountWithAuthorizationClaims(claims jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
||||
if am.GetAccountWithAuthorizationClaimsFunc != nil {
|
||||
return am.GetAccountWithAuthorizationClaimsFunc(claims)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccountWithAuthorizationClaims not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) AccountExists(accountId string) (*bool, error) {
|
||||
if am.AccountExistsFunc != nil {
|
||||
return am.AccountExistsFunc(accountId)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AccountExists not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) AddAccount(accountId, userId, domain string) (*server.Account, error) {
|
||||
if am.AddAccountFunc != nil {
|
||||
return am.AddAccountFunc(accountId, userId, domain)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddAccount not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) GetPeer(peerKey string) (*server.Peer, error) {
|
||||
if am.GetPeerFunc != nil {
|
||||
return am.GetPeerFunc(peerKey)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeer not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) MarkPeerConnected(peerKey string, connected bool) error {
|
||||
if am.MarkPeerConnectedFunc != nil {
|
||||
return am.MarkPeerConnectedFunc(peerKey, connected)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method MarkPeerConnected not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) RenamePeer(accountId string, peerKey string, newName string) (*server.Peer, error) {
|
||||
if am.RenamePeerFunc != nil {
|
||||
return am.RenamePeerFunc(accountId, peerKey, newName)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RenamePeer not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) DeletePeer(accountId string, peerKey string) (*server.Peer, error) {
|
||||
if am.DeletePeerFunc != nil {
|
||||
return am.DeletePeerFunc(accountId, peerKey)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeletePeer not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) GetPeerByIP(accountId string, peerIP string) (*server.Peer, error) {
|
||||
if am.GetPeerByIPFunc != nil {
|
||||
return am.GetPeerByIPFunc(accountId, peerIP)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeerByIP not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) GetNetworkMap(peerKey string) (*server.NetworkMap, error) {
|
||||
if am.GetNetworkMapFunc != nil {
|
||||
return am.GetNetworkMapFunc(peerKey)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetNetworkMap not implemented")
|
||||
}
|
||||
|
||||
func (am *MockAccountManager) AddPeer(setupKey string, peer *server.Peer) (*server.Peer, error) {
|
||||
if am.AddPeerFunc != nil {
|
||||
return am.AddPeerFunc(setupKey, peer)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddPeer not implemented")
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package server
|
||||
package mock_server
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -56,7 +56,7 @@ func (p *Peer) Copy() *Peer {
|
||||
}
|
||||
|
||||
//GetPeer returns a peer from a Store
|
||||
func (am *AccountManager) GetPeer(peerKey string) (*Peer, error) {
|
||||
func (am *DefaultAccountManager) GetPeer(peerKey string) (*Peer, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
@@ -69,7 +69,7 @@ func (am *AccountManager) GetPeer(peerKey string) (*Peer, error) {
|
||||
}
|
||||
|
||||
//MarkPeerConnected marks peer as connected (true) or disconnected (false)
|
||||
func (am *AccountManager) MarkPeerConnected(peerKey string, connected bool) error {
|
||||
func (am *DefaultAccountManager) MarkPeerConnected(peerKey string, connected bool) error {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
@@ -94,7 +94,7 @@ func (am *AccountManager) MarkPeerConnected(peerKey string, connected bool) erro
|
||||
}
|
||||
|
||||
//RenamePeer changes peer's name
|
||||
func (am *AccountManager) RenamePeer(accountId string, peerKey string, newName string) (*Peer, error) {
|
||||
func (am *DefaultAccountManager) RenamePeer(accountId string, peerKey string, newName string) (*Peer, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
@@ -114,7 +114,7 @@ func (am *AccountManager) RenamePeer(accountId string, peerKey string, newName s
|
||||
}
|
||||
|
||||
//DeletePeer removes peer from the account by it's IP
|
||||
func (am *AccountManager) DeletePeer(accountId string, peerKey string) (*Peer, error) {
|
||||
func (am *DefaultAccountManager) DeletePeer(accountId string, peerKey string) (*Peer, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
@@ -188,7 +188,7 @@ func (am *AccountManager) DeletePeer(accountId string, peerKey string) (*Peer, e
|
||||
}
|
||||
|
||||
//GetPeerByIP returns peer by it's IP
|
||||
func (am *AccountManager) GetPeerByIP(accountId string, peerIP string) (*Peer, error) {
|
||||
func (am *DefaultAccountManager) GetPeerByIP(accountId string, peerIP string) (*Peer, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
@@ -207,7 +207,7 @@ func (am *AccountManager) GetPeerByIP(accountId string, peerIP string) (*Peer, e
|
||||
}
|
||||
|
||||
// GetNetworkMap returns Network map for a given peer (omits original peer from the Peers result)
|
||||
func (am *AccountManager) GetNetworkMap(peerKey string) (*NetworkMap, error) {
|
||||
func (am *DefaultAccountManager) GetNetworkMap(peerKey string) (*NetworkMap, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
@@ -235,7 +235,7 @@ func (am *AccountManager) GetNetworkMap(peerKey string) (*NetworkMap, error) {
|
||||
// will be returned, meaning the key is invalid
|
||||
// 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).
|
||||
// The peer property is just a placeholder for the Peer properties to pass further
|
||||
func (am *AccountManager) AddPeer(setupKey string, peer *Peer) (*Peer, error) {
|
||||
func (am *DefaultAccountManager) AddPeer(setupKey string, peer *Peer) (*Peer, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
|
||||
@@ -9,5 +9,6 @@ type Store interface {
|
||||
GetAccountPeers(accountId string) ([]*Peer, error)
|
||||
GetPeerAccount(peerKey string) (*Account, error)
|
||||
GetAccountBySetupKey(setupKey string) (*Account, error)
|
||||
GetAccountByPrivateDomain(domain string) (*Account, error)
|
||||
SaveAccount(account *Account) error
|
||||
}
|
||||
|
||||
3
management/server/testdata/store.json
vendored
3
management/server/testdata/store.json
vendored
@@ -2,6 +2,9 @@
|
||||
"Accounts": {
|
||||
"bf1c8084-ba50-4ce7-9439-34653001fc3b": {
|
||||
"Id": "bf1c8084-ba50-4ce7-9439-34653001fc3b",
|
||||
"Domain": "test.com",
|
||||
"DomainCategory": "private",
|
||||
"IsDomainPrimaryAccount": true,
|
||||
"SetupKeys": {
|
||||
"A2C8E62B-38F5-4553-B31E-DD66C696CEBB": {
|
||||
"Key": "A2C8E62B-38F5-4553-B31E-DD66C696CEBB",
|
||||
|
||||
@@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -34,20 +35,27 @@ func NewUser(id string, role UserRole) *User {
|
||||
}
|
||||
}
|
||||
|
||||
// NewRegularUser creates a new user with role UserRoleAdmin
|
||||
func NewRegularUser(id string) *User {
|
||||
return NewUser(id, UserRoleUser)
|
||||
}
|
||||
|
||||
// NewAdminUser creates a new user with role UserRoleAdmin
|
||||
func NewAdminUser(id string) *User {
|
||||
return NewUser(id, UserRoleAdmin)
|
||||
}
|
||||
|
||||
// GetOrCreateAccountByUser returns an existing account for a given user id or creates a new one if doesn't exist
|
||||
func (am *AccountManager) GetOrCreateAccountByUser(userId, domain string) (*Account, error) {
|
||||
func (am *DefaultAccountManager) GetOrCreateAccountByUser(userId, domain string) (*Account, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
lowerDomain := strings.ToLower(domain)
|
||||
|
||||
account, err := am.Store.GetUserAccount(userId)
|
||||
if err != nil {
|
||||
if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound {
|
||||
account = NewAccount(userId, domain)
|
||||
account = NewAccount(userId, lowerDomain)
|
||||
account.Users[userId] = NewAdminUser(userId)
|
||||
err = am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
@@ -59,8 +67,8 @@ func (am *AccountManager) GetOrCreateAccountByUser(userId, domain string) (*Acco
|
||||
}
|
||||
}
|
||||
|
||||
if account.Domain != domain {
|
||||
account.Domain = domain
|
||||
if account.Domain != lowerDomain {
|
||||
account.Domain = lowerDomain
|
||||
err = am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed updating account with domain")
|
||||
@@ -71,7 +79,7 @@ func (am *AccountManager) GetOrCreateAccountByUser(userId, domain string) (*Acco
|
||||
}
|
||||
|
||||
// GetAccountByUser returns an existing account for a given user id, NotFound if account couldn't be found
|
||||
func (am *AccountManager) GetAccountByUser(userId string) (*Account, error) {
|
||||
func (am *DefaultAccountManager) GetAccountByUser(userId string) (*Account, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
|
||||
28
util/file.go
28
util/file.go
@@ -12,7 +12,7 @@ import (
|
||||
// The output JSON is pretty-formatted
|
||||
func WriteJson(file string, obj interface{}) error {
|
||||
|
||||
configDir := filepath.Dir(file)
|
||||
configDir, configFileName := filepath.Split(file)
|
||||
err := os.MkdirAll(configDir, 0750)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -24,7 +24,31 @@ func WriteJson(file string, obj interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(file, bs, 0600)
|
||||
tempFile, err := ioutil.TempFile(configDir, ".*"+configFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tempFileName := tempFile.Name()
|
||||
// closing file ops as windows doesn't allow to move it
|
||||
err = tempFile.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_, err = os.Stat(tempFileName)
|
||||
if err == nil {
|
||||
os.Remove(tempFileName)
|
||||
}
|
||||
}()
|
||||
|
||||
err = ioutil.WriteFile(tempFileName, bs, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Rename(tempFileName, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user