mirror of
https://github.com/kubenetworks/kubevpn.git
synced 2025-09-27 03:36:09 +08:00
270 lines
6.2 KiB
Go
270 lines
6.2 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/netip"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/crypto/ssh"
|
|
"golang.org/x/crypto/ssh/terminal"
|
|
"golang.org/x/net/websocket"
|
|
"golang.org/x/oauth2"
|
|
|
|
"github.com/wencaiwulue/kubevpn/pkg/config"
|
|
"github.com/wencaiwulue/kubevpn/pkg/core"
|
|
"github.com/wencaiwulue/kubevpn/pkg/handler"
|
|
"github.com/wencaiwulue/kubevpn/pkg/upgrade"
|
|
"github.com/wencaiwulue/kubevpn/pkg/util"
|
|
)
|
|
|
|
// Ws
|
|
// 0) remote server install kubevpn if not found
|
|
// 1) start remote kubevpn server
|
|
// 2) start local tunnel
|
|
// 3) ssh terminal
|
|
func Ws(conn *websocket.Conn, sshConfig *util.SshConfig) {
|
|
var ctx = context.Background()
|
|
cancel, cancelFunc := context.WithCancel(ctx)
|
|
defer cancelFunc()
|
|
|
|
err := remoteInstallKubevpnIfCommandNotFound(ctx, sshConfig)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
clientIP, err := util.GetIPBaseNic()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
local, err := portMap(cancel, sshConfig)
|
|
if err != nil {
|
|
return
|
|
}
|
|
cmd := fmt.Sprintf(`export %s=%s && kubevpn ssh-daemon --client-ip %s`, config.EnvStartSudoKubeVPNByKubeVPN, "true", clientIP.String())
|
|
serverIP, stderr, err := util.RemoteRun(sshConfig, cmd, nil)
|
|
if err != nil {
|
|
log.Errorf("run error: %v", err)
|
|
log.Errorf("run stdout: %v", string(serverIP))
|
|
log.Errorf("run stderr: %v", string(stderr))
|
|
return
|
|
}
|
|
ip, _, err := net.ParseCIDR(string(serverIP))
|
|
if err != nil {
|
|
return
|
|
}
|
|
r := core.Route{
|
|
ServeNodes: []string{
|
|
fmt.Sprintf("tun:/127.0.0.1:8422?net=%s&route=%s", clientIP, string(serverIP)),
|
|
},
|
|
ChainNode: fmt.Sprintf("tcp://127.0.0.1:%d", local),
|
|
Retries: 5,
|
|
}
|
|
servers, err := handler.Parse(r)
|
|
if err != nil {
|
|
log.Errorf("parse route error: %v", err)
|
|
return
|
|
}
|
|
go func() {
|
|
log.Error(handler.Run(cancel, servers))
|
|
}()
|
|
tun, err := util.GetTunDevice(clientIP.IP)
|
|
if err != nil {
|
|
return
|
|
}
|
|
log.Info("tunnel connected")
|
|
go func() {
|
|
ticker := time.NewTicker(time.Second * 2)
|
|
for {
|
|
select {
|
|
case <-cancel.Done():
|
|
return
|
|
case <-ticker.C:
|
|
_, _ = util.Ping(clientIP.IP.String())
|
|
_, _ = util.Ping(ip.String())
|
|
_ = exec.CommandContext(cancel, "ping", "-c", "4", "-b", tun.Name, ip.String()).Run()
|
|
}
|
|
}
|
|
}()
|
|
err = enterTerminal(sshConfig, conn)
|
|
return
|
|
}
|
|
|
|
func portMap(ctx context.Context, conf *util.SshConfig) (localPort int, err error) {
|
|
removePort := 10800
|
|
localPort, err = util.GetAvailableTCPPortOrDie()
|
|
if err != nil {
|
|
return
|
|
}
|
|
var remote netip.AddrPort
|
|
remote, err = netip.ParseAddrPort(net.JoinHostPort("127.0.0.1", strconv.Itoa(removePort)))
|
|
if err != nil {
|
|
return
|
|
}
|
|
var local netip.AddrPort
|
|
local, err = netip.ParseAddrPort(net.JoinHostPort("127.0.0.1", strconv.Itoa(localPort)))
|
|
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
|
|
}
|
|
}
|
|
|
|
func enterTerminal(conf *util.SshConfig, conn *websocket.Conn) error {
|
|
cli, err := util.DialSshRemote(conf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
session, err := cli.NewSession()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer session.Close()
|
|
session.Stdout = conn
|
|
session.Stderr = conn
|
|
session.Stdin = conn
|
|
|
|
fd := int(os.Stdin.Fd())
|
|
state, err := terminal.MakeRaw(fd)
|
|
if err != nil {
|
|
return fmt.Errorf("terminal make raw: %s", err)
|
|
}
|
|
defer terminal.Restore(fd, state)
|
|
|
|
w, h, err := terminal.GetSize(fd)
|
|
if err != nil {
|
|
return fmt.Errorf("terminal get size: %s", err)
|
|
}
|
|
modes := ssh.TerminalModes{
|
|
ssh.ECHO: 1,
|
|
ssh.ECHOCTL: 0,
|
|
ssh.TTY_OP_ISPEED: 14400,
|
|
ssh.TTY_OP_OSPEED: 14400,
|
|
}
|
|
if err := session.RequestPty("xterm", h, w, modes); err != nil {
|
|
return err
|
|
}
|
|
if err = session.Shell(); err != nil {
|
|
return err
|
|
}
|
|
return session.Wait()
|
|
}
|
|
|
|
func remoteInstallKubevpnIfCommandNotFound(ctx context.Context, sshConfig *util.SshConfig) error {
|
|
cmd := `hash kubevpn1 || type kubevpn1 || which kubevpn1 || command -v kubevpn1`
|
|
_, _, err := util.RemoteRun(sshConfig, cmd, nil)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
log.Infof("remote kubevpn command not found, try to install it...")
|
|
var client = http.DefaultClient
|
|
if config.GitHubOAuthToken != "" {
|
|
client = oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.GitHubOAuthToken, TokenType: "Bearer"}))
|
|
}
|
|
latestVersion, latestCommit, url, err := upgrade.GetManifest(client, "linux", "amd64")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("The latest version is: %s, commit: %s\n", latestVersion, latestCommit)
|
|
var temp *os.File
|
|
temp, err = os.CreateTemp("", "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = temp.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = upgrade.Download(client, url, temp.Name())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var tempBin *os.File
|
|
tempBin, err = os.CreateTemp("", "kubevpn")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = tempBin.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = upgrade.UnzipKubeVPNIntoFile(temp.Name(), tempBin.Name())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// scp kubevpn to remote ssh server and run daemon
|
|
err = os.Chmod(tempBin.Name(), 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = os.Remove(temp.Name())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Infof("Upgrade daemon...")
|
|
err = util.SCP(sshConfig, tempBin.Name(), "/usr/local/bin/kubevpn")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// try to startup daemon process
|
|
go util.RemoteRun(sshConfig, "kubevpn get pods", nil)
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
http.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
|
sshConfig := util.SshConfig{
|
|
Addr: conn.Request().Header.Get("ssh-addr"),
|
|
User: conn.Request().Header.Get("ssh-username"),
|
|
Password: conn.Request().Header.Get("ssh-password"),
|
|
Keyfile: conn.Request().Header.Get("ssh-keyfile"),
|
|
ConfigAlias: conn.Request().Header.Get("ssh-alias"),
|
|
}
|
|
Ws(conn, &sshConfig)
|
|
}))
|
|
}
|