diff --git a/client/cmd/root.go b/client/cmd/root.go index 9a02dcc44..26e9a76bf 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -94,6 +94,7 @@ func init() { rootCmd.AddCommand(statusCmd) rootCmd.AddCommand(loginCmd) rootCmd.AddCommand(versionCmd) + rootCmd.AddCommand(sshCmd) 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/ssh.go b/client/cmd/ssh.go new file mode 100644 index 000000000..b8ae20b9f --- /dev/null +++ b/client/cmd/ssh.go @@ -0,0 +1,137 @@ +package cmd + +import ( + "context" + "errors" + "fmt" + "github.com/netbirdio/netbird/client/internal" + "github.com/netbirdio/netbird/client/proto" + nbssh "github.com/netbirdio/netbird/client/ssh" + "github.com/netbirdio/netbird/util" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "os" + "os/signal" + "strings" + "syscall" +) + +var ( + port int + user = "netbird" + host string +) + +var sshCmd = &cobra.Command{ + Use: "ssh", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("requires a host argument") + } + + split := strings.Split(args[0], "@") + if len(split) == 2 { + user = split[0] + host = split[1] + } else { + host = args[0] + } + + return nil + }, + Short: "connect to a remote SSH server", + RunE: func(cmd *cobra.Command, args []string) error { + SetFlagsFromEnvVars() + + cmd.SetOut(cmd.OutOrStdout()) + + err := util.InitLog(logLevel, "console") + if err != nil { + return fmt.Errorf("failed initializing log %v", err) + } + + if !util.IsAdmin() { + cmd.Printf("error: you must have Administrator privileges to run this command\n") + return nil + } + + ctx := internal.CtxInitState(cmd.Context()) + + conn, err := DialClientGRPCServer(ctx, daemonAddr) + if err != nil { + return fmt.Errorf("failed to connect to daemon error: %v\n"+ + "If the daemon is not running please run: "+ + "\nnetbird service install \nnetbird service start\n", err) + } + + defer func() { + err := conn.Close() + if err != nil { + log.Warnf("failed closing dameon gRPC client connection %v", err) + return + } + }() + client := proto.NewDaemonServiceClient(conn) + + status, err := client.Status(ctx, &proto.StatusRequest{}) + if err != nil { + return fmt.Errorf("unable to get daemon status: %v", err) + } + + if status.Status != string(internal.StatusConnected) { + // todo maybe automatically start it? + cmd.Printf("You are disconnected from the NetBird network. Please run the UP command first to connect: \n\n" + + " netbird up \n\n") + return nil + } + + config, err := internal.ReadConfig("", "", configPath, nil) + if err != nil { + return err + } + + sig := make(chan os.Signal, 1) + signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT) + sshctx, cancel := context.WithCancel(ctx) + + go func() { + if err := runSSH(sshctx, host, []byte(config.SSHKey)); err != nil { + log.Print(err) + } + cancel() + }() + + select { + case <-sig: + cancel() + case <-sshctx.Done(): + } + + return nil + }, +} + +func runSSH(ctx context.Context, addr string, pemKey []byte) error { + c, err := nbssh.DialWithKey(fmt.Sprintf("%s:%d", addr, port), user, pemKey) + if err != nil { + return err + } + go func() { + <-ctx.Done() + err = c.Close() + if err != nil { + return + } + }() + + err = c.OpenTerminal() + if err != nil { + return err + } + + return nil +} + +func init() { + sshCmd.PersistentFlags().IntVarP(&port, "port", "p", nbssh.DefaultSSHPort, "Sets remote SSH port. Defaults to "+fmt.Sprint(nbssh.DefaultSSHPort)) +} diff --git a/client/internal/config.go b/client/internal/config.go index e37d6e3e5..dcb26ee36 100644 --- a/client/internal/config.go +++ b/client/internal/config.go @@ -3,16 +3,16 @@ package internal import ( "context" "fmt" + "github.com/netbirdio/netbird/client/ssh" + "github.com/netbirdio/netbird/iface" mgm "github.com/netbirdio/netbird/management/client" + "github.com/netbirdio/netbird/util" + log "github.com/sirupsen/logrus" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "net/url" "os" - - "github.com/netbirdio/netbird/iface" - "github.com/netbirdio/netbird/util" - log "github.com/sirupsen/logrus" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) var managementURLDefault *url.URL @@ -38,12 +38,18 @@ type Config struct { AdminURL *url.URL WgIface string IFaceBlackList []string + // SSHKey is a private SSH key in a PEM format + SSHKey string } // createNewConfig creates a new config generating a new Wireguard key and saving to file func createNewConfig(managementURL, adminURL, configPath, preSharedKey string) (*Config, error) { wgKey := generateKey() - config := &Config{PrivateKey: wgKey, WgIface: iface.WgInterfaceDefault, IFaceBlackList: []string{}} + pem, err := ssh.GeneratePrivateKey(ssh.ED25519) + if err != nil { + return nil, err + } + config := &Config{SSHKey: string(pem), PrivateKey: wgKey, WgIface: iface.WgInterfaceDefault, IFaceBlackList: []string{}} if managementURL != "" { URL, err := parseURL("Management URL", managementURL) if err != nil { @@ -61,7 +67,7 @@ func createNewConfig(managementURL, adminURL, configPath, preSharedKey string) ( config.IFaceBlackList = []string{iface.WgInterfaceDefault, "tun0", "zt", "ZeroTier", "utun", "wg", "ts", "Tailscale", "tailscale"} - err := util.WriteJson(configPath, config) + err = util.WriteJson(configPath, config) if err != nil { return nil, err } @@ -126,6 +132,14 @@ func ReadConfig(managementURL, adminURL, configPath string, preSharedKey *string config.PreSharedKey = *preSharedKey refresh = true } + if config.SSHKey == "" { + pem, err := ssh.GeneratePrivateKey(ssh.ED25519) + if err != nil { + return nil, err + } + config.SSHKey = string(pem) + refresh = true + } if refresh { // since we have new management URL, we need to update config file diff --git a/client/internal/connect.go b/client/internal/connect.go index fcf11f9c1..074f275e1 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -2,6 +2,7 @@ package internal import ( "context" + "github.com/netbirdio/netbird/client/ssh" "time" "github.com/netbirdio/netbird/client/system" @@ -63,8 +64,13 @@ func RunClient(ctx context.Context, config *Config) error { engineCtx, cancel := context.WithCancel(ctx) defer cancel() + publicSSHKey, err := ssh.GeneratePublicKey([]byte(config.SSHKey)) + if err != nil { + return err + } // connect (just a connection, no stream yet) and login to Management Service to get an initial global Wiretrustee config - mgmClient, loginResp, err := connectToManagement(engineCtx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled) + mgmClient, loginResp, err := connectToManagement(engineCtx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled, + publicSSHKey) if err != nil { log.Debug(err) if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied { @@ -147,6 +153,7 @@ func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.Pe IFaceBlackList: config.IFaceBlackList, WgPrivateKey: key, WgPort: iface.DefaultWgPort, + SSHKey: []byte(config.SSHKey), } if config.PreSharedKey != "" { @@ -179,7 +186,7 @@ func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig, } // 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) { +func connectToManagement(ctx context.Context, managementAddr string, ourPrivateKey wgtypes.Key, tlsEnabled bool, pubSSHKey []byte) (*mgm.GrpcClient, *mgmProto.LoginResponse, error) { log.Debugf("connecting to Management Service %s", managementAddr) client, err := mgm.NewClient(ctx, managementAddr, ourPrivateKey, tlsEnabled) if err != nil { @@ -193,7 +200,7 @@ func connectToManagement(ctx context.Context, managementAddr string, ourPrivateK } sysInfo := system.GetInfo(ctx) - loginResp, err := client.Login(*serverPublicKey, sysInfo) + loginResp, err := client.Login(*serverPublicKey, sysInfo, pubSSHKey) if err != nil { return nil, nil, err } diff --git a/client/internal/engine.go b/client/internal/engine.go index ca33852ad..0271bd599 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -3,8 +3,10 @@ package internal import ( "context" "fmt" + nbssh "github.com/netbirdio/netbird/client/ssh" "math/rand" "net" + "runtime" "strings" "sync" "time" @@ -54,6 +56,9 @@ type EngineConfig struct { // UDPMuxSrflxPort default value 0 - the system will pick an available port UDPMuxSrflxPort int + + // SSHKey is a private SSH key in a PEM format + SSHKey []byte } // Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers. @@ -87,6 +92,9 @@ type Engine struct { // networkSerial is the latest CurrentSerial (state ID) of the network sent by the Management service networkSerial uint64 + + sshServerFunc func(hostKeyPEM []byte, addr string) (nbssh.Server, error) + sshServer nbssh.Server } // Peer is an instance of the Connection Peer @@ -111,6 +119,7 @@ func NewEngine( STUNs: []*ice.URL{}, TURNs: []*ice.URL{}, networkSerial: 0, + sshServerFunc: nbssh.DefaultSSHServer, } } @@ -283,9 +292,14 @@ func (e *Engine) removeAllPeers() error { return nil } -// removePeer closes an existing peer connection and removes a peer +// removePeer closes an existing peer connection, removes a peer, and clears authorized key of the SSH server func (e *Engine) removePeer(peerKey string) error { log.Debugf("removing peer from engine %s", peerKey) + + if e.sshServer != nil { + e.sshServer.RemoveAuthorizedKey(peerKey) + } + conn, exists := e.peerConns[peerKey] if exists { delete(e.peerConns, peerKey) @@ -398,12 +412,6 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error { } if update.GetNetworkMap() != nil { - if update.GetNetworkMap().GetPeerConfig() != nil { - err := e.updateConfig(update.GetNetworkMap().GetPeerConfig()) - if err != nil { - return err - } - } // only apply new changes and ignore old ones err := e.updateNetworkMap(update.GetNetworkMap()) if err != nil { @@ -414,6 +422,49 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error { return nil } +func (e *Engine) updateSSH(sshConf *mgmProto.SSHConfig) error { + if sshConf.GetSshEnabled() { + if runtime.GOOS == "windows" { + log.Warnf("running SSH server on Windows is not supported") + return nil + } + // start SSH server if it wasn't running + if e.sshServer == nil { + //nil sshServer means it has not yet been started + var err error + e.sshServer, err = e.sshServerFunc(e.config.SSHKey, + fmt.Sprintf("%s:%d", e.wgInterface.Address.IP.String(), nbssh.DefaultSSHPort)) + if err != nil { + return err + } + go func() { + // blocking + err = e.sshServer.Start() + if err != nil { + // will throw error when we stop it even if it is a graceful stop + log.Debugf("stopped SSH server with error %v", err) + } + e.syncMsgMux.Lock() + defer e.syncMsgMux.Unlock() + e.sshServer = nil + log.Infof("stopped SSH server") + }() + } else { + log.Debugf("SSH server is already running") + } + } else { + // Disable SSH server request, so stop it if it was running + if e.sshServer != nil { + err := e.sshServer.Stop() + if err != nil { + log.Warnf("failed to stop SSH server %v", err) + } + e.sshServer = nil + } + } + return nil +} + func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error { if e.wgInterface.Address.String() != conf.Address { oldAddr := e.wgInterface.Address.String() @@ -422,9 +473,17 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error { if err != nil { return err } + e.config.WgAddr = conf.Address log.Infof("updated peer address from %s to %s", oldAddr, conf.Address) } + if conf.GetSshConfig() != nil { + err := e.updateSSH(conf.GetSshConfig()) + if err != nil { + log.Warnf("failed handling SSH server setup %v", e) + } + } + return nil } @@ -486,6 +545,15 @@ func (e *Engine) updateTURNs(turns []*mgmProto.ProtectedHostConfig) error { } func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error { + + // intentionally leave it before checking serial because for now it can happen that peer IP changed but serial didn't + if networkMap.GetPeerConfig() != nil { + err := e.updateConfig(networkMap.GetPeerConfig()) + if err != nil { + return err + } + } + serial := networkMap.GetSerial() if e.networkSerial > serial { log.Debugf("received outdated NetworkMap with serial %d, ignoring", serial) @@ -515,6 +583,18 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error { if err != nil { return err } + + // update SSHServer by adding remote peer SSH keys + if e.sshServer != nil { + for _, config := range networkMap.GetRemotePeers() { + if config.GetSshConfig() != nil && config.GetSshConfig().GetSshPubKey() != nil { + err := e.sshServer.AddAuthorizedKey(config.WgPubKey, string(config.GetSshConfig().GetSshPubKey())) + if err != nil { + log.Warnf("failed adding authroized key to SSH DefaultServer %v", err) + } + } + } + } } e.networkSerial = serial diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index 6408259c1..e7b767929 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -3,6 +3,9 @@ package internal import ( "context" "fmt" + "github.com/netbirdio/netbird/client/ssh" + "github.com/netbirdio/netbird/iface" + "github.com/stretchr/testify/assert" "net" "os" "path/filepath" @@ -40,6 +43,140 @@ var ( } ) +func TestEngine_SSH(t *testing.T) { + + if runtime.GOOS == "windows" { + t.Skip("skipping TestEngine_SSH on Windows") + } + + key, err := wgtypes.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + return + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{ + WgIfaceName: "utun101", + WgAddr: "100.64.0.1/24", + WgPrivateKey: key, + WgPort: 33100, + }) + + var sshKeysAdded []string + var sshPeersRemoved []string + + sshCtx, cancel := context.WithCancel(context.Background()) + + engine.sshServerFunc = func(hostKeyPEM []byte, addr string) (ssh.Server, error) { + return &ssh.MockServer{ + Ctx: sshCtx, + StopFunc: func() error { + cancel() + return nil + }, + StartFunc: func() error { + <-ctx.Done() + return ctx.Err() + }, + AddAuthorizedKeyFunc: func(peer, newKey string) error { + sshKeysAdded = append(sshKeysAdded, newKey) + return nil + }, + RemoveAuthorizedKeyFunc: func(peer string) { + sshPeersRemoved = append(sshPeersRemoved, peer) + }, + }, nil + } + err = engine.Start() + if err != nil { + t.Fatal(err) + } + + defer func() { + err := engine.Stop() + if err != nil { + return + } + }() + + peerWithSSH := &mgmtProto.RemotePeerConfig{ + WgPubKey: "MNHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=", + AllowedIps: []string{"100.64.0.21/24"}, + SshConfig: &mgmtProto.SSHConfig{ + SshPubKey: []byte("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFATYCqaQw/9id1Qkq3n16JYhDhXraI6Pc1fgB8ynEfQ"), + }, + } + + // SSH server is not enabled so SSH config of a remote peer should be ignored + networkMap := &mgmtProto.NetworkMap{ + Serial: 6, + PeerConfig: nil, + RemotePeers: []*mgmtProto.RemotePeerConfig{peerWithSSH}, + RemotePeersIsEmpty: false, + } + + err = engine.updateNetworkMap(networkMap) + if err != nil { + t.Fatal(err) + } + + assert.Nil(t, engine.sshServer) + + // SSH server is enabled, therefore SSH config should be applied + networkMap = &mgmtProto.NetworkMap{ + Serial: 7, + PeerConfig: &mgmtProto.PeerConfig{Address: "100.64.0.1/24", + SshConfig: &mgmtProto.SSHConfig{SshEnabled: true}}, + RemotePeers: []*mgmtProto.RemotePeerConfig{peerWithSSH}, + RemotePeersIsEmpty: false, + } + + err = engine.updateNetworkMap(networkMap) + if err != nil { + t.Fatal(err) + } + + time.Sleep(250 * time.Millisecond) + assert.NotNil(t, engine.sshServer) + assert.Contains(t, sshKeysAdded, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFATYCqaQw/9id1Qkq3n16JYhDhXraI6Pc1fgB8ynEfQ") + + // now remove peer + networkMap = &mgmtProto.NetworkMap{ + Serial: 8, + RemotePeers: []*mgmtProto.RemotePeerConfig{}, + RemotePeersIsEmpty: false, + } + + err = engine.updateNetworkMap(networkMap) + if err != nil { + t.Fatal(err) + } + + //time.Sleep(250 * time.Millisecond) + assert.NotNil(t, engine.sshServer) + assert.Contains(t, sshPeersRemoved, "MNHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=") + + // now disable SSH server + networkMap = &mgmtProto.NetworkMap{ + Serial: 9, + PeerConfig: &mgmtProto.PeerConfig{Address: "100.64.0.1/24", + SshConfig: &mgmtProto.SSHConfig{SshEnabled: false}}, + RemotePeers: []*mgmtProto.RemotePeerConfig{peerWithSSH}, + RemotePeersIsEmpty: false, + } + + err = engine.updateNetworkMap(networkMap) + if err != nil { + t.Fatal(err) + } + + assert.Nil(t, engine.sshServer) + +} + func TestEngine_UpdateNetworkMap(t *testing.T) { // test setup key, err := wgtypes.GeneratePrivateKey() @@ -52,11 +189,12 @@ func TestEngine_UpdateNetworkMap(t *testing.T) { defer cancel() engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{ - WgIfaceName: "utun100", + WgIfaceName: "utun102", WgAddr: "100.64.0.1/24", WgPrivateKey: key, WgPort: 33100, }) + engine.wgInterface, err = iface.NewWGIFace("utun102", "100.64.0.1/24", iface.DefaultMTU) type testCase struct { name string @@ -231,7 +369,7 @@ func TestEngine_Sync(t *testing.T) { } engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{SyncFunc: syncFunc}, &EngineConfig{ - WgIfaceName: "utun100", + WgIfaceName: "utun103", WgAddr: "100.64.0.1/24", WgPrivateKey: key, WgPort: 33100, @@ -418,7 +556,7 @@ func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey strin } info := system.GetInfo(ctx) - resp, err := mgmtClient.Register(*publicKey, setupKey, "", info) + resp, err := mgmtClient.Register(*publicKey, setupKey, "", info, nil) if err != nil { return nil, err } diff --git a/client/internal/login.go b/client/internal/login.go index 12c04ef21..a09bd3c21 100644 --- a/client/internal/login.go +++ b/client/internal/login.go @@ -2,8 +2,8 @@ package internal import ( "context" - "github.com/google/uuid" + "github.com/netbirdio/netbird/client/ssh" "github.com/netbirdio/netbird/client/system" mgm "github.com/netbirdio/netbird/management/client" mgmProto "github.com/netbirdio/netbird/management/proto" @@ -40,7 +40,11 @@ func Login(ctx context.Context, config *Config, setupKey string, jwtToken string return err } - _, err = loginPeer(ctx, *serverKey, mgmClient, setupKey, jwtToken) + pubSSHKey, err := ssh.GeneratePublicKey([]byte(config.SSHKey)) + if err != nil { + return err + } + _, err = loginPeer(ctx, *serverKey, mgmClient, setupKey, jwtToken, pubSSHKey) if err != nil { log.Errorf("failed logging-in peer on Management Service : %v", err) return err @@ -56,13 +60,13 @@ func Login(ctx context.Context, config *Config, setupKey string, jwtToken string } // loginPeer attempts to login to Management Service. If peer wasn't registered, tries the registration flow. -func loginPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string, jwtToken string) (*mgmProto.LoginResponse, error) { +func loginPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string, jwtToken string, pubSSHKey []byte) (*mgmProto.LoginResponse, error) { sysInfo := system.GetInfo(ctx) - loginResp, err := client.Login(serverPublicKey, sysInfo) + loginResp, err := client.Login(serverPublicKey, sysInfo, pubSSHKey) if err != nil { if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied { log.Debugf("peer registration required") - return registerPeer(ctx, serverPublicKey, client, setupKey, jwtToken) + return registerPeer(ctx, serverPublicKey, client, setupKey, jwtToken, pubSSHKey) } else { return nil, err } @@ -75,7 +79,7 @@ func loginPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.Grp // 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(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string, jwtToken string) (*mgmProto.LoginResponse, error) { +func registerPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string, jwtToken string, pubSSHKey []byte) (*mgmProto.LoginResponse, error) { validSetupKey, err := uuid.Parse(setupKey) if err != nil && jwtToken == "" { return nil, status.Errorf(codes.InvalidArgument, "invalid setup-key or no sso information provided, err: %v", err) @@ -83,7 +87,7 @@ func registerPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm. log.Debugf("sending peer registration request to Management Service") info := system.GetInfo(ctx) - loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), jwtToken, info) + loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), jwtToken, info, pubSSHKey) if err != nil { log.Errorf("failed registering peer %v,%s", err, validSetupKey.String()) return nil, err diff --git a/client/ssh/client.go b/client/ssh/client.go new file mode 100644 index 000000000..486a8f803 --- /dev/null +++ b/client/ssh/client.go @@ -0,0 +1,114 @@ +package ssh + +import ( + "fmt" + "golang.org/x/crypto/ssh" + "golang.org/x/term" + "net" + "os" +) + +// Client wraps crypto/ssh Client to simplify usage +type Client struct { + client *ssh.Client +} + +// Close closes the wrapped SSH Client +func (c *Client) Close() error { + return c.client.Close() +} + +// OpenTerminal starts an interactive terminal session with the remote SSH server +func (c *Client) OpenTerminal() error { + session, err := c.client.NewSession() + if err != nil { + return fmt.Errorf("failed to open new session: %v", err) + } + defer func() { + err := session.Close() + if err != nil { + return + } + }() + + fd := int(os.Stdout.Fd()) + state, err := term.MakeRaw(fd) + if err != nil { + return fmt.Errorf("failed to run raw terminal: %s", err) + } + defer func() { + err := term.Restore(fd, state) + if err != nil { + return + } + }() + + w, h, err := term.GetSize(fd) + if err != nil { + return fmt.Errorf("terminal get size: %s", err) + } + + modes := ssh.TerminalModes{ + ssh.ECHO: 1, + ssh.TTY_OP_ISPEED: 14400, + ssh.TTY_OP_OSPEED: 14400, + } + + terminal := os.Getenv("TERM") + if terminal == "" { + terminal = "xterm-256color" + } + if err := session.RequestPty(terminal, h, w, modes); err != nil { + return fmt.Errorf("failed requesting pty session with xterm: %s", err) + } + + session.Stdout = os.Stdout + session.Stderr = os.Stderr + session.Stdin = os.Stdin + + if err := session.Shell(); err != nil { + return fmt.Errorf("failed to start login shell on the remote host: %s", err) + } + + if err := session.Wait(); err != nil { + if e, ok := err.(*ssh.ExitError); ok { + switch e.ExitStatus() { + case 130: + return nil + } + } + return fmt.Errorf("failed running SSH session: %s", err) + } + + return nil +} + +// DialWithKey connects to the remote SSH server with a provided private key file (PEM). +func DialWithKey(addr, user string, privateKey []byte) (*Client, error) { + + signer, err := ssh.ParsePrivateKey(privateKey) + if err != nil { + return nil, err + } + + config := &ssh.ClientConfig{ + User: user, + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(signer), + }, + HostKeyCallback: ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }), + } + + return Dial("tcp", addr, config) +} + +// Dial connects to the remote SSH server. +func Dial(network, addr string, config *ssh.ClientConfig) (*Client, error) { + client, err := ssh.Dial(network, addr, config) + if err != nil { + return nil, err + } + return &Client{ + client: client, + }, nil +} diff --git a/client/ssh/server.go b/client/ssh/server.go new file mode 100644 index 000000000..9180f0bf9 --- /dev/null +++ b/client/ssh/server.go @@ -0,0 +1,183 @@ +package ssh + +import ( + "fmt" + "github.com/creack/pty" + "github.com/gliderlabs/ssh" + log "github.com/sirupsen/logrus" + "io" + "net" + "os" + "os/exec" + "sync" +) + +// DefaultSSHPort is the default SSH port of the NetBird's embedded SSH server +const DefaultSSHPort = 44338 + +// DefaultSSHServer is a function that creates DefaultServer +func DefaultSSHServer(hostKeyPEM []byte, addr string) (Server, error) { + return newDefaultServer(hostKeyPEM, addr) +} + +// Server is an interface of SSH server +type Server interface { + // Stop stops SSH server. + Stop() error + // Start starts SSH server. Blocking + Start() error + // RemoveAuthorizedKey removes SSH key of a given peer from the authorized keys + RemoveAuthorizedKey(peer string) + // AddAuthorizedKey add a given peer key to server authorized keys + AddAuthorizedKey(peer, newKey string) error +} + +// DefaultServer is the embedded NetBird SSH server +type DefaultServer struct { + listener net.Listener + // authorizedKeys is ssh pub key indexed by peer WireGuard public key + authorizedKeys map[string]ssh.PublicKey + mu sync.Mutex + hostKeyPEM []byte + sessions []ssh.Session +} + +// newDefaultServer creates new server with provided host key +func newDefaultServer(hostKeyPEM []byte, addr string) (*DefaultServer, error) { + ln, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + allowedKeys := make(map[string]ssh.PublicKey) + return &DefaultServer{listener: ln, mu: sync.Mutex{}, hostKeyPEM: hostKeyPEM, authorizedKeys: allowedKeys, sessions: make([]ssh.Session, 0)}, nil +} + +// RemoveAuthorizedKey removes SSH key of a given peer from the authorized keys +func (srv *DefaultServer) RemoveAuthorizedKey(peer string) { + srv.mu.Lock() + defer srv.mu.Unlock() + + delete(srv.authorizedKeys, peer) +} + +// AddAuthorizedKey add a given peer key to server authorized keys +func (srv *DefaultServer) AddAuthorizedKey(peer, newKey string) error { + srv.mu.Lock() + defer srv.mu.Unlock() + + parsedKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(newKey)) + if err != nil { + return err + } + + srv.authorizedKeys[peer] = parsedKey + return nil +} + +// Stop stops SSH server. +func (srv *DefaultServer) Stop() error { + srv.mu.Lock() + defer srv.mu.Unlock() + err := srv.listener.Close() + if err != nil { + return err + } + for _, session := range srv.sessions { + err := session.Close() + if err != nil { + log.Warnf("failed closing SSH session from %v", err) + } + } + + return nil +} + +func (srv *DefaultServer) publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { + srv.mu.Lock() + defer srv.mu.Unlock() + + for _, allowed := range srv.authorizedKeys { + if ssh.KeysEqual(allowed, key) { + return true + } + } + + return false +} + +func getShellType() string { + shell := os.Getenv("SHELL") + if shell == "" { + shell = "sh" + } + return shell +} + +// sessionHandler handles SSH session post auth +func (srv *DefaultServer) sessionHandler(session ssh.Session) { + srv.mu.Lock() + srv.sessions = append(srv.sessions, session) + srv.mu.Unlock() + ptyReq, winCh, isPty := session.Pty() + if isPty { + cmd := exec.Command(getShellType()) + cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%session", ptyReq.Term)) + file, err := pty.Start(cmd) + if err != nil { + log.Errorf("failed starting SSH server %v", err) + } + go func() { + for win := range winCh { + setWinSize(file, win.Width, win.Height) + } + }() + + srv.stdInOut(file, session) + + err = cmd.Wait() + if err != nil { + return + } + } else { + _, err := io.WriteString(session, "only PTY is supported.\n") + if err != nil { + return + } + err = session.Exit(1) + if err != nil { + return + } + } +} + +func (srv *DefaultServer) stdInOut(file *os.File, session ssh.Session) { + go func() { + // stdin + _, err := io.Copy(file, session) + if err != nil { + return + } + }() + + go func() { + // stdout + _, err := io.Copy(session, file) + if err != nil { + return + } + }() +} + +// Start starts SSH server. Blocking +func (srv *DefaultServer) Start() error { + log.Infof("starting SSH server on addr: %s", srv.listener.Addr().String()) + + publicKeyOption := ssh.PublicKeyAuth(srv.publicKeyHandler) + hostKeyPEM := ssh.HostKeyPEM(srv.hostKeyPEM) + err := ssh.Serve(srv.listener, srv.sessionHandler, publicKeyOption, hostKeyPEM) + if err != nil { + return err + } + + return nil +} diff --git a/client/ssh/server_mock.go b/client/ssh/server_mock.go new file mode 100644 index 000000000..cc080ffdb --- /dev/null +++ b/client/ssh/server_mock.go @@ -0,0 +1,44 @@ +package ssh + +import "context" + +// MockServer mocks ssh.Server +type MockServer struct { + Ctx context.Context + StopFunc func() error + StartFunc func() error + AddAuthorizedKeyFunc func(peer, newKey string) error + RemoveAuthorizedKeyFunc func(peer string) +} + +// RemoveAuthorizedKey removes SSH key of a given peer from the authorized keys +func (srv *MockServer) RemoveAuthorizedKey(peer string) { + if srv.RemoveAuthorizedKeyFunc == nil { + return + } + srv.RemoveAuthorizedKeyFunc(peer) +} + +// AddAuthorizedKey add a given peer key to server authorized keys +func (srv *MockServer) AddAuthorizedKey(peer, newKey string) error { + if srv.AddAuthorizedKeyFunc == nil { + return nil + } + return srv.AddAuthorizedKeyFunc(peer, newKey) +} + +// Stop stops SSH server. +func (srv *MockServer) Stop() error { + if srv.StopFunc == nil { + return nil + } + return srv.StopFunc() +} + +// Start starts SSH server. Blocking +func (srv *MockServer) Start() error { + if srv.StartFunc == nil { + return nil + } + return srv.StartFunc() +} diff --git a/client/ssh/server_test.go b/client/ssh/server_test.go new file mode 100644 index 000000000..5caca1834 --- /dev/null +++ b/client/ssh/server_test.go @@ -0,0 +1,121 @@ +package ssh + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "golang.org/x/crypto/ssh" + "strings" + "testing" +) + +func TestServer_AddAuthorizedKey(t *testing.T) { + key, err := GeneratePrivateKey(ED25519) + if err != nil { + t.Fatal(err) + } + server, err := newDefaultServer(key, "localhost:") + if err != nil { + t.Fatal(err) + } + + // add multiple keys + keys := map[string][]byte{} + for i := 0; i < 10; i++ { + peer := fmt.Sprintf("%s-%d", "remotePeer", i) + remotePrivKey, err := GeneratePrivateKey(ED25519) + if err != nil { + t.Fatal(err) + } + remotePubKey, err := GeneratePublicKey(remotePrivKey) + if err != nil { + t.Fatal(err) + } + + err = server.AddAuthorizedKey(peer, string(remotePubKey)) + if err != nil { + t.Error(err) + } + keys[peer] = remotePubKey + } + + // make sure that all keys have been added + for peer, remotePubKey := range keys { + k, ok := server.authorizedKeys[peer] + assert.True(t, ok, "expecting remotePeer key to be found in authorizedKeys") + + assert.Equal(t, string(remotePubKey), strings.TrimSpace(string(ssh.MarshalAuthorizedKey(k)))) + } + +} + +func TestServer_RemoveAuthorizedKey(t *testing.T) { + key, err := GeneratePrivateKey(ED25519) + if err != nil { + t.Fatal(err) + } + server, err := newDefaultServer(key, "localhost:") + if err != nil { + t.Fatal(err) + } + + remotePrivKey, err := GeneratePrivateKey(ED25519) + if err != nil { + t.Fatal(err) + } + remotePubKey, err := GeneratePublicKey(remotePrivKey) + if err != nil { + t.Fatal(err) + } + + err = server.AddAuthorizedKey("remotePeer", string(remotePubKey)) + if err != nil { + t.Error(err) + } + + server.RemoveAuthorizedKey("remotePeer") + + _, ok := server.authorizedKeys["remotePeer"] + assert.False(t, ok, "expecting remotePeer's SSH key to be removed") +} + +func TestServer_PubKeyHandler(t *testing.T) { + key, err := GeneratePrivateKey(ED25519) + if err != nil { + t.Fatal(err) + } + server, err := newDefaultServer(key, "localhost:") + if err != nil { + t.Fatal(err) + } + + var keys []ssh.PublicKey + for i := 0; i < 10; i++ { + peer := fmt.Sprintf("%s-%d", "remotePeer", i) + remotePrivKey, err := GeneratePrivateKey(ED25519) + if err != nil { + t.Fatal(err) + } + remotePubKey, err := GeneratePublicKey(remotePrivKey) + if err != nil { + t.Fatal(err) + } + + remoteParsedPubKey, _, _, _, err := ssh.ParseAuthorizedKey(remotePubKey) + if err != nil { + t.Fatal(err) + } + + err = server.AddAuthorizedKey(peer, string(remotePubKey)) + if err != nil { + t.Error(err) + } + keys = append(keys, remoteParsedPubKey) + } + + for _, key := range keys { + accepted := server.publicKeyHandler(nil, key) + + assert.Truef(t, accepted, "expecting SSH connection to be accepted for a given SSH key %s", string(ssh.MarshalAuthorizedKey(key))) + } + +} diff --git a/client/ssh/util.go b/client/ssh/util.go new file mode 100644 index 000000000..8ecb77c93 --- /dev/null +++ b/client/ssh/util.go @@ -0,0 +1,86 @@ +package ssh + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "golang.org/x/crypto/ed25519" + gossh "golang.org/x/crypto/ssh" + "strings" +) + +// KeyType is a type of SSH key +type KeyType string + +// ED25519 is key of type ed25519 +const ED25519 KeyType = "ed25519" + +// ECDSA is key of type ecdsa +const ECDSA KeyType = "ecdsa" + +// RSA is key of type rsa +const RSA KeyType = "rsa" + +// RSAKeySize is a size of newly generated RSA key +const RSAKeySize = 2048 + +// GeneratePrivateKey creates RSA Private Key of specified byte size +func GeneratePrivateKey(keyType KeyType) ([]byte, error) { + + var key crypto.Signer + var err error + switch keyType { + case ED25519: + _, key, err = ed25519.GenerateKey(rand.Reader) + case ECDSA: + key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + case RSA: + key, err = rsa.GenerateKey(rand.Reader, RSAKeySize) + default: + return nil, fmt.Errorf("unsupported ket type %s", keyType) + } + if err != nil { + return nil, err + } + + pemBytes, err := EncodePrivateKeyToPEM(key) + if err != nil { + return nil, err + } + + return pemBytes, nil +} + +// GeneratePublicKey returns the public part of the private key +func GeneratePublicKey(key []byte) ([]byte, error) { + signer, err := gossh.ParsePrivateKey(key) + if err != nil { + return nil, err + } + + strKey := strings.TrimSpace(string(gossh.MarshalAuthorizedKey(signer.PublicKey()))) + return []byte(strKey), nil +} + +// EncodePrivateKeyToPEM encodes Private Key from RSA to PEM format +func EncodePrivateKeyToPEM(privateKey crypto.Signer) ([]byte, error) { + mk, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return nil, err + } + + // pem.Block + privBlock := pem.Block{ + Type: "PRIVATE KEY", + Bytes: mk, + } + + // Private key in PEM format + privatePEM := pem.EncodeToMemory(&privBlock) + return privatePEM, nil +} diff --git a/client/ssh/window_unix.go b/client/ssh/window_unix.go new file mode 100644 index 000000000..2891eb70e --- /dev/null +++ b/client/ssh/window_unix.go @@ -0,0 +1,14 @@ +//go:build linux || darwin + +package ssh + +import ( + "os" + "syscall" + "unsafe" +) + +func setWinSize(file *os.File, width, height int) { + syscall.Syscall(syscall.SYS_IOCTL, file.Fd(), uintptr(syscall.TIOCSWINSZ), //nolint + uintptr(unsafe.Pointer(&struct{ h, w, x, y uint16 }{uint16(height), uint16(width), 0, 0}))) +} diff --git a/client/ssh/window_windows.go b/client/ssh/window_windows.go new file mode 100644 index 000000000..5abd41f27 --- /dev/null +++ b/client/ssh/window_windows.go @@ -0,0 +1,9 @@ +package ssh + +import ( + "os" +) + +func setWinSize(file *os.File, width, height int) { + +} diff --git a/go.mod b/go.mod index 9f1a6b334..8cfd0e43e 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/vishvananda/netlink v1.1.0 golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 - golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a + golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 golang.zx2c4.com/wireguard v0.0.0-20211209221555-9c9e7e272434 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de golang.zx2c4.com/wireguard/windows v0.5.1 @@ -30,18 +30,22 @@ require ( require ( fyne.io/fyne/v2 v2.1.4 github.com/c-robinson/iplib v1.0.3 + github.com/creack/pty v1.1.18 github.com/eko/gocache/v2 v2.3.1 github.com/getlantern/systray v1.2.1 + github.com/gliderlabs/ssh v0.3.4 github.com/magiconair/properties v1.8.5 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/rs/xid v1.3.0 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/stretchr/testify v1.7.1 + golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 ) require ( github.com/BurntSushi/toml v0.4.1 // indirect github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect @@ -110,5 +114,3 @@ require ( ) replace github.com/pion/ice/v2 => github.com/wiretrustee/ice/v2 v2.1.21-0.20220218121004-dc81faead4bb - -//replace github.com/eko/gocache/v3 => /home/braginini/Documents/projects/my/wiretrustee/gocache diff --git a/go.sum b/go.sum index c38b83a2b..bcf23d9dc 100644 --- a/go.sum +++ b/go.sum @@ -69,6 +69,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9IrprcQfI= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -118,6 +120,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -178,6 +182,8 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2H 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= +github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw= +github.com/gliderlabs/ssh v0.3.4/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914= github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f h1:s0O46d8fPwk9kU4k1jj76wBquMVETx7uveQD9MCIQoU= github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -644,6 +650,7 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/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-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -891,8 +898,13 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY= golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU= +golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/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 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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= diff --git a/management/client/client.go b/management/client/client.go index 1deea690f..3ca79d3b6 100644 --- a/management/client/client.go +++ b/management/client/client.go @@ -12,7 +12,7 @@ type Client interface { io.Closer Sync(msgHandler func(msg *proto.SyncResponse) error) error GetServerPublicKey() (*wgtypes.Key, error) - Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info) (*proto.LoginResponse, error) - Login(serverKey wgtypes.Key, sysInfo *system.Info) (*proto.LoginResponse, error) + Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error) + Login(serverKey wgtypes.Key, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error) GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error) } diff --git a/management/client/client_test.go b/management/client/client_test.go index 02de07eb7..19421d19f 100644 --- a/management/client/client_test.go +++ b/management/client/client_test.go @@ -158,7 +158,7 @@ func TestClient_LoginUnregistered_ShouldThrow_401(t *testing.T) { t.Fatal(err) } sysInfo := system.GetInfo(context.TODO()) - _, err = client.Login(*key, sysInfo) + _, err = client.Login(*key, sysInfo, nil) if err == nil { t.Error("expecting err on unregistered login, got nil") } @@ -186,7 +186,7 @@ func TestClient_LoginRegistered(t *testing.T) { t.Error(err) } info := system.GetInfo(context.TODO()) - resp, err := client.Register(*key, ValidKey, "", info) + resp, err := client.Register(*key, ValidKey, "", info, nil) if err != nil { t.Error(err) } @@ -216,7 +216,7 @@ func TestClient_Sync(t *testing.T) { } info := system.GetInfo(context.TODO()) - _, err = client.Register(*serverKey, ValidKey, "", info) + _, err = client.Register(*serverKey, ValidKey, "", info, nil) if err != nil { t.Error(err) } @@ -232,7 +232,7 @@ func TestClient_Sync(t *testing.T) { } info = system.GetInfo(context.TODO()) - _, err = remoteClient.Register(*serverKey, ValidKey, "", info) + _, err = remoteClient.Register(*serverKey, ValidKey, "", info, nil) if err != nil { t.Fatal(err) } @@ -330,7 +330,7 @@ func Test_SystemMetaDataFromClient(t *testing.T) { } info := system.GetInfo(context.TODO()) - _, err = testClient.Register(*key, ValidKey, "", info) + _, err = testClient.Register(*key, ValidKey, "", info, nil) if err != nil { t.Errorf("error while trying to register client: %v", err) } diff --git a/management/client/grpc.go b/management/client/grpc.go index cca1d0dd8..39e94bc96 100644 --- a/management/client/grpc.go +++ b/management/client/grpc.go @@ -233,13 +233,21 @@ func (c *GrpcClient) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*pro // Register registers peer on Management Server. It actually calls a Login endpoint with a provided setup key // Takes care of encrypting and decrypting messages. // This method will also collect system info and send it with the request (e.g. hostname, os, etc) -func (c *GrpcClient) Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info) (*proto.LoginResponse, error) { - return c.login(serverKey, &proto.LoginRequest{SetupKey: setupKey, Meta: infoToMetaData(sysInfo), JwtToken: jwtToken}) +func (c *GrpcClient) Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info, pubSSHKey []byte) (*proto.LoginResponse, error) { + keys := &proto.PeerKeys{ + SshPubKey: pubSSHKey, + WgPubKey: []byte(c.key.PublicKey().String()), + } + return c.login(serverKey, &proto.LoginRequest{SetupKey: setupKey, Meta: infoToMetaData(sysInfo), JwtToken: jwtToken, PeerKeys: keys}) } // Login attempts login to Management Server. Takes care of encrypting and decrypting messages. -func (c *GrpcClient) Login(serverKey wgtypes.Key, sysInfo *system.Info) (*proto.LoginResponse, error) { - return c.login(serverKey, &proto.LoginRequest{Meta: infoToMetaData(sysInfo)}) +func (c *GrpcClient) Login(serverKey wgtypes.Key, sysInfo *system.Info, pubSSHKey []byte) (*proto.LoginResponse, error) { + keys := &proto.PeerKeys{ + SshPubKey: pubSSHKey, + WgPubKey: []byte(c.key.PublicKey().String()), + } + return c.login(serverKey, &proto.LoginRequest{Meta: infoToMetaData(sysInfo), PeerKeys: keys}) } // GetDeviceAuthorizationFlow returns a device authorization flow information. diff --git a/management/client/mock.go b/management/client/mock.go index 4d0d99149..f81b0b30c 100644 --- a/management/client/mock.go +++ b/management/client/mock.go @@ -10,8 +10,8 @@ type MockClient struct { CloseFunc func() error SyncFunc func(msgHandler func(msg *proto.SyncResponse) error) error GetServerPublicKeyFunc func() (*wgtypes.Key, error) - RegisterFunc func(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info) (*proto.LoginResponse, error) - LoginFunc func(serverKey wgtypes.Key, info *system.Info) (*proto.LoginResponse, error) + RegisterFunc func(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info, sshKey []byte) (*proto.LoginResponse, error) + LoginFunc func(serverKey wgtypes.Key, info *system.Info, sshKey []byte) (*proto.LoginResponse, error) GetDeviceAuthorizationFlowFunc func(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error) } @@ -36,18 +36,18 @@ func (m *MockClient) GetServerPublicKey() (*wgtypes.Key, error) { return m.GetServerPublicKeyFunc() } -func (m *MockClient) Register(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info) (*proto.LoginResponse, error) { +func (m *MockClient) Register(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info, sshKey []byte) (*proto.LoginResponse, error) { if m.RegisterFunc == nil { return nil, nil } - return m.RegisterFunc(serverKey, setupKey, jwtToken, info) + return m.RegisterFunc(serverKey, setupKey, jwtToken, info, sshKey) } -func (m *MockClient) Login(serverKey wgtypes.Key, info *system.Info) (*proto.LoginResponse, error) { +func (m *MockClient) Login(serverKey wgtypes.Key, info *system.Info, sshKey []byte) (*proto.LoginResponse, error) { if m.LoginFunc == nil { return nil, nil } - return m.LoginFunc(serverKey, info) + return m.LoginFunc(serverKey, info, sshKey) } func (m *MockClient) GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error) { diff --git a/management/proto/management.pb.go b/management/proto/management.pb.go index b66542ae6..8fae8560f 100644 --- a/management/proto/management.pb.go +++ b/management/proto/management.pb.go @@ -1,15 +1,15 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v3.20.1 +// protoc v3.12.4 // source: management.proto package proto import ( + timestamp "github.com/golang/protobuf/ptypes/timestamp" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) @@ -73,7 +73,7 @@ func (x HostConfig_Protocol) Number() protoreflect.EnumNumber { // Deprecated: Use HostConfig_Protocol.Descriptor instead. func (HostConfig_Protocol) EnumDescriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{9, 0} + return file_management_proto_rawDescGZIP(), []int{10, 0} } type DeviceAuthorizationFlowProvider int32 @@ -116,7 +116,7 @@ func (x DeviceAuthorizationFlowProvider) Number() protoreflect.EnumNumber { // Deprecated: Use DeviceAuthorizationFlowProvider.Descriptor instead. func (DeviceAuthorizationFlowProvider) EnumDescriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{15, 0} + return file_management_proto_rawDescGZIP(), []int{17, 0} } type EncryptedMessage struct { @@ -319,6 +319,8 @@ type LoginRequest struct { Meta *PeerSystemMeta `protobuf:"bytes,2,opt,name=meta,proto3" json:"meta,omitempty"` // SSO token (can be empty) JwtToken string `protobuf:"bytes,3,opt,name=jwtToken,proto3" json:"jwtToken,omitempty"` + // Can be absent for now. + PeerKeys *PeerKeys `protobuf:"bytes,4,opt,name=peerKeys,proto3" json:"peerKeys,omitempty"` } func (x *LoginRequest) Reset() { @@ -374,7 +376,73 @@ func (x *LoginRequest) GetJwtToken() string { return "" } -// Peer machine meta data +func (x *LoginRequest) GetPeerKeys() *PeerKeys { + if x != nil { + return x.PeerKeys + } + return nil +} + +// PeerKeys is additional peer info like SSH pub key and WireGuard public key. +// This message is sent on Login or register requests, or when a key rotation has to happen. +type PeerKeys struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // sshPubKey represents a public SSH key of the peer. Can be absent. + SshPubKey []byte `protobuf:"bytes,1,opt,name=sshPubKey,proto3" json:"sshPubKey,omitempty"` + // wgPubKey represents a public WireGuard key of the peer. Can be absent. + WgPubKey []byte `protobuf:"bytes,2,opt,name=wgPubKey,proto3" json:"wgPubKey,omitempty"` +} + +func (x *PeerKeys) Reset() { + *x = PeerKeys{} + if protoimpl.UnsafeEnabled { + mi := &file_management_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PeerKeys) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PeerKeys) ProtoMessage() {} + +func (x *PeerKeys) ProtoReflect() protoreflect.Message { + mi := &file_management_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 PeerKeys.ProtoReflect.Descriptor instead. +func (*PeerKeys) Descriptor() ([]byte, []int) { + return file_management_proto_rawDescGZIP(), []int{4} +} + +func (x *PeerKeys) GetSshPubKey() []byte { + if x != nil { + return x.SshPubKey + } + return nil +} + +func (x *PeerKeys) GetWgPubKey() []byte { + if x != nil { + return x.WgPubKey + } + return nil +} + +// PeerSystemMeta is machine meta data like OS and version. type PeerSystemMeta struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -393,7 +461,7 @@ type PeerSystemMeta struct { func (x *PeerSystemMeta) Reset() { *x = PeerSystemMeta{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[4] + mi := &file_management_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -406,7 +474,7 @@ func (x *PeerSystemMeta) String() string { func (*PeerSystemMeta) ProtoMessage() {} func (x *PeerSystemMeta) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[4] + mi := &file_management_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -419,7 +487,7 @@ func (x *PeerSystemMeta) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerSystemMeta.ProtoReflect.Descriptor instead. func (*PeerSystemMeta) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{4} + return file_management_proto_rawDescGZIP(), []int{5} } func (x *PeerSystemMeta) GetHostname() string { @@ -492,7 +560,7 @@ type LoginResponse struct { func (x *LoginResponse) Reset() { *x = LoginResponse{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[5] + mi := &file_management_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -505,7 +573,7 @@ func (x *LoginResponse) String() string { func (*LoginResponse) ProtoMessage() {} func (x *LoginResponse) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[5] + mi := &file_management_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -518,7 +586,7 @@ func (x *LoginResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead. func (*LoginResponse) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{5} + return file_management_proto_rawDescGZIP(), []int{6} } func (x *LoginResponse) GetWiretrusteeConfig() *WiretrusteeConfig { @@ -543,7 +611,7 @@ type ServerKeyResponse struct { // Server's Wireguard public key Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // Key expiration timestamp after which the key should be fetched again by the client - ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expiresAt,proto3" json:"expiresAt,omitempty"` + ExpiresAt *timestamp.Timestamp `protobuf:"bytes,2,opt,name=expiresAt,proto3" json:"expiresAt,omitempty"` // Version of the Wiretrustee Management Service protocol Version int32 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"` } @@ -551,7 +619,7 @@ type ServerKeyResponse struct { func (x *ServerKeyResponse) Reset() { *x = ServerKeyResponse{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[6] + mi := &file_management_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -564,7 +632,7 @@ func (x *ServerKeyResponse) String() string { func (*ServerKeyResponse) ProtoMessage() {} func (x *ServerKeyResponse) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[6] + mi := &file_management_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -577,7 +645,7 @@ func (x *ServerKeyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ServerKeyResponse.ProtoReflect.Descriptor instead. func (*ServerKeyResponse) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{6} + return file_management_proto_rawDescGZIP(), []int{7} } func (x *ServerKeyResponse) GetKey() string { @@ -587,7 +655,7 @@ func (x *ServerKeyResponse) GetKey() string { return "" } -func (x *ServerKeyResponse) GetExpiresAt() *timestamppb.Timestamp { +func (x *ServerKeyResponse) GetExpiresAt() *timestamp.Timestamp { if x != nil { return x.ExpiresAt } @@ -610,7 +678,7 @@ type Empty struct { func (x *Empty) Reset() { *x = Empty{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[7] + mi := &file_management_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -623,7 +691,7 @@ func (x *Empty) String() string { func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[7] + mi := &file_management_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -636,7 +704,7 @@ func (x *Empty) ProtoReflect() protoreflect.Message { // Deprecated: Use Empty.ProtoReflect.Descriptor instead. func (*Empty) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{7} + return file_management_proto_rawDescGZIP(), []int{8} } // WiretrusteeConfig is a common configuration of any Wiretrustee peer. It contains STUN, TURN, Signal and Management servers configurations @@ -656,7 +724,7 @@ type WiretrusteeConfig struct { func (x *WiretrusteeConfig) Reset() { *x = WiretrusteeConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[8] + mi := &file_management_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -669,7 +737,7 @@ func (x *WiretrusteeConfig) String() string { func (*WiretrusteeConfig) ProtoMessage() {} func (x *WiretrusteeConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[8] + mi := &file_management_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -682,7 +750,7 @@ func (x *WiretrusteeConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use WiretrusteeConfig.ProtoReflect.Descriptor instead. func (*WiretrusteeConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{8} + return file_management_proto_rawDescGZIP(), []int{9} } func (x *WiretrusteeConfig) GetStuns() []*HostConfig { @@ -720,7 +788,7 @@ type HostConfig struct { func (x *HostConfig) Reset() { *x = HostConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[9] + mi := &file_management_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -733,7 +801,7 @@ func (x *HostConfig) String() string { func (*HostConfig) ProtoMessage() {} func (x *HostConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[9] + mi := &file_management_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -746,7 +814,7 @@ func (x *HostConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use HostConfig.ProtoReflect.Descriptor instead. func (*HostConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{9} + return file_management_proto_rawDescGZIP(), []int{10} } func (x *HostConfig) GetUri() string { @@ -778,7 +846,7 @@ type ProtectedHostConfig struct { func (x *ProtectedHostConfig) Reset() { *x = ProtectedHostConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[10] + mi := &file_management_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -791,7 +859,7 @@ func (x *ProtectedHostConfig) String() string { func (*ProtectedHostConfig) ProtoMessage() {} func (x *ProtectedHostConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[10] + mi := &file_management_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -804,7 +872,7 @@ func (x *ProtectedHostConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtectedHostConfig.ProtoReflect.Descriptor instead. func (*ProtectedHostConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{10} + return file_management_proto_rawDescGZIP(), []int{11} } func (x *ProtectedHostConfig) GetHostConfig() *HostConfig { @@ -839,12 +907,14 @@ type PeerConfig struct { Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` // Wiretrustee DNS server (a Wireguard DNS config) Dns string `protobuf:"bytes,2,opt,name=dns,proto3" json:"dns,omitempty"` + // SSHConfig of the peer. + SshConfig *SSHConfig `protobuf:"bytes,3,opt,name=sshConfig,proto3" json:"sshConfig,omitempty"` } func (x *PeerConfig) Reset() { *x = PeerConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[11] + mi := &file_management_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -857,7 +927,7 @@ func (x *PeerConfig) String() string { func (*PeerConfig) ProtoMessage() {} func (x *PeerConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[11] + mi := &file_management_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -870,7 +940,7 @@ func (x *PeerConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerConfig.ProtoReflect.Descriptor instead. func (*PeerConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{11} + return file_management_proto_rawDescGZIP(), []int{12} } func (x *PeerConfig) GetAddress() string { @@ -887,6 +957,13 @@ func (x *PeerConfig) GetDns() string { return "" } +func (x *PeerConfig) GetSshConfig() *SSHConfig { + if x != nil { + return x.SshConfig + } + return nil +} + // NetworkMap represents a network state of the peer with the corresponding configuration parameters to establish peer-to-peer connections type NetworkMap struct { state protoimpl.MessageState @@ -908,7 +985,7 @@ type NetworkMap struct { func (x *NetworkMap) Reset() { *x = NetworkMap{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[12] + mi := &file_management_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -921,7 +998,7 @@ func (x *NetworkMap) String() string { func (*NetworkMap) ProtoMessage() {} func (x *NetworkMap) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[12] + mi := &file_management_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -934,7 +1011,7 @@ func (x *NetworkMap) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkMap.ProtoReflect.Descriptor instead. func (*NetworkMap) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{12} + return file_management_proto_rawDescGZIP(), []int{13} } func (x *NetworkMap) GetSerial() uint64 { @@ -976,12 +1053,14 @@ type RemotePeerConfig struct { WgPubKey string `protobuf:"bytes,1,opt,name=wgPubKey,proto3" json:"wgPubKey,omitempty"` // Wireguard allowed IPs of a remote peer e.g. [10.30.30.1/32] AllowedIps []string `protobuf:"bytes,2,rep,name=allowedIps,proto3" json:"allowedIps,omitempty"` + // SSHConfig is a SSH config of the remote peer. SSHConfig.sshPubKey should be ignored because peer knows it's SSH key. + SshConfig *SSHConfig `protobuf:"bytes,3,opt,name=sshConfig,proto3" json:"sshConfig,omitempty"` } func (x *RemotePeerConfig) Reset() { *x = RemotePeerConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[13] + mi := &file_management_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -994,7 +1073,7 @@ func (x *RemotePeerConfig) String() string { func (*RemotePeerConfig) ProtoMessage() {} func (x *RemotePeerConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[13] + mi := &file_management_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1007,7 +1086,7 @@ func (x *RemotePeerConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use RemotePeerConfig.ProtoReflect.Descriptor instead. func (*RemotePeerConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{13} + return file_management_proto_rawDescGZIP(), []int{14} } func (x *RemotePeerConfig) GetWgPubKey() string { @@ -1024,6 +1103,72 @@ func (x *RemotePeerConfig) GetAllowedIps() []string { return nil } +func (x *RemotePeerConfig) GetSshConfig() *SSHConfig { + if x != nil { + return x.SshConfig + } + return nil +} + +// SSHConfig represents SSH configurations of a peer. +type SSHConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // sshEnabled indicates whether a SSH server is enabled on this peer + SshEnabled bool `protobuf:"varint,1,opt,name=sshEnabled,proto3" json:"sshEnabled,omitempty"` + // sshPubKey is a SSH public key of a peer to be added to authorized_hosts. + // This property should be ignore if SSHConfig comes from PeerConfig. + SshPubKey []byte `protobuf:"bytes,2,opt,name=sshPubKey,proto3" json:"sshPubKey,omitempty"` +} + +func (x *SSHConfig) Reset() { + *x = SSHConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_management_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SSHConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SSHConfig) ProtoMessage() {} + +func (x *SSHConfig) ProtoReflect() protoreflect.Message { + mi := &file_management_proto_msgTypes[15] + 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 SSHConfig.ProtoReflect.Descriptor instead. +func (*SSHConfig) Descriptor() ([]byte, []int) { + return file_management_proto_rawDescGZIP(), []int{15} +} + +func (x *SSHConfig) GetSshEnabled() bool { + if x != nil { + return x.SshEnabled + } + return false +} + +func (x *SSHConfig) GetSshPubKey() []byte { + if x != nil { + return x.SshPubKey + } + return nil +} + // DeviceAuthorizationFlowRequest empty struct for future expansion type DeviceAuthorizationFlowRequest struct { state protoimpl.MessageState @@ -1034,7 +1179,7 @@ type DeviceAuthorizationFlowRequest struct { func (x *DeviceAuthorizationFlowRequest) Reset() { *x = DeviceAuthorizationFlowRequest{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[14] + mi := &file_management_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1047,7 +1192,7 @@ func (x *DeviceAuthorizationFlowRequest) String() string { func (*DeviceAuthorizationFlowRequest) ProtoMessage() {} func (x *DeviceAuthorizationFlowRequest) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[14] + mi := &file_management_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1060,7 +1205,7 @@ func (x *DeviceAuthorizationFlowRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeviceAuthorizationFlowRequest.ProtoReflect.Descriptor instead. func (*DeviceAuthorizationFlowRequest) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{14} + return file_management_proto_rawDescGZIP(), []int{16} } // DeviceAuthorizationFlow represents Device Authorization Flow information @@ -1079,7 +1224,7 @@ type DeviceAuthorizationFlow struct { func (x *DeviceAuthorizationFlow) Reset() { *x = DeviceAuthorizationFlow{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[15] + mi := &file_management_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1092,7 +1237,7 @@ func (x *DeviceAuthorizationFlow) String() string { func (*DeviceAuthorizationFlow) ProtoMessage() {} func (x *DeviceAuthorizationFlow) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[15] + mi := &file_management_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1105,7 +1250,7 @@ func (x *DeviceAuthorizationFlow) ProtoReflect() protoreflect.Message { // Deprecated: Use DeviceAuthorizationFlow.ProtoReflect.Descriptor instead. func (*DeviceAuthorizationFlow) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{15} + return file_management_proto_rawDescGZIP(), []int{17} } func (x *DeviceAuthorizationFlow) GetProvider() DeviceAuthorizationFlowProvider { @@ -1141,7 +1286,7 @@ type ProviderConfig struct { func (x *ProviderConfig) Reset() { *x = ProviderConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[16] + mi := &file_management_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1154,7 +1299,7 @@ func (x *ProviderConfig) String() string { func (*ProviderConfig) ProtoMessage() {} func (x *ProviderConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[16] + mi := &file_management_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1167,7 +1312,7 @@ func (x *ProviderConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderConfig.ProtoReflect.Descriptor instead. func (*ProviderConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{16} + return file_management_proto_rawDescGZIP(), []int{18} } func (x *ProviderConfig) GetClientID() string { @@ -1231,144 +1376,163 @@ var file_management_proto_rawDesc = []byte{ 0x74, 0x79, 0x12, 0x36, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x52, 0x0a, - 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x22, 0x76, 0x0a, 0x0c, 0x4c, 0x6f, - 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, - 0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, - 0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, - 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x6a, 0x77, 0x74, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6a, 0x77, 0x74, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x22, 0xe6, 0x01, 0x0a, 0x0e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x79, 0x73, 0x74, 0x65, - 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x6f, 0x4f, 0x53, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x67, 0x6f, 0x4f, 0x53, 0x12, 0x16, 0x0a, 0x06, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x12, 0x12, 0x0a, - 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x72, - 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x0e, 0x0a, - 0x02, 0x4f, 0x53, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x4f, 0x53, 0x12, 0x2e, 0x0a, - 0x12, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x77, 0x69, 0x72, 0x65, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, - 0x09, 0x75, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x75, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x94, 0x01, 0x0a, 0x0d, - 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, - 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x57, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, - 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65, + 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x22, 0xa8, 0x01, 0x0a, 0x0c, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, + 0x65, 0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, + 0x65, 0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x6a, 0x77, 0x74, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6a, 0x77, 0x74, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x12, 0x30, 0x0a, 0x08, 0x70, 0x65, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x08, 0x70, 0x65, 0x65, + 0x72, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x44, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4b, 0x65, 0x79, + 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, + 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0xe6, 0x01, 0x0a, 0x0e, + 0x50, 0x65, 0x65, 0x72, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1a, + 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x6f, + 0x4f, 0x53, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x6f, 0x4f, 0x53, 0x12, 0x16, + 0x0a, 0x06, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, + 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, + 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x4f, 0x53, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x4f, 0x53, 0x12, 0x2e, 0x0a, 0x12, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, + 0x75, 0x73, 0x74, 0x65, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x12, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x69, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x69, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x94, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, + 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x57, + 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x79, 0x0a, 0x11, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x38, 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, + 0xa8, 0x01, 0x0a, 0x11, 0x57, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x0a, 0x05, 0x73, 0x74, 0x75, 0x6e, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x73, 0x74, + 0x75, 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x05, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, + 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x05, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x22, 0x98, 0x01, 0x0a, 0x0a, 0x48, + 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x3b, 0x0a, 0x08, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, 0x3b, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, + 0x03, 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x02, + 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x44, + 0x54, 0x4c, 0x53, 0x10, 0x04, 0x22, 0x7d, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, + 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0a, + 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, + 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x22, 0x6d, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, + 0x64, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x6e, 0x73, 0x12, 0x33, + 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, + 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x22, 0xcc, 0x01, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, + 0x61, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x22, 0x79, 0x0a, 0x11, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x38, 0x0a, 0x09, 0x65, 0x78, 0x70, - 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, - 0x73, 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x07, 0x0a, - 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xa8, 0x01, 0x0a, 0x11, 0x57, 0x69, 0x72, 0x65, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x0a, 0x05, - 0x73, 0x74, 0x75, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x05, 0x73, 0x74, 0x75, 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x05, 0x74, 0x75, - 0x72, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, - 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x74, 0x75, 0x72, 0x6e, - 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, - 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, - 0x6c, 0x22, 0x98, 0x01, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, - 0x72, 0x69, 0x12, 0x3b, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, - 0x3b, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03, 0x55, - 0x44, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, - 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, - 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x54, 0x4c, 0x53, 0x10, 0x04, 0x22, 0x7d, 0x0a, 0x13, - 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x75, - 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, - 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x38, 0x0a, 0x0a, 0x50, - 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x64, 0x6e, 0x73, 0x22, 0xcc, 0x01, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, - 0x6b, 0x4d, 0x61, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x0a, - 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, - 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3e, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, - 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, - 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, - 0x65, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, - 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x22, 0x4e, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, - 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, - 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, - 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, - 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, - 0x64, 0x49, 0x70, 0x73, 0x22, 0x20, 0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, - 0x6f, 0x77, 0x12, 0x48, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x22, 0x16, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a, 0x0a, 0x06, - 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0x84, 0x01, 0x0a, 0x0e, 0x50, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x32, - 0xf7, 0x02, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, - 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, - 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, - 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, - 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, + 0x69, 0x67, 0x12, 0x3e, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, + 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, + 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, + 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x22, 0x83, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, + 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, + 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, + 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, + 0x49, 0x70, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, + 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x49, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, + 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, + 0x4b, 0x65, 0x79, 0x22, 0x20, 0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, + 0x77, 0x12, 0x48, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e, 0x50, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, + 0x16, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a, 0x0a, 0x06, 0x48, + 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0x84, 0x01, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x32, 0xf7, + 0x02, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, - 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, + 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, + 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, + 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, + 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1384,7 +1548,7 @@ func file_management_proto_rawDescGZIP() []byte { } var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 19) var file_management_proto_goTypes = []interface{}{ (HostConfig_Protocol)(0), // 0: management.HostConfig.Protocol (DeviceAuthorizationFlowProvider)(0), // 1: management.DeviceAuthorizationFlow.provider @@ -1392,54 +1556,59 @@ var file_management_proto_goTypes = []interface{}{ (*SyncRequest)(nil), // 3: management.SyncRequest (*SyncResponse)(nil), // 4: management.SyncResponse (*LoginRequest)(nil), // 5: management.LoginRequest - (*PeerSystemMeta)(nil), // 6: management.PeerSystemMeta - (*LoginResponse)(nil), // 7: management.LoginResponse - (*ServerKeyResponse)(nil), // 8: management.ServerKeyResponse - (*Empty)(nil), // 9: management.Empty - (*WiretrusteeConfig)(nil), // 10: management.WiretrusteeConfig - (*HostConfig)(nil), // 11: management.HostConfig - (*ProtectedHostConfig)(nil), // 12: management.ProtectedHostConfig - (*PeerConfig)(nil), // 13: management.PeerConfig - (*NetworkMap)(nil), // 14: management.NetworkMap - (*RemotePeerConfig)(nil), // 15: management.RemotePeerConfig - (*DeviceAuthorizationFlowRequest)(nil), // 16: management.DeviceAuthorizationFlowRequest - (*DeviceAuthorizationFlow)(nil), // 17: management.DeviceAuthorizationFlow - (*ProviderConfig)(nil), // 18: management.ProviderConfig - (*timestamppb.Timestamp)(nil), // 19: google.protobuf.Timestamp + (*PeerKeys)(nil), // 6: management.PeerKeys + (*PeerSystemMeta)(nil), // 7: management.PeerSystemMeta + (*LoginResponse)(nil), // 8: management.LoginResponse + (*ServerKeyResponse)(nil), // 9: management.ServerKeyResponse + (*Empty)(nil), // 10: management.Empty + (*WiretrusteeConfig)(nil), // 11: management.WiretrusteeConfig + (*HostConfig)(nil), // 12: management.HostConfig + (*ProtectedHostConfig)(nil), // 13: management.ProtectedHostConfig + (*PeerConfig)(nil), // 14: management.PeerConfig + (*NetworkMap)(nil), // 15: management.NetworkMap + (*RemotePeerConfig)(nil), // 16: management.RemotePeerConfig + (*SSHConfig)(nil), // 17: management.SSHConfig + (*DeviceAuthorizationFlowRequest)(nil), // 18: management.DeviceAuthorizationFlowRequest + (*DeviceAuthorizationFlow)(nil), // 19: management.DeviceAuthorizationFlow + (*ProviderConfig)(nil), // 20: management.ProviderConfig + (*timestamp.Timestamp)(nil), // 21: google.protobuf.Timestamp } var file_management_proto_depIdxs = []int32{ - 10, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig - 13, // 1: management.SyncResponse.peerConfig:type_name -> management.PeerConfig - 15, // 2: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig - 14, // 3: management.SyncResponse.NetworkMap:type_name -> management.NetworkMap - 6, // 4: management.LoginRequest.meta:type_name -> management.PeerSystemMeta - 10, // 5: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig - 13, // 6: management.LoginResponse.peerConfig:type_name -> management.PeerConfig - 19, // 7: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp - 11, // 8: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig - 12, // 9: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig - 11, // 10: management.WiretrusteeConfig.signal:type_name -> management.HostConfig - 0, // 11: management.HostConfig.protocol:type_name -> management.HostConfig.Protocol - 11, // 12: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig - 13, // 13: management.NetworkMap.peerConfig:type_name -> management.PeerConfig - 15, // 14: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig - 1, // 15: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider - 18, // 16: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig - 2, // 17: management.ManagementService.Login:input_type -> management.EncryptedMessage - 2, // 18: management.ManagementService.Sync:input_type -> management.EncryptedMessage - 9, // 19: management.ManagementService.GetServerKey:input_type -> management.Empty - 9, // 20: management.ManagementService.isHealthy:input_type -> management.Empty - 2, // 21: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage - 2, // 22: management.ManagementService.Login:output_type -> management.EncryptedMessage - 2, // 23: management.ManagementService.Sync:output_type -> management.EncryptedMessage - 8, // 24: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse - 9, // 25: management.ManagementService.isHealthy:output_type -> management.Empty - 2, // 26: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage - 22, // [22:27] is the sub-list for method output_type - 17, // [17:22] is the sub-list for method input_type - 17, // [17:17] is the sub-list for extension type_name - 17, // [17:17] is the sub-list for extension extendee - 0, // [0:17] is the sub-list for field type_name + 11, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig + 14, // 1: management.SyncResponse.peerConfig:type_name -> management.PeerConfig + 16, // 2: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig + 15, // 3: management.SyncResponse.NetworkMap:type_name -> management.NetworkMap + 7, // 4: management.LoginRequest.meta:type_name -> management.PeerSystemMeta + 6, // 5: management.LoginRequest.peerKeys:type_name -> management.PeerKeys + 11, // 6: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig + 14, // 7: management.LoginResponse.peerConfig:type_name -> management.PeerConfig + 21, // 8: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp + 12, // 9: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig + 13, // 10: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig + 12, // 11: management.WiretrusteeConfig.signal:type_name -> management.HostConfig + 0, // 12: management.HostConfig.protocol:type_name -> management.HostConfig.Protocol + 12, // 13: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig + 17, // 14: management.PeerConfig.sshConfig:type_name -> management.SSHConfig + 14, // 15: management.NetworkMap.peerConfig:type_name -> management.PeerConfig + 16, // 16: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig + 17, // 17: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig + 1, // 18: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider + 20, // 19: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig + 2, // 20: management.ManagementService.Login:input_type -> management.EncryptedMessage + 2, // 21: management.ManagementService.Sync:input_type -> management.EncryptedMessage + 10, // 22: management.ManagementService.GetServerKey:input_type -> management.Empty + 10, // 23: management.ManagementService.isHealthy:input_type -> management.Empty + 2, // 24: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage + 2, // 25: management.ManagementService.Login:output_type -> management.EncryptedMessage + 2, // 26: management.ManagementService.Sync:output_type -> management.EncryptedMessage + 9, // 27: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse + 10, // 28: management.ManagementService.isHealthy:output_type -> management.Empty + 2, // 29: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage + 25, // [25:30] is the sub-list for method output_type + 20, // [20:25] is the sub-list for method input_type + 20, // [20:20] is the sub-list for extension type_name + 20, // [20:20] is the sub-list for extension extendee + 0, // [0:20] is the sub-list for field type_name } func init() { file_management_proto_init() } @@ -1497,7 +1666,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeerSystemMeta); i { + switch v := v.(*PeerKeys); i { case 0: return &v.state case 1: @@ -1509,7 +1678,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginResponse); i { + switch v := v.(*PeerSystemMeta); i { case 0: return &v.state case 1: @@ -1521,7 +1690,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ServerKeyResponse); i { + switch v := v.(*LoginResponse); i { case 0: return &v.state case 1: @@ -1533,7 +1702,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Empty); i { + switch v := v.(*ServerKeyResponse); i { case 0: return &v.state case 1: @@ -1545,7 +1714,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WiretrusteeConfig); i { + switch v := v.(*Empty); i { case 0: return &v.state case 1: @@ -1557,7 +1726,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HostConfig); i { + switch v := v.(*WiretrusteeConfig); i { case 0: return &v.state case 1: @@ -1569,7 +1738,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ProtectedHostConfig); i { + switch v := v.(*HostConfig); i { case 0: return &v.state case 1: @@ -1581,7 +1750,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeerConfig); i { + switch v := v.(*ProtectedHostConfig); i { case 0: return &v.state case 1: @@ -1593,7 +1762,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NetworkMap); i { + switch v := v.(*PeerConfig); i { case 0: return &v.state case 1: @@ -1605,7 +1774,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RemotePeerConfig); i { + switch v := v.(*NetworkMap); i { case 0: return &v.state case 1: @@ -1617,7 +1786,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeviceAuthorizationFlowRequest); i { + switch v := v.(*RemotePeerConfig); i { case 0: return &v.state case 1: @@ -1629,7 +1798,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeviceAuthorizationFlow); i { + switch v := v.(*SSHConfig); i { case 0: return &v.state case 1: @@ -1641,6 +1810,30 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeviceAuthorizationFlowRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_management_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeviceAuthorizationFlow); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_management_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ProviderConfig); i { case 0: return &v.state @@ -1659,7 +1852,7 @@ func file_management_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_management_proto_rawDesc, NumEnums: 2, - NumMessages: 17, + NumMessages: 19, NumExtensions: 0, NumServices: 1, }, diff --git a/management/proto/management.proto b/management/proto/management.proto index afdd09722..b9522878a 100644 --- a/management/proto/management.proto +++ b/management/proto/management.proto @@ -71,9 +71,21 @@ message LoginRequest { PeerSystemMeta meta = 2; // SSO token (can be empty) string jwtToken = 3; + // Can be absent for now. + PeerKeys peerKeys = 4; + +} +// PeerKeys is additional peer info like SSH pub key and WireGuard public key. +// This message is sent on Login or register requests, or when a key rotation has to happen. +message PeerKeys { + + // sshPubKey represents a public SSH key of the peer. Can be absent. + bytes sshPubKey = 1; + // wgPubKey represents a public WireGuard key of the peer. Can be absent. + bytes wgPubKey = 2; } -// Peer machine meta data +// PeerSystemMeta is machine meta data like OS and version. message PeerSystemMeta { string hostname = 1; string goOS = 2; @@ -143,6 +155,9 @@ message PeerConfig { string address = 1; // Wiretrustee DNS server (a Wireguard DNS config) string dns = 2; + + // SSHConfig of the peer. + SSHConfig sshConfig = 3; } // NetworkMap represents a network state of the peer with the corresponding configuration parameters to establish peer-to-peer connections @@ -172,7 +187,22 @@ message RemotePeerConfig { // Wireguard allowed IPs of a remote peer e.g. [10.30.30.1/32] repeated string allowedIps = 2; + + // SSHConfig is a SSH config of the remote peer. SSHConfig.sshPubKey should be ignored because peer knows it's SSH key. + SSHConfig sshConfig = 3; + } + +// SSHConfig represents SSH configurations of a peer. +message SSHConfig { + // sshEnabled indicates whether a SSH server is enabled on this peer + bool sshEnabled = 1; + + // sshPubKey is a SSH public key of a peer to be added to authorized_hosts. + // This property should be ignore if SSHConfig comes from PeerConfig. + bytes sshPubKey = 2; +} + // DeviceAuthorizationFlowRequest empty struct for future expansion message DeviceAuthorizationFlowRequest {} // DeviceAuthorizationFlow represents Device Authorization Flow information diff --git a/management/server/account.go b/management/server/account.go index 8d6e50b55..520858a16 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -51,6 +51,7 @@ type AccountManager interface { GetNetworkMap(peerKey string) (*NetworkMap, error) AddPeer(setupKey string, userId string, peer *Peer) (*Peer, error) UpdatePeerMeta(peerKey string, meta PeerSystemMeta) error + UpdatePeerSSHKey(peerKey string, sshKey string) error GetUsersFromAccount(accountId string) ([]*UserInfo, error) GetGroup(accountId, groupID string) (*Group, error) SaveGroup(accountId string, group *Group) error @@ -65,6 +66,7 @@ type AccountManager interface { UpdateRule(accountID string, ruleID string, operations []RuleUpdateOperation) (*Rule, error) DeleteRule(accountId, ruleID string) error ListRules(accountId string) ([]*Rule, error) + UpdatePeer(accountID string, peer *Peer) (*Peer, error) } type DefaultAccountManager struct { diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index 9f642c3f5..0fa8a4026 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -185,9 +185,15 @@ func (s *Server) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Pe return nil, status.Errorf(codes.InvalidArgument, "peer meta data was not provided") } + var sshKey []byte + if req.GetPeerKeys() != nil { + sshKey = req.GetPeerKeys().GetSshPubKey() + } + peer, err := s.accountManager.AddPeer(reqSetupKey, userId, &Peer{ - Key: peerKey.String(), - Name: meta.GetHostname(), + Key: peerKey.String(), + Name: meta.GetHostname(), + SSHKey: string(sshKey), Meta: PeerSystemMeta{ Hostname: meta.GetHostname(), GoOS: meta.GetGoOS(), @@ -290,6 +296,19 @@ func (s *Server) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto return nil, status.Error(codes.Internal, "internal server error") } } + + var sshKey []byte + if loginReq.GetPeerKeys() != nil { + sshKey = loginReq.GetPeerKeys().GetSshPubKey() + } + + if len(sshKey) > 0 { + err = s.accountManager.UpdatePeerSSHKey(peerKey.String(), string(sshKey)) + if err != nil { + return nil, err + } + } + // if peer has reached this point then it has logged in loginResp := &proto.LoginResponse{ WiretrusteeConfig: toWiretrusteeConfig(s.config, nil), @@ -365,7 +384,8 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot func toPeerConfig(peer *Peer) *proto.PeerConfig { return &proto.PeerConfig{ - Address: fmt.Sprintf("%s/%d", peer.IP.String(), SubnetSize), // take it from the network + Address: fmt.Sprintf("%s/%d", peer.IP.String(), SubnetSize), // take it from the network + SshConfig: &proto.SSHConfig{SshEnabled: peer.SSHEnabled}, } } @@ -375,9 +395,9 @@ func toRemotePeerConfig(peers []*Peer) []*proto.RemotePeerConfig { remotePeers = append(remotePeers, &proto.RemotePeerConfig{ WgPubKey: rPeer.Key, AllowedIps: []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)}, + SshConfig: &proto.SSHConfig{SshPubKey: []byte(rPeer.SSHKey)}, }) } - return remotePeers } diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index f8af27105..d5bf580e5 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -85,6 +85,9 @@ components: required: - type - value + ssh_enabled: + description: Indicates whether SSH server is enabled on this peer + type: boolean required: - ip - connected @@ -93,6 +96,7 @@ components: - version - groups - activated_by + - ssh_enabled SetupKey: type: object properties: @@ -397,8 +401,11 @@ paths: properties: name: type: string + ssh_enabled: + type: boolean required: - name + - ssh_enabled responses: '200': description: A Peer object diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index a58b33522..8c37a484b 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -115,6 +115,9 @@ type Peer struct { // Peer's operating system and version Os string `json:"os"` + // Indicates whether SSH server is enabled on this peer + SshEnabled bool `json:"ssh_enabled"` + // Peer's daemon or cli version Version string `json:"version"` } @@ -265,7 +268,8 @@ type PutApiGroupsIdJSONBody struct { // PutApiPeersIdJSONBody defines parameters for PutApiPeersId. type PutApiPeersIdJSONBody struct { - Name string `json:"name"` + Name string `json:"name"` + SshEnabled bool `json:"ssh_enabled"` } // PostApiRulesJSONBody defines parameters for PostApiRules. diff --git a/management/server/http/handler/peers.go b/management/server/http/handler/peers.go index 2765372a4..dd7592d2f 100644 --- a/management/server/http/handler/peers.go +++ b/management/server/http/handler/peers.go @@ -34,7 +34,9 @@ func (h *Peers) updatePeer(account *server.Account, peer *server.Peer, w http.Re http.Error(w, err.Error(), http.StatusBadRequest) return } - peer, err = h.accountManager.RenamePeer(account.Id, peer.Key, req.Name) + + update := &server.Peer{Key: peer.Key, SSHEnabled: req.SshEnabled, Name: req.Name} + peer, err = h.accountManager.UpdatePeer(account.Id, update) if err != nil { log.Errorf("failed updating peer %s under account %s %v", peerIp, account.Id, err) http.Redirect(w, r, "/", http.StatusInternalServerError) @@ -133,13 +135,14 @@ func toPeerResponse(peer *server.Peer, account *server.Account) *api.Peer { } } return &api.Peer{ - Id: peer.IP.String(), - Name: peer.Name, - Ip: peer.IP.String(), - Connected: peer.Status.Connected, - LastSeen: peer.Status.LastSeen, - Os: fmt.Sprintf("%s %s", peer.Meta.OS, peer.Meta.Core), - Version: peer.Meta.WtVersion, - Groups: groupsInfo, + Id: peer.IP.String(), + Name: peer.Name, + Ip: peer.IP.String(), + Connected: peer.Status.Connected, + LastSeen: peer.Status.LastSeen, + Os: fmt.Sprintf("%s %s", peer.Meta.OS, peer.Meta.Core), + Version: peer.Meta.WtVersion, + Groups: groupsInfo, + SshEnabled: peer.SSHEnabled, } } diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index c49ee5bbe..5e85bf2e6 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -41,6 +41,8 @@ type MockAccountManager struct { ListRulesFunc func(accountID string) ([]*server.Rule, error) GetUsersFromAccountFunc func(accountID string) ([]*server.UserInfo, error) UpdatePeerMetaFunc func(peerKey string, meta server.PeerSystemMeta) error + UpdatePeerSSHKeyFunc func(peerKey string, sshKey string) error + UpdatePeerFunc func(accountID string, peer *server.Peer) (*server.Peer, error) } // GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface @@ -48,7 +50,7 @@ func (am *MockAccountManager) GetUsersFromAccount(accountID string) ([]*server.U if am.GetUsersFromAccountFunc != nil { return am.GetUsersFromAccountFunc(accountID) } - return nil, status.Errorf(codes.Unimplemented, "method GetUsersFromAccount not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetUsersFromAccount is not implemented") } // GetOrCreateAccountByUser mock implementation of GetOrCreateAccountByUser from server.AccountManager interface @@ -60,7 +62,7 @@ func (am *MockAccountManager) GetOrCreateAccountByUser( } return nil, status.Errorf( codes.Unimplemented, - "method GetOrCreateAccountByUser not implemented", + "method GetOrCreateAccountByUser is not implemented", ) } @@ -69,7 +71,7 @@ func (am *MockAccountManager) GetAccountByUser(userId string) (*server.Account, if am.GetAccountByUserFunc != nil { return am.GetAccountByUserFunc(userId) } - return nil, status.Errorf(codes.Unimplemented, "method GetAccountByUser not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetAccountByUser is not implemented") } // AddSetupKey mock implementation of AddSetupKey from server.AccountManager interface @@ -82,7 +84,7 @@ func (am *MockAccountManager) AddSetupKey( if am.AddSetupKeyFunc != nil { return am.AddSetupKeyFunc(accountId, keyName, keyType, expiresIn) } - return nil, status.Errorf(codes.Unimplemented, "method AddSetupKey not implemented") + return nil, status.Errorf(codes.Unimplemented, "method AddSetupKey is not implemented") } // RevokeSetupKey mock implementation of RevokeSetupKey from server.AccountManager interface @@ -93,7 +95,7 @@ func (am *MockAccountManager) RevokeSetupKey( if am.RevokeSetupKeyFunc != nil { return am.RevokeSetupKeyFunc(accountId, keyId) } - return nil, status.Errorf(codes.Unimplemented, "method RevokeSetupKey not implemented") + return nil, status.Errorf(codes.Unimplemented, "method RevokeSetupKey is not implemented") } // RenameSetupKey mock implementation of RenameSetupKey from server.AccountManager interface @@ -105,7 +107,7 @@ func (am *MockAccountManager) RenameSetupKey( if am.RenameSetupKeyFunc != nil { return am.RenameSetupKeyFunc(accountId, keyId, newName) } - return nil, status.Errorf(codes.Unimplemented, "method RenameSetupKey not implemented") + return nil, status.Errorf(codes.Unimplemented, "method RenameSetupKey is not implemented") } // GetAccountById mock implementation of GetAccountById from server.AccountManager interface @@ -113,7 +115,7 @@ func (am *MockAccountManager) GetAccountById(accountId string) (*server.Account, if am.GetAccountByIdFunc != nil { return am.GetAccountByIdFunc(accountId) } - return nil, status.Errorf(codes.Unimplemented, "method GetAccountById not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetAccountById is not implemented") } // GetAccountByUserOrAccountId mock implementation of GetAccountByUserOrAccountId from server.AccountManager interface @@ -125,7 +127,7 @@ func (am *MockAccountManager) GetAccountByUserOrAccountId( } return nil, status.Errorf( codes.Unimplemented, - "method GetAccountByUserOrAccountId not implemented", + "method GetAccountByUserOrAccountId is not implemented", ) } @@ -138,7 +140,7 @@ func (am *MockAccountManager) GetAccountWithAuthorizationClaims( } return nil, status.Errorf( codes.Unimplemented, - "method GetAccountWithAuthorizationClaims not implemented", + "method GetAccountWithAuthorizationClaims is not implemented", ) } @@ -147,7 +149,7 @@ 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") + return nil, status.Errorf(codes.Unimplemented, "method AccountExists is not implemented") } // GetPeer mock implementation of GetPeer from server.AccountManager interface @@ -155,7 +157,7 @@ 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") + return nil, status.Errorf(codes.Unimplemented, "method GetPeer is not implemented") } // MarkPeerConnected mock implementation of MarkPeerConnected from server.AccountManager interface @@ -163,7 +165,7 @@ func (am *MockAccountManager) MarkPeerConnected(peerKey string, connected bool) if am.MarkPeerConnectedFunc != nil { return am.MarkPeerConnectedFunc(peerKey, connected) } - return status.Errorf(codes.Unimplemented, "method MarkPeerConnected not implemented") + return status.Errorf(codes.Unimplemented, "method MarkPeerConnected is not implemented") } // RenamePeer mock implementation of RenamePeer from server.AccountManager interface @@ -175,7 +177,7 @@ func (am *MockAccountManager) RenamePeer( if am.RenamePeerFunc != nil { return am.RenamePeerFunc(accountId, peerKey, newName) } - return nil, status.Errorf(codes.Unimplemented, "method RenamePeer not implemented") + return nil, status.Errorf(codes.Unimplemented, "method RenamePeer is not implemented") } // DeletePeer mock implementation of DeletePeer from server.AccountManager interface @@ -183,7 +185,7 @@ func (am *MockAccountManager) DeletePeer(accountId string, peerKey string) (*ser if am.DeletePeerFunc != nil { return am.DeletePeerFunc(accountId, peerKey) } - return nil, status.Errorf(codes.Unimplemented, "method DeletePeer not implemented") + return nil, status.Errorf(codes.Unimplemented, "method DeletePeer is not implemented") } // GetPeerByIP mock implementation of GetPeerByIP from server.AccountManager interface @@ -191,7 +193,7 @@ func (am *MockAccountManager) GetPeerByIP(accountId string, peerIP string) (*ser if am.GetPeerByIPFunc != nil { return am.GetPeerByIPFunc(accountId, peerIP) } - return nil, status.Errorf(codes.Unimplemented, "method GetPeerByIP not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetPeerByIP is not implemented") } // GetNetworkMap mock implementation of GetNetworkMap from server.AccountManager interface @@ -199,7 +201,7 @@ func (am *MockAccountManager) GetNetworkMap(peerKey string) (*server.NetworkMap, if am.GetNetworkMapFunc != nil { return am.GetNetworkMapFunc(peerKey) } - return nil, status.Errorf(codes.Unimplemented, "method GetNetworkMap not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetNetworkMap is not implemented") } // AddPeer mock implementation of AddPeer from server.AccountManager interface @@ -211,7 +213,7 @@ func (am *MockAccountManager) AddPeer( if am.AddPeerFunc != nil { return am.AddPeerFunc(setupKey, userId, peer) } - return nil, status.Errorf(codes.Unimplemented, "method AddPeer not implemented") + return nil, status.Errorf(codes.Unimplemented, "method AddPeer is not implemented") } // GetGroup mock implementation of GetGroup from server.AccountManager interface @@ -219,7 +221,7 @@ func (am *MockAccountManager) GetGroup(accountID, groupID string) (*server.Group if am.GetGroupFunc != nil { return am.GetGroupFunc(accountID, groupID) } - return nil, status.Errorf(codes.Unimplemented, "method GetGroup not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetGroup is not implemented") } // SaveGroup mock implementation of SaveGroup from server.AccountManager interface @@ -227,7 +229,7 @@ func (am *MockAccountManager) SaveGroup(accountID string, group *server.Group) e if am.SaveGroupFunc != nil { return am.SaveGroupFunc(accountID, group) } - return status.Errorf(codes.Unimplemented, "method SaveGroup not implemented") + return status.Errorf(codes.Unimplemented, "method SaveGroup is not implemented") } // UpdateGroup mock implementation of UpdateGroup from server.AccountManager interface @@ -243,7 +245,7 @@ func (am *MockAccountManager) DeleteGroup(accountID, groupID string) error { if am.DeleteGroupFunc != nil { return am.DeleteGroupFunc(accountID, groupID) } - return status.Errorf(codes.Unimplemented, "method DeleteGroup not implemented") + return status.Errorf(codes.Unimplemented, "method DeleteGroup is not implemented") } // ListGroups mock implementation of ListGroups from server.AccountManager interface @@ -251,7 +253,7 @@ func (am *MockAccountManager) ListGroups(accountID string) ([]*server.Group, err if am.ListGroupsFunc != nil { return am.ListGroupsFunc(accountID) } - return nil, status.Errorf(codes.Unimplemented, "method ListGroups not implemented") + return nil, status.Errorf(codes.Unimplemented, "method ListGroups is not implemented") } // GroupAddPeer mock implementation of GroupAddPeer from server.AccountManager interface @@ -259,7 +261,7 @@ func (am *MockAccountManager) GroupAddPeer(accountID, groupID, peerKey string) e if am.GroupAddPeerFunc != nil { return am.GroupAddPeerFunc(accountID, groupID, peerKey) } - return status.Errorf(codes.Unimplemented, "method GroupAddPeer not implemented") + return status.Errorf(codes.Unimplemented, "method GroupAddPeer is not implemented") } // GroupDeletePeer mock implementation of GroupDeletePeer from server.AccountManager interface @@ -267,7 +269,7 @@ func (am *MockAccountManager) GroupDeletePeer(accountID, groupID, peerKey string if am.GroupDeletePeerFunc != nil { return am.GroupDeletePeerFunc(accountID, groupID, peerKey) } - return status.Errorf(codes.Unimplemented, "method GroupDeletePeer not implemented") + return status.Errorf(codes.Unimplemented, "method GroupDeletePeer is not implemented") } // GroupListPeers mock implementation of GroupListPeers from server.AccountManager interface @@ -275,7 +277,7 @@ func (am *MockAccountManager) GroupListPeers(accountID, groupID string) ([]*serv if am.GroupListPeersFunc != nil { return am.GroupListPeersFunc(accountID, groupID) } - return nil, status.Errorf(codes.Unimplemented, "method GroupListPeers not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GroupListPeers is not implemented") } // GetRule mock implementation of GetRule from server.AccountManager interface @@ -283,7 +285,7 @@ func (am *MockAccountManager) GetRule(accountID, ruleID string) (*server.Rule, e if am.GetRuleFunc != nil { return am.GetRuleFunc(accountID, ruleID) } - return nil, status.Errorf(codes.Unimplemented, "method GetRule not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetRule is not implemented") } // SaveRule mock implementation of SaveRule from server.AccountManager interface @@ -291,7 +293,7 @@ func (am *MockAccountManager) SaveRule(accountID string, rule *server.Rule) erro if am.SaveRuleFunc != nil { return am.SaveRuleFunc(accountID, rule) } - return status.Errorf(codes.Unimplemented, "method SaveRule not implemented") + return status.Errorf(codes.Unimplemented, "method SaveRule is not implemented") } // UpdateRule mock implementation of UpdateRule from server.AccountManager interface @@ -307,7 +309,7 @@ func (am *MockAccountManager) DeleteRule(accountID, ruleID string) error { if am.DeleteRuleFunc != nil { return am.DeleteRuleFunc(accountID, ruleID) } - return status.Errorf(codes.Unimplemented, "method DeleteRule not implemented") + return status.Errorf(codes.Unimplemented, "method DeleteRule is not implemented") } // ListRules mock implementation of ListRules from server.AccountManager interface @@ -315,7 +317,7 @@ func (am *MockAccountManager) ListRules(accountID string) ([]*server.Rule, error if am.ListRulesFunc != nil { return am.ListRulesFunc(accountID) } - return nil, status.Errorf(codes.Unimplemented, "method ListRules not implemented") + return nil, status.Errorf(codes.Unimplemented, "method ListRules is not implemented") } // UpdatePeerMeta mock implementation of UpdatePeerMeta from server.AccountManager interface @@ -323,7 +325,7 @@ func (am *MockAccountManager) UpdatePeerMeta(peerKey string, meta server.PeerSys if am.UpdatePeerMetaFunc != nil { return am.UpdatePeerMetaFunc(peerKey, meta) } - return status.Errorf(codes.Unimplemented, "method UpdatePeerMetaFunc not implemented") + return status.Errorf(codes.Unimplemented, "method UpdatePeerMetaFunc is not implemented") } // IsUserAdmin mock implementation of IsUserAdmin from server.AccountManager interface @@ -331,5 +333,21 @@ func (am *MockAccountManager) IsUserAdmin(claims jwtclaims.AuthorizationClaims) if am.IsUserAdminFunc != nil { return am.IsUserAdminFunc(claims) } - return false, status.Errorf(codes.Unimplemented, "method IsUserAdmin not implemented") + return false, status.Errorf(codes.Unimplemented, "method IsUserAdmin is not implemented") +} + +// UpdatePeerSSHKey mocks UpdatePeerSSHKey function of the account manager +func (am *MockAccountManager) UpdatePeerSSHKey(peerKey string, sshKey string) error { + if am.UpdatePeerSSHKeyFunc != nil { + return am.UpdatePeerSSHKeyFunc(peerKey, sshKey) + } + return status.Errorf(codes.Unimplemented, "method UpdatePeerSSHKey is is not implemented") +} + +// UpdatePeer mocks UpdatePeerFunc function of the account manager +func (am *MockAccountManager) UpdatePeer(accountID string, peer *server.Peer) (*server.Peer, error) { + if am.UpdatePeerFunc != nil { + return am.UpdatePeerFunc(accountID, peer) + } + return nil, status.Errorf(codes.Unimplemented, "method UpdatePeerFunc is is not implemented") } diff --git a/management/server/peer.go b/management/server/peer.go index 735baced7..9fafb346c 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -47,18 +47,24 @@ type Peer struct { Status *PeerStatus // The user ID that registered the peer UserID string + // SSHKey is a public SSH key of the peer + SSHKey string + // SSHEnabled indicated whether SSH server is enabled on the peer + SSHEnabled bool } // Copy copies Peer object func (p *Peer) Copy() *Peer { return &Peer{ - Key: p.Key, - SetupKey: p.SetupKey, - IP: p.IP, - Meta: p.Meta, - Name: p.Name, - Status: p.Status, - UserID: p.UserID, + Key: p.Key, + SetupKey: p.SetupKey, + IP: p.IP, + Meta: p.Meta, + Name: p.Name, + Status: p.Status, + UserID: p.UserID, + SSHKey: p.SSHKey, + SSHEnabled: p.SSHEnabled, } } @@ -100,6 +106,41 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerKey string, connected boo return nil } +// UpdatePeer updates peer. Only Peer.Name and Peer.SSHEnabled can be updated. +func (am *DefaultAccountManager) UpdatePeer(accountID string, update *Peer) (*Peer, error) { + am.mux.Lock() + defer am.mux.Unlock() + + account, err := am.Store.GetAccount(accountID) + if err != nil { + return nil, status.Errorf(codes.NotFound, "account not found") + } + + peer, err := am.Store.GetPeer(update.Key) + if err != nil { + return nil, err + } + + peerCopy := peer.Copy() + if peer.Name != "" { + peerCopy.Name = update.Name + } + peerCopy.SSHEnabled = update.SSHEnabled + + err = am.Store.SavePeer(accountID, peerCopy) + if err != nil { + return nil, err + } + + err = am.updateAccountPeers(account) + if err != nil { + return nil, err + } + + return peerCopy, nil + +} + // RenamePeer changes peer's name func (am *DefaultAccountManager) RenamePeer( accountId string, @@ -285,13 +326,15 @@ func (am *DefaultAccountManager) AddPeer( } newPeer := &Peer{ - Key: peer.Key, - SetupKey: upperKey, - IP: nextIp, - Meta: peer.Meta, - Name: peer.Name, - UserID: userID, - Status: &PeerStatus{Connected: false, LastSeen: time.Now()}, + Key: peer.Key, + SetupKey: upperKey, + IP: nextIp, + Meta: peer.Meta, + Name: peer.Name, + UserID: userID, + Status: &PeerStatus{Connected: false, LastSeen: time.Now()}, + SSHEnabled: false, + SSHKey: peer.SSHKey, } // add peer to 'All' group @@ -315,6 +358,38 @@ func (am *DefaultAccountManager) AddPeer( return newPeer, nil } +// UpdatePeerSSHKey updates peer's public SSH key +func (am *DefaultAccountManager) UpdatePeerSSHKey(peerKey string, sshKey string) error { + am.mux.Lock() + defer am.mux.Unlock() + + if sshKey == "" { + log.Debugf("empty SSH key provided for peer %s, skipping update", peerKey) + return nil + } + + peer, err := am.Store.GetPeer(peerKey) + if err != nil { + return err + } + + account, err := am.Store.GetPeerAccount(peerKey) + if err != nil { + return err + } + + peerCopy := peer.Copy() + peerCopy.SSHKey = sshKey + + err = am.Store.SavePeer(account.Id, peerCopy) + if err != nil { + return err + } + + // trigger network map update + return am.updateAccountPeers(account) +} + // UpdatePeerMeta updates peer's system metadata func (am *DefaultAccountManager) UpdatePeerMeta(peerKey string, meta PeerSystemMeta) error { am.mux.Lock() @@ -345,7 +420,7 @@ func (am *DefaultAccountManager) UpdatePeerMeta(peerKey string, meta PeerSystemM return nil } -// getPeersByACL allowed for given peer by ACL +// getPeersByACL returns all peers that given peer has access to. func (am *DefaultAccountManager) getPeersByACL(account *Account, peerKey string) []*Peer { var peers []*Peer srcRules, err := am.Store.GetPeerSrcRules(account.Id, peerKey) @@ -409,7 +484,8 @@ func (am *DefaultAccountManager) getPeersByACL(account *Account, peerKey string) return peers } -// updateAccountPeers network map constructed by ACL +// updateAccountPeers updates all peers that belong to an account. +// Should be called when changes have to be synced to peers. func (am *DefaultAccountManager) updateAccountPeers(account *Account) error { // notify other peers of the change peers, err := am.Store.GetAccountPeers(account.Id) @@ -422,7 +498,7 @@ func (am *DefaultAccountManager) updateAccountPeers(account *Account) error { err = am.peersUpdateManager.SendUpdate(p.Key, &UpdateMessage{ Update: &proto.SyncResponse{ - // fill those field for backward compatibility + // fill deprecated fields for backward compatibility RemotePeers: update, RemotePeersIsEmpty: len(update) == 0, // new field @@ -430,6 +506,7 @@ func (am *DefaultAccountManager) updateAccountPeers(account *Account) error { Serial: account.Network.CurrentSerial(), RemotePeers: update, RemotePeersIsEmpty: len(update) == 0, + PeerConfig: toPeerConfig(p), }, }, }) diff --git a/management/server/updatechannel.go b/management/server/updatechannel.go index d4e7d9220..ba443c8c1 100644 --- a/management/server/updatechannel.go +++ b/management/server/updatechannel.go @@ -9,6 +9,7 @@ import ( type UpdateMessage struct { Update *proto.SyncResponse } + type PeersUpdateManager struct { peerChannels map[string]chan *UpdateMessage channelsMux *sync.Mutex diff --git a/util/membership_unix.go b/util/membership_unix.go new file mode 100644 index 000000000..82237461c --- /dev/null +++ b/util/membership_unix.go @@ -0,0 +1,12 @@ +//go:build linux || darwin + +package util + +import ( + "os" +) + +// IsAdmin returns true if user is root +func IsAdmin() bool { + return os.Geteuid() == 0 +} diff --git a/util/membership_windows.go b/util/membership_windows.go new file mode 100644 index 000000000..67a04577e --- /dev/null +++ b/util/membership_windows.go @@ -0,0 +1,12 @@ +package util + +import "golang.zx2c4.com/wireguard/windows/elevate" + +// IsAdmin returns true if user has admin privileges +func IsAdmin() bool { + adminDesktop, err := elevate.IsAdminDesktop() + if err == nil && adminDesktop { + return true + } + return false +}