From 1dfa99d07c80f6014d5bab1c6259b581d060cf89 Mon Sep 17 00:00:00 2001 From: Mikhail Bragin Date: Wed, 18 Aug 2021 13:35:42 +0200 Subject: [PATCH] add wiretrustee LOGIN command (#90) * feature: add wiretrustee LOGIN command * chore: add management initial connection timeout * test: add login cmd test * test: validate generated config in login cmd * test: add up command test * chore: add timeout to signal client creation method * test: close wireguard interface once test finished --- client/cmd/login.go | 152 +++++++++++++++++++++++++++++++ client/cmd/login_test.go | 68 ++++++++++++++ client/cmd/root.go | 9 +- client/cmd/service.go | 9 -- client/cmd/service_controller.go | 8 +- client/cmd/service_test.go | 129 -------------------------- client/cmd/testutil.go | 54 +++++++++++ client/cmd/up.go | 117 ++++++++---------------- client/cmd/up_test.go | 110 ++++++++++++++++++++++ client/internal/config.go | 83 ++++++++++++----- client/testdata/management.json | 33 +++++++ client/testdata/store.json | 21 +++++ management/client/client.go | 4 +- signal/client/client.go | 4 +- 14 files changed, 557 insertions(+), 244 deletions(-) create mode 100644 client/cmd/login.go create mode 100644 client/cmd/login_test.go delete mode 100644 client/cmd/service_test.go create mode 100644 client/cmd/testutil.go create mode 100644 client/cmd/up_test.go create mode 100644 client/testdata/management.json create mode 100644 client/testdata/store.json diff --git a/client/cmd/login.go b/client/cmd/login.go new file mode 100644 index 000000000..44e099325 --- /dev/null +++ b/client/cmd/login.go @@ -0,0 +1,152 @@ +package cmd + +import ( + "bufio" + "context" + "fmt" + "github.com/google/uuid" + 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" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "os" +) + +var ( + setupKey string + + loginCmd = &cobra.Command{ + Use: "login", + Short: "login to the Wiretrustee Management Service (first run)", + RunE: func(cmd *cobra.Command, args []string) error { + InitLog(logLevel) + + config, err := internal.GetConfig(managementURL, configPath) + if err != nil { + log.Errorf("failed getting config %s %v", configPath, err) + //os.Exit(ExitSetupFailed) + 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()) + //os.Exit(ExitSetupFailed) + 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) + //os.Exit(ExitSetupFailed) + return err + } + log.Debugf("connected to anagement Service %s", config.ManagementURL.String()) + + serverKey, err := mgmClient.GetServerPublicKey() + if err != nil { + log.Errorf("failed while getting Management Service public key: %v", err) + //os.Exit(ExitSetupFailed) + return err + } + + _, err = loginPeer(*serverKey, mgmClient, setupKey) + if err != nil { + log.Errorf("failed logging-in peer on Management Service : %v", err) + //os.Exit(ExitSetupFailed) + return err + } + + err = mgmClient.Close() + if err != nil { + log.Errorf("failed closing Management Service client: %v", err) + //os.Exit(ExitSetupFailed) + 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.Client, 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.Client, setupKey string) (*mgmProto.LoginResponse, error) { + + var err error + if setupKey == "" { + setupKey, err = promptPeerSetupKey() + if err != nil { + log.Errorf("failed getting setup key from user: %s", err) + return nil, err + } + } + + validSetupKey, err := uuid.Parse(setupKey) + if err != nil { + return nil, err + } + + log.Debugf("sending peer registration request to Management Service") + loginResp, err := client.Register(serverPublicKey, validSetupKey.String()) + 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 + } + fmt.Println("Specified key is empty, try again:") + + } + + return "", s.Err() +} + +func init() { + loginCmd.PersistentFlags().StringVar(&setupKey, "setup-key", "", "Setup key obtained from the Management Service Dashboard (used to register peer)") +} diff --git a/client/cmd/login_test.go b/client/cmd/login_test.go new file mode 100644 index 000000000..448cae020 --- /dev/null +++ b/client/cmd/login_test.go @@ -0,0 +1,68 @@ +package cmd + +import ( + "fmt" + "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" + "testing" +) + +var mgmAddr string + +func TestLogin_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) + } + _, listener := startManagement(config, t) + mgmAddr = listener.Addr().String() +} + +func TestLogin(t *testing.T) { + + tempDir := t.TempDir() + confPath := tempDir + "/config.json" + mgmtURL := fmt.Sprintf("http://%s", mgmAddr) + rootCmd.SetArgs([]string{ + "login", + "--config", + confPath, + "--setup-key", + "a2c8e62b-38f5-4553-b31e-dd66c696cebb", + "--management-url", + mgmtURL, + }) + err := rootCmd.Execute() + if err != nil { + t.Fatal(err) + } + + // validate generated config + actualConf := &internal.Config{} + _, err = util.ReadJson(confPath, actualConf) + if err != nil { + t.Errorf("expected proper config file written, got broken %v", err) + } + + if actualConf.ManagementURL.String() != mgmtURL { + t.Errorf("expected management URL %s got %s", mgmtURL, actualConf.ManagementURL.String()) + } + + if actualConf.WgIface != iface.WgInterfaceDefault { + t.Errorf("expected WgIface %s got %s", iface.WgInterfaceDefault, actualConf.WgIface) + } + + if len(actualConf.PrivateKey) == 0 { + t.Errorf("expected non empty Private key, got empty") + } +} diff --git a/client/cmd/root.go b/client/cmd/root.go index 128a9809f..dd788a9a9 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/wiretrustee/wiretrustee/client/internal" "os" "os/signal" "runtime" @@ -20,6 +21,7 @@ var ( configPath string defaultConfigPath string logLevel string + managementURL string rootCmd = &cobra.Command{ Use: "wiretrustee", @@ -43,10 +45,13 @@ func init() { if runtime.GOOS == "windows" { defaultConfigPath = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "config.json" } - rootCmd.PersistentFlags().StringVar(&configPath, "config", defaultConfigPath, "Wiretrustee config file location to write new config to") - rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "") + + rootCmd.PersistentFlags().StringVar(&managementURL, "management-url", "", fmt.Sprintf("Management Service URL [http|https]://[host]:[port] (default \"%s\")", internal.ManagementURLDefault().String())) + rootCmd.PersistentFlags().StringVar(&configPath, "config", defaultConfigPath, "Wiretrustee config file location") + rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "sets Wiretrustee log level") rootCmd.AddCommand(serviceCmd) rootCmd.AddCommand(upCmd) + rootCmd.AddCommand(loginCmd) serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service serviceCmd.AddCommand(installCmd, uninstallCmd) // service installer commands are subcommands of service } diff --git a/client/cmd/service.go b/client/cmd/service.go index 8cb359727..2a60c31fe 100644 --- a/client/cmd/service.go +++ b/client/cmd/service.go @@ -11,8 +11,6 @@ type program struct { args []string } -var logger service.Logger - func newSVCConfig() *service.Config { return &service.Config{ Name: "wiretrustee", @@ -27,11 +25,6 @@ func newSVC(prg *program, conf *service.Config) (service.Service, error) { log.Fatal(err) return nil, err } - logger, err = s.Logger(nil) - if err != nil { - log.Fatal(err) - return nil, err - } return s, nil } @@ -39,8 +32,6 @@ var ( serviceCmd = &cobra.Command{ Use: "service", Short: "manages wiretrustee service", - //Run: func(cmd *cobra.Command, args []string) { - //}, } ) diff --git a/client/cmd/service_controller.go b/client/cmd/service_controller.go index b0015f7c9..6b3945189 100644 --- a/client/cmd/service_controller.go +++ b/client/cmd/service_controller.go @@ -9,7 +9,13 @@ import ( func (p *program) Start(s service.Service) error { // Start should not block. Do the actual work async. log.Info("starting service") //nolint - go upCmd.Run(p.cmd, p.args) + go func() { + err := upCmd.RunE(p.cmd, p.args) + if err != nil { + return + } + + }() return nil } diff --git a/client/cmd/service_test.go b/client/cmd/service_test.go deleted file mode 100644 index 5b900676d..000000000 --- a/client/cmd/service_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package cmd - -import ( - "bytes" - "io/ioutil" - "os" - "testing" - - "github.com/kardianos/service" -) - -func Test_ServiceInstallCMD(t *testing.T) { - b := bytes.NewBufferString("") - rootCmd.SetOut(b) - rootCmd.SetErr(b) - rootCmd.SetArgs([]string{ - "service", - "install", - "--config", - "/tmp/config.json", - }) - err := rootCmd.Execute() - if err != nil { - t.Fatal(err) - } - out, err := ioutil.ReadAll(b) - if err != nil { - t.Fatal(err) - } - expectedMSG := "Wiretrustee service has been installed" - if string(out) != expectedMSG { - t.Fatalf("expected \"%s\" got \"%s\"", expectedMSG, string(out)) - } -} - -func Test_ServiceStartCMD(t *testing.T) { - b := bytes.NewBufferString("") - rootCmd.SetOut(b) - rootCmd.SetErr(b) - rootCmd.SetArgs([]string{"service", "start"}) - err := rootCmd.Execute() - if err != nil { - t.Fatal(err) - } - out, err := ioutil.ReadAll(b) - if err != nil { - t.Fatal(err) - } - expectedMSG := "Wiretrustee service has been started" - if string(out) != expectedMSG { - t.Fatalf("expected \"%s\" got \"%s\"", expectedMSG, string(out)) - } -} - -func Test_ServiceRunCMD(t *testing.T) { - configFilePath := "/tmp/config.json" - if _, err := os.Stat(configFilePath); err == nil { - e := os.Remove(configFilePath) - if e != nil { - t.Fatal(err) - } - } - rootCmd.SetArgs([]string{ - "--config", - configFilePath, - }) - err := rootCmd.Execute() - if err != nil { - t.Fatal(err) - } - - rootCmd.ResetFlags() - rootCmd.SetArgs([]string{"service", "start"}) - err = rootCmd.Execute() - if err != nil { - t.Fatal(err) - } - s, err := newSVC(&program{}, newSVCConfig()) - if err != nil { - t.Fatal(err) - } - status, err := s.Status() - if err != nil { - t.Fatal(err) - } - - if status != service.StatusRunning { - t.Fatalf("expected running status of \"%d\" got \"%d\"", service.StatusRunning, status) - } -} - -/*func Test_ServiceStopCMD(t *testing.T) { - b := bytes.NewBufferString("") - rootCmd.SetOut(b) - rootCmd.SetErr(b) - rootCmd.SetArgs([]string{"service", "stop"}) - err := rootCmd.Execute() - if err != nil { - t.Fatal(err) - } - out, err := ioutil.ReadAll(b) - if err != nil { - t.Fatal(err) - } - - expectedMSG := "Wiretrustee service has been stopped" - if string(out) != expectedMSG { - t.Fatalf("expected \"%s\" got \"%s\"", expectedMSG, string(out)) - } -}*/ - -func Test_ServiceUninstallCMD(t *testing.T) { - b := bytes.NewBufferString("") - rootCmd.SetOut(b) - rootCmd.SetErr(b) - rootCmd.SetArgs([]string{"service", "uninstall"}) - err := rootCmd.Execute() - if err != nil { - t.Fatal(err) - } - out, err := ioutil.ReadAll(b) - if err != nil { - t.Fatal(err) - } - expectedMSG := "Wiretrustee has been uninstalled" - if string(out) != expectedMSG { - t.Fatalf("expected \"%s\" got \"%s\"", expectedMSG, string(out)) - } -} diff --git a/client/cmd/testutil.go b/client/cmd/testutil.go new file mode 100644 index 000000000..436217002 --- /dev/null +++ b/client/cmd/testutil.go @@ -0,0 +1,54 @@ +package cmd + +import ( + 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) { + lis, err := net.Listen("tcp", ":0") + if err != nil { + t.Fatal(err) + } + s := grpc.NewServer() + sigProto.RegisterSignalExchangeServer(s, sig.NewServer()) + go func() { + if err := s.Serve(lis); err != nil { + panic(err) + } + }() + + return s, lis +} + +func startManagement(config *mgmt.Config, t *testing.T) (*grpc.Server, net.Listener) { + lis, err := net.Listen("tcp", ":0") + if err != nil { + t.Fatal(err) + } + s := grpc.NewServer() + store, err := mgmt.NewStore(config.Datadir) + if err != nil { + t.Fatal(err) + } + + accountManager := mgmt.NewManager(store) + mgmtServer, err := mgmt.NewServer(config, accountManager) + if err != nil { + t.Fatal(err) + } + mgmtProto.RegisterManagementServiceServer(s, mgmtServer) + go func() { + if err := s.Serve(lis); err != nil { + t.Error(err) + return + } + }() + + return s, lis +} diff --git a/client/cmd/up.go b/client/cmd/up.go index 2973b7709..e426db0b8 100644 --- a/client/cmd/up.go +++ b/client/cmd/up.go @@ -1,9 +1,7 @@ package cmd import ( - "bufio" "context" - "fmt" "github.com/pion/ice/v2" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -15,64 +13,58 @@ import ( "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "net/url" - "os" ) var ( - managementAddr string - upCmd = &cobra.Command{ Use: "up", Short: "start wiretrustee", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { InitLog(logLevel) - config, err := internal.GetConfig(managementAddr, configPath) + config, err := internal.ReadConfig(managementURL, configPath) if err != nil { - log.Errorf("failed getting config %s %v", configPath, err) - os.Exit(ExitSetupFailed) + log.Errorf("failed reading config %s %v", configPath, err) + //os.Exit(ExitSetupFailed) + 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()) - os.Exit(ExitSetupFailed) + //os.Exit(ExitSetupFailed) + return err } ctx := context.Background() - managementURL, err := url.Parse(config.ManagementURL) - if err != nil { - log.Errorf("failed parsing managemtn URL%s: [%s]", config.ManagementURL, err.Error()) - os.Exit(ExitSetupFailed) - } - mgmTlsEnabled := false - if managementURL.Scheme == "https" { + 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, managementURL.Host, myPrivateKey, mgmTlsEnabled) + mgmClient, loginResp, err := connectToManagement(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled) if err != nil { - log.Error(err) - os.Exit(ExitSetupFailed) + log.Warn(err) + //os.Exit(ExitSetupFailed) + 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) - os.Exit(ExitSetupFailed) + //os.Exit(ExitSetupFailed) + return err } engineConfig, err := createEngineConfig(myPrivateKey, config, loginResp.GetWiretrusteeConfig(), loginResp.GetPeerConfig()) if err != nil { log.Error(err) - os.Exit(ExitSetupFailed) + //os.Exit(ExitSetupFailed) + return err } // create start the Wiretrustee Engine that will connect to the Signal and Management streams and manage connections to remote peers. @@ -80,7 +72,8 @@ var ( err = engine.Start() if err != nil { log.Errorf("error while starting Wiretrustee Connection Engine: %s", err) - os.Exit(ExitSetupFailed) + //os.Exit(ExitSetupFailed) + return err } SetupCloseHandler() @@ -89,23 +82,30 @@ var ( err = mgmClient.Close() if err != nil { log.Errorf("failed closing Management Service client %v", err) + //os.Exit(ExitSetupFailed) + return err } err = signalClient.Close() if err != nil { log.Errorf("failed closing Signal Service client %v", err) + //os.Exit(ExitSetupFailed) + return err } log.Debugf("removing Wiretrustee interface %s", config.WgIface) err = iface.Close() if err != nil { log.Errorf("failed closing Wiretrustee interface %s %v", config.WgIface, err) + //os.Exit(ExitSetupFailed) + return err } + + return nil }, } ) func init() { - upCmd.PersistentFlags().StringVar(&managementAddr, "management-addr", "", "Management Service address (e.g. app.wiretrustee.com") } // createEngineConfig converts configuration received from Management Service to EngineConfig @@ -161,6 +161,7 @@ func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig, } 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) @@ -170,75 +171,31 @@ func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig, return signalClient, nil } -// connectToManagement creates Management Services client, establishes a connection and gets a global Wiretrustee config (signal, turn, stun hosts, etc) +// 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.Client, *mgmProto.LoginResponse, error) { log.Debugf("connecting to management server %s", managementAddr) - mgmClient, err := mgm.NewClient(ctx, managementAddr, ourPrivateKey, tlsEnabled) + 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) - serverKey, err := mgmClient.GetServerPublicKey() + serverPublicKey, err := client.GetServerPublicKey() if err != nil { return nil, nil, status.Errorf(codes.FailedPrecondition, "failed while getting Management Service public key: %s", err) } - wtConfig, err := loginPeer(*serverKey, mgmClient) - if err != nil { - return nil, nil, status.Errorf(codes.FailedPrecondition, "failed logging-in peer on Management Service : %s", err) - } - - log.Debugf("peer logged in to Management Service %s", wtConfig) - - return mgmClient, wtConfig, nil -} - -func registerPeer(serverPublicKey wgtypes.Key, client *mgm.Client) (*mgmProto.LoginResponse, error) { - setupKey, err := promptPeerSetupKey() - if err != nil { - log.Errorf("failed getting setup key: %s", err) - return nil, err - } - - log.Debugf("sending peer registration request") - loginResp, err := client.Register(serverPublicKey, *setupKey) - if err != nil { - log.Errorf("failed registering peer %v", err) - return nil, err - } - - return loginResp, nil -} - -func loginPeer(serverPublicKey wgtypes.Key, client *mgm.Client) (*mgmProto.LoginResponse, error) { - - loginResp, err := client.Login(serverPublicKey) + 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) + log.Error("peer registration required. Please run wiretrustee login command first") + return nil, nil, err } else { - return nil, err + return nil, nil, err } } - return loginResp, nil -} - -// promptPeerSetupKey prompts user to input a 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 - } - fmt.Println("Specified key is empty, try again:") - - } - - return nil, s.Err() + log.Infof("peer logged in to Management Service %s", managementAddr) + + return client, loginResp, nil } diff --git a/client/cmd/up_test.go b/client/cmd/up_test.go new file mode 100644 index 000000000..811507b46 --- /dev/null +++ b/client/cmd/up_test.go @@ -0,0 +1,110 @@ +package cmd + +import ( + "errors" + "fmt" + "github.com/wiretrustee/wiretrustee/iface" + mgmt "github.com/wiretrustee/wiretrustee/management/server" + "github.com/wiretrustee/wiretrustee/util" + "net/url" + "os" + "path/filepath" + "testing" + "time" +) + +var signalAddr string + +func TestUp_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(config, t) + mgmAddr = mgmLis.Addr().String() + +} + +func TestUp_ShouldFail_On_NoConfig(t *testing.T) { + + tempDir := t.TempDir() + confPath := tempDir + "/config.json" + mgmtURL := fmt.Sprintf("http://%s", mgmAddr) + rootCmd.SetArgs([]string{ + "up", + "--config", + confPath, + "--management-url", + mgmtURL, + }) + err := rootCmd.Execute() + if err == nil || !errors.Is(err, os.ErrNotExist) { + t.Errorf("expecting login command to fail on absence of config") + } +} + +func TestUp(t *testing.T) { + + defer iface.Close() + + tempDir := t.TempDir() + confPath := tempDir + "/config.json" + mgmtURL, err := url.Parse("http://" + mgmAddr) + if err != nil { + t.Fatal(err) + } + rootCmd.SetArgs([]string{ + "login", + "--config", + confPath, + "--setup-key", + "a2c8e62b-38f5-4553-b31e-dd66c696cebb", + "--management-url", + mgmtURL.String(), + }) + err = rootCmd.Execute() + if err != nil { + t.Fatal(err) + } + + rootCmd.SetArgs([]string{ + "up", + "--config", + confPath, + }) + go func() { + err = rootCmd.Execute() + if err != nil { + t.Errorf("expected no error while running up command, got %v", err) + } + }() + + exists := false + for start := time.Now(); time.Since(start) < 7*time.Second; { + e, err := iface.Exists(iface.WgInterfaceDefault) + if err != nil { + continue + } + if *e { + exists = true + break + } + + } + + if !exists { + t.Errorf("expected wireguard interface %s to be created", iface.WgInterfaceDefault) + } +} diff --git a/client/internal/config.go b/client/internal/config.go index f64f26856..c6eb353a2 100644 --- a/client/internal/config.go +++ b/client/internal/config.go @@ -1,20 +1,34 @@ package internal import ( + "fmt" log "github.com/sirupsen/logrus" "github.com/wiretrustee/wiretrustee/iface" "github.com/wiretrustee/wiretrustee/util" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "net/url" "os" ) -const ManagementAddrDefault = "https://app.wiretrustee.com" +var managementURLDefault *url.URL + +func ManagementURLDefault() *url.URL { + return managementURLDefault +} + +func init() { + managementURL, err := parseManagementURL("https://api.wiretrustee.com:33073") + if err != nil { + panic(err) + } + managementURLDefault = managementURL +} // Config Configuration type type Config struct { // Wireguard private key of local peer PrivateKey string - ManagementURL string + ManagementURL *url.URL WgIface string IFaceBlackList []string } @@ -24,11 +38,17 @@ func createNewConfig(managementURL string, configPath string) (*Config, error) { wgKey := generateKey() config := &Config{PrivateKey: wgKey, WgIface: iface.WgInterfaceDefault, IFaceBlackList: []string{}} if managementURL != "" { - config.ManagementURL = managementURL + URL, err := parseManagementURL(managementURL) + if err != nil { + return nil, err + } + config.ManagementURL = URL } else { - config.ManagementURL = ManagementAddrDefault + config.ManagementURL = managementURLDefault } + config.IFaceBlackList = []string{iface.WgInterfaceDefault, "tun0"} + err := util.WriteJson(configPath, config) if err != nil { return nil, err @@ -37,29 +57,50 @@ func createNewConfig(managementURL string, configPath string) (*Config, error) { return config, nil } -// GetConfig reads existing config or generates a new one -func GetConfig(managementURL string, configPath string) (*Config, error) { +func parseManagementURL(managementURL string) (*url.URL, error) { - var config *Config - if _, err := os.Stat(configPath); os.IsNotExist(err) { - log.Warnf("first run - generating new config %s", configPath) - config, err = createNewConfig(managementURL, configPath) - if err != nil { - return nil, err - } - } else { - config = &Config{} - _, err := util.ReadJson(configPath, config) - if err != nil { - return nil, err - } + parsedMgmtURL, err := url.ParseRequestURI(managementURL) + if err != nil { + log.Errorf("failed parsing management URL %s: [%s]", managementURL, err.Error()) + return nil, err + } + + if !(parsedMgmtURL.Scheme == "https" || parsedMgmtURL.Scheme == "http") { + return nil, fmt.Errorf("invalid Management Service URL provided %s. Supported format [http|https]://[host]:[port]", managementURL) + } + + return parsedMgmtURL, err + +} + +// ReadConfig reads existing config. In case provided managementURL is not empty overrides the read property +func ReadConfig(managementURL string, configPath string) (*Config, error) { + config := &Config{} + _, err := util.ReadJson(configPath, config) + if err != nil { + return nil, err } if managementURL != "" { - config.ManagementURL = managementURL + URL, err := parseManagementURL(managementURL) + if err != nil { + return nil, err + } + config.ManagementURL = URL } - return config, nil + return config, err +} + +// GetConfig reads existing config or generates a new one +func GetConfig(managementURL string, configPath string) (*Config, error) { + + if _, err := os.Stat(configPath); os.IsNotExist(err) { + log.Infof("generating new config %s", configPath) + return createNewConfig(managementURL, configPath) + } else { + return ReadConfig(managementURL, configPath) + } } // generateKey generates a new Wireguard private key diff --git a/client/testdata/management.json b/client/testdata/management.json new file mode 100644 index 000000000..8282a7756 --- /dev/null +++ b/client/testdata/management.json @@ -0,0 +1,33 @@ +{ + "Stuns": [ + { + "Proto": "udp", + "URI": "stun:stun.wiretrustee.com:3468", + "Username": "", + "Password": null + } + ], + "Turns": [ + { + "Proto": "udp", + "URI": "turn:stun.wiretrustee.com:3468", + "Username": "some_user", + "Password": "c29tZV9wYXNzd29yZA==" + } + ], + "Signal": { + "Proto": "http", + "URI": "signal.wiretrustee.com:10000", + "Username": "", + "Password": null + }, + "DataDir": "", + "HttpConfig": { + "LetsEncryptDomain": "", + "Address": "0.0.0.0:3000", + "AuthDomain": "", + "AuthClientId": "", + "AuthClientSecret": "", + "AuthCallback": "http://localhost:3000/callback" + } +} \ No newline at end of file diff --git a/client/testdata/store.json b/client/testdata/store.json new file mode 100644 index 000000000..a63c18727 --- /dev/null +++ b/client/testdata/store.json @@ -0,0 +1,21 @@ +{ + "Accounts": { + "bf1c8084-ba50-4ce7-9439-34653001fc3b": { + "Id": "bf1c8084-ba50-4ce7-9439-34653001fc3b", + "SetupKeys": { + "a2c8e62b-38f5-4553-b31e-dd66c696cebb": { + "Key": "a2c8e62b-38f5-4553-b31e-dd66c696cebb" + } + }, + "Network": { + "Id": "af1c8024-ha40-4ce2-9418-34653101fc3c", + "Net": { + "IP": "100.64.0.0", + "Mask": "/8AAAA==" + }, + "Dns": null + }, + "Peers": {} + } + } +} \ No newline at end of file diff --git a/management/client/client.go b/management/client/client.go index e70477369..74c4ad436 100644 --- a/management/client/client.go +++ b/management/client/client.go @@ -31,8 +31,10 @@ func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsE transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})) } + mgmCtx, cancel := context.WithTimeout(ctx, 3*time.Second) + defer cancel() conn, err := grpc.DialContext( - ctx, + mgmCtx, addr, transportOption, grpc.WithBlock(), diff --git a/signal/client/client.go b/signal/client/client.go index 85a18f968..7537f6e38 100644 --- a/signal/client/client.go +++ b/signal/client/client.go @@ -48,8 +48,10 @@ func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled boo transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})) } + sigCtx, cancel := context.WithTimeout(ctx, 3*time.Second) + defer cancel() conn, err := grpc.DialContext( - ctx, + sigCtx, addr, transportOption, grpc.WithBlock(),