diff --git a/cmd/kubevpn/cmds/ssh.go b/cmd/kubevpn/cmds/ssh.go new file mode 100644 index 00000000..5b35b115 --- /dev/null +++ b/cmd/kubevpn/cmds/ssh.go @@ -0,0 +1,50 @@ +package cmds + +import ( + "os" + + "github.com/spf13/cobra" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" + + "github.com/wencaiwulue/kubevpn/pkg/config" + "github.com/wencaiwulue/kubevpn/pkg/handler" + "github.com/wencaiwulue/kubevpn/pkg/util" +) + +// CmdSSH +// 设置本地的IP是223.254.0.1/32 ,记得一定是掩码 32位, +// 这样别的路由不会走到这里来 +func CmdSSH(_ cmdutil.Factory) *cobra.Command { + var sshConf = &util.SshConfig{} + cmd := &cobra.Command{ + Use: "ssh", + Hidden: true, + Short: "Ssh to jump server", + Long: `Ssh to jump server`, + Example: templates.Examples(i18n.T(` + # Jump to server behind of bastion host or ssh jump host + kubevpn ssh --ssh-addr 192.168.1.100:22 --ssh-username root --ssh-keyfile ~/.ssh/ssh.pem + + # it also support ProxyJump, like + ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌────────┐ + │ pc ├────►│ ssh1 ├────►│ ssh2 ├────►│ ssh3 ├─────►... ─────► │ server │ + └──────┘ └──────┘ └──────┘ └──────┘ └────────┘ + kubevpn ssh --ssh-alias +`)), + PreRun: func(*cobra.Command, []string) { + if !util.IsAdmin() { + util.RunWithElevated() + os.Exit(0) + } + }, + RunE: func(cmd *cobra.Command, args []string) error { + _ = os.Setenv(config.EnvKubeVPNTransportEngine, string(config.EngineGvisor)) + err := handler.SSH(cmd.Context(), sshConf) + return err + }, + } + addSshFlags(cmd, sshConf) + return cmd +} diff --git a/pkg/handler/ssh.go b/pkg/handler/ssh.go new file mode 100644 index 00000000..184fc49c --- /dev/null +++ b/pkg/handler/ssh.go @@ -0,0 +1,109 @@ +package handler + +import ( + "context" + "errors" + "fmt" + "golang.org/x/crypto/ssh" + "net" + "net/netip" + "strconv" + + log "github.com/sirupsen/logrus" + "github.com/wencaiwulue/kubevpn/pkg/core" + "github.com/wencaiwulue/kubevpn/pkg/util" +) + +// SSH +// 0) remote server install kubevpn if not found +// 1) start remote kubevpn server +// 2) start local tunnel +// 3) ssh terminal +func SSH(ctx context.Context, config *util.SshConfig) error { + cancel, cancelFunc := context.WithCancel(ctx) + defer cancelFunc() + err := portMap(ctx, config) + if err != nil { + return err + } + go func() { + stdout, stderr, err := util.RemoteRun(config, fmt.Sprintf(`kubevpn serve -L "tcp://:10800" -L "tun://127.0.0.1:8422?net=223.254.0.123/32"`), nil) + if err != nil { + log.Errorf("run error: %v", err) + log.Errorf("run stdout: %v", string(stdout)) + log.Errorf("run stderr: %v", string(stderr)) + cancelFunc() + } + }() + + r := core.Route{ + ServeNodes: []string{ + fmt.Sprintf("tun:/127.0.0.1:8422?net=%s&route=%s", "223.254.0.124/32", "223.254.0.124/32"), + }, + ChainNode: "tcp://172.17.64.35:10800", + Retries: 5, + } + servers, err := Parse(r) + if err != nil { + log.Errorf("parse route error: %v", err) + return err + } + go func() { + log.Error(Run(cancel, servers)) + }() + log.Info("tunnel connected") + <-cancel.Done() + return err +} + +func portMap(ctx context.Context, conf *util.SshConfig) (err error) { + port := 10800 + var remote netip.AddrPort + remote, err = netip.ParseAddrPort(net.JoinHostPort("127.0.0.1", strconv.Itoa(port))) + if err != nil { + return + } + var local netip.AddrPort + local, err = netip.ParseAddrPort(net.JoinHostPort("127.0.0.1", strconv.Itoa(port))) + if err != nil { + return + } + + // pre-check network ip connect + var cli *ssh.Client + cli, err = util.DialSshRemote(conf) + if err != nil { + return + } else { + _ = cli.Close() + } + errChan := make(chan error, 1) + readyChan := make(chan struct{}, 1) + go func() { + for { + select { + case <-ctx.Done(): + return + default: + } + + err := util.Main(ctx, remote, local, conf, readyChan) + if err != nil { + if !errors.Is(err, context.Canceled) { + log.Errorf("ssh forward failed err: %v", err) + } + select { + case errChan <- err: + default: + } + } + } + }() + select { + case <-readyChan: + return + case err = <-errChan: + log.Errorf("ssh proxy err: %v", err) + return + } +}