diff --git a/cmd/kubevpn/cmds/root.go b/cmd/kubevpn/cmds/root.go index 69130afb..5bef221c 100644 --- a/cmd/kubevpn/cmds/root.go +++ b/cmd/kubevpn/cmds/root.go @@ -114,7 +114,7 @@ func (f *warp) ToRawKubeConfigLoader() clientcmd.ClientConfig { f.KubeConfig = ptr.To(strings.Replace(*f.KubeConfig, "~", home, 1)) } if strings.TrimSpace(f.KubeconfigJson) != "" { - path, err := util.ConvertToTempKubeconfigFile([]byte(f.KubeconfigJson)) + path, err := util.ConvertToTempKubeconfigFile([]byte(f.KubeconfigJson), "") if err == nil { f.KubeConfig = ptr.To(path) } diff --git a/cmd/kubevpn/cmds/run.go b/cmd/kubevpn/cmds/run.go index 6372880a..6c80e5aa 100644 --- a/cmd/kubevpn/cmds/run.go +++ b/cmd/kubevpn/cmds/run.go @@ -89,15 +89,14 @@ func CmdRun(f cmdutil.Factory) *cobra.Command { if err != nil { return err } + if sshConf.IsEmpty() { + return nil + } bytes, _, err := util.ConvertToKubeConfigBytes(f) if err != nil { return err } - file, err := util.ConvertToTempKubeconfigFile(bytes) - if err != nil { - return err - } - return pkgssh.SshJumpAndSetEnv(cmd.Context(), sshConf, file, false) + return pkgssh.SshJumpAndSetEnv(cmd.Context(), sshConf, bytes, false) }, RunE: func(cmd *cobra.Command, args []string) error { options.Workload = args[0] diff --git a/cmd/kubevpn/cmds/status.go b/cmd/kubevpn/cmds/status.go index 4d9a82ad..d416e29b 100644 --- a/cmd/kubevpn/cmds/status.go +++ b/cmd/kubevpn/cmds/status.go @@ -252,20 +252,19 @@ func GetConnectionIDByConfig(cmd *cobra.Command, config Config) (string, error) _ = flags.Set(flag.Name, value) return nil }) - bytes, ns, err := util.ConvertToKubeConfigBytes(f) - if err != nil { - return "", err - } - file, err := util.ConvertToTempKubeconfigFile(bytes) + kubeConfigBytes, ns, err := util.ConvertToKubeConfigBytes(f) if err != nil { return "", err } + var file string defer os.Remove(file) if !sshConf.IsEmpty() { - file, err = pkgssh.SshJump(cmd.Context(), sshConf, file, false) - if err != nil { - return "", err - } + file, err = pkgssh.SshJump(cmd.Context(), sshConf, kubeConfigBytes, false) + } else { + file, err = util.ConvertToTempKubeconfigFile(kubeConfigBytes, "") + } + if err != nil { + return "", err } var c = &handler.ConnectOptions{} err = c.InitClient(util.InitFactoryByPath(file, ns)) @@ -297,7 +296,7 @@ func ParseArgs(cmd *cobra.Command, conf *Config) error { return nil } - file, err := util.ConvertToKubeconfigFile([]byte(str), filepath.Join(config.GetTempPath(), conf.Name)) + file, err := util.ConvertToTempKubeconfigFile([]byte(str), filepath.Join(config.GetTempPath(), conf.Name)) if err != nil { return err } diff --git a/pkg/daemon/action/connect.go b/pkg/daemon/action/connect.go index dcfdfa77..c64fec14 100644 --- a/pkg/daemon/action/connect.go +++ b/pkg/daemon/action/connect.go @@ -47,7 +47,7 @@ func (svr *Server) Connect(resp rpc.Daemon_ConnectServer) (err error) { ImagePullSecretName: req.ImagePullSecretName, Request: proto.Clone(req).(*rpc.ConnectRequest), } - file, err := util.ConvertToTempKubeconfigFile([]byte(req.KubeconfigBytes)) + file, err := util.ConvertToTempKubeconfigFile([]byte(req.KubeconfigBytes), "") if err != nil { return err } @@ -95,10 +95,6 @@ func (svr *Server) redirectConnectToSudoDaemon(req *rpc.ConnectRequest, resp rpc return errors.Wrap(err, "sudo daemon not start") } var sshConf = ssh.ParseSshFromRPC(req.SshJump) - file, err := util.ConvertToTempKubeconfigFile([]byte(req.KubeconfigBytes)) - if err != nil { - return err - } sshCtx, sshCancel := context.WithCancel(context.Background()) sshCtx = plog.WithLogger(sshCtx, logger) defer plog.WithoutLogger(sshCtx) @@ -109,11 +105,28 @@ func (svr *Server) redirectConnectToSudoDaemon(req *rpc.ConnectRequest, resp rpc OriginKubeconfigPath: req.OriginKubeconfigPath, Request: proto.Clone(req).(*rpc.ConnectRequest), } + var file string connect.AddRolloutFunc(func() error { sshCancel() _ = os.Remove(file) return nil }) + + if !sshConf.IsEmpty() { + file, err = ssh.SshJump(sshCtx, sshConf, []byte(req.KubeconfigBytes), true) + if err != nil { + return err + } + if sshConf.RemoteKubeconfig != "" { + connect.OriginKubeconfigPath = file + } + } else { + file, err = util.ConvertToTempKubeconfigFile([]byte(req.KubeconfigBytes), "") + if err != nil { + return err + } + } + defer func() { if err != nil { connect.Cleanup(plog.WithLogger(context.Background(), logger)) @@ -134,13 +147,6 @@ func (svr *Server) redirectConnectToSudoDaemon(req *rpc.ConnectRequest, resp rpc sshCancel() } }() - - if !sshConf.IsEmpty() { - file, err = ssh.SshJump(sshCtx, sshConf, file, true) - if err != nil { - return err - } - } err = connect.InitClient(util.InitFactoryByPath(file, req.Namespace)) if err != nil { return err diff --git a/pkg/daemon/action/disconnect.go b/pkg/daemon/action/disconnect.go index 1e916b00..d3c627e1 100644 --- a/pkg/daemon/action/disconnect.go +++ b/pkg/daemon/action/disconnect.go @@ -112,17 +112,17 @@ func (svr *Server) Disconnect(resp rpc.Daemon_DisconnectServer) (err error) { } func disconnectByKubeconfig(ctx context.Context, svr *Server, kubeconfigBytes string, ns string, jump *rpc.SshJump) error { - file, err := util.ConvertToTempKubeconfigFile([]byte(kubeconfigBytes)) - if err != nil { - return err - } - defer os.Remove(file) + var file string + var err error var sshConf = ssh.ParseSshFromRPC(jump) if !sshConf.IsEmpty() { - file, err = ssh.SshJump(ctx, sshConf, file, false) - if err != nil { - return err - } + file, err = ssh.SshJump(ctx, sshConf, []byte(kubeconfigBytes), false) + } else { + file, err = util.ConvertToTempKubeconfigFile([]byte(kubeconfigBytes), "") + } + defer os.Remove(file) + if err != nil { + return err } connect := &handler.ConnectOptions{} err = connect.InitClient(util.InitFactoryByPath(file, ns)) diff --git a/pkg/daemon/action/proxy.go b/pkg/daemon/action/proxy.go index dc24b419..1cfe2253 100644 --- a/pkg/daemon/action/proxy.go +++ b/pkg/daemon/action/proxy.go @@ -36,16 +36,14 @@ func (svr *Server) Proxy(resp rpc.Daemon_ProxyServer) (err error) { var sshConf = ssh.ParseSshFromRPC(req.SshJump) var file string - file, err = util.ConvertToTempKubeconfigFile([]byte(req.KubeconfigBytes)) - if err != nil { - return err - } defer os.Remove(file) if !sshConf.IsEmpty() { - file, err = ssh.SshJump(ctx, sshConf, file, false) - if err != nil { - return err - } + file, err = ssh.SshJump(ctx, sshConf, []byte(req.KubeconfigBytes), false) + } else { + file, err = util.ConvertToTempKubeconfigFile([]byte(req.KubeconfigBytes), "") + } + if err != nil { + return err } connect := &handler.ConnectOptions{ ExtraRouteInfo: *handler.ParseExtraRouteFromRPC(req.ExtraRoute), diff --git a/pkg/daemon/action/reset.go b/pkg/daemon/action/reset.go index a3560906..345fb6dd 100644 --- a/pkg/daemon/action/reset.go +++ b/pkg/daemon/action/reset.go @@ -20,18 +20,17 @@ func (svr *Server) Reset(resp rpc.Daemon_ResetServer) error { } logger := plog.GetLoggerForClient(int32(log.InfoLevel), io.MultiWriter(newResetWarp(resp), svr.LogFile)) - file, err := util.ConvertToTempKubeconfigFile([]byte(req.KubeconfigBytes)) - if err != nil { - return err - } + var file string defer os.Remove(file) var sshConf = ssh.ParseSshFromRPC(req.SshJump) var ctx = plog.WithLogger(resp.Context(), logger) if !sshConf.IsEmpty() { - file, err = ssh.SshJump(ctx, sshConf, file, false) - if err != nil { - return err - } + file, err = ssh.SshJump(ctx, sshConf, []byte(req.KubeconfigBytes), false) + } else { + file, err = util.ConvertToTempKubeconfigFile([]byte(req.KubeconfigBytes), "") + } + if err != nil { + return err } connect := &handler.ConnectOptions{} err = connect.InitClient(util.InitFactoryByPath(file, req.Namespace)) diff --git a/pkg/daemon/action/sync.go b/pkg/daemon/action/sync.go index 91ac3b28..4091ebf9 100644 --- a/pkg/daemon/action/sync.go +++ b/pkg/daemon/action/sync.go @@ -89,26 +89,25 @@ func (svr *Server) Sync(resp rpc.Daemon_SyncServer) (err error) { LocalDir: req.LocalDir, RemoteDir: req.RemoteDir, } - file, err := util.ConvertToTempKubeconfigFile([]byte(req.KubeconfigBytes)) - if err != nil { - return err - } defer func() { if err != nil { _ = options.Cleanup(sshCtx) sshFunc() } }() + var file string options.AddRollbackFunc(func() error { sshFunc() _ = os.Remove(file) return nil }) if !sshConf.IsEmpty() { - file, err = ssh.SshJump(sshCtx, sshConf, file, false) - if err != nil { - return err - } + file, err = ssh.SshJump(sshCtx, sshConf, []byte(req.KubeconfigBytes), false) + } else { + file, err = util.ConvertToTempKubeconfigFile([]byte(req.KubeconfigBytes), "") + } + if err != nil { + return err } f := util.InitFactoryByPath(file, req.Namespace) err = options.InitClient(f) diff --git a/pkg/daemon/action/uninstall.go b/pkg/daemon/action/uninstall.go index 0baa0850..f352ea0a 100644 --- a/pkg/daemon/action/uninstall.go +++ b/pkg/daemon/action/uninstall.go @@ -24,18 +24,17 @@ func (svr *Server) Uninstall(resp rpc.Daemon_UninstallServer) (err error) { } logger := plog.GetLoggerForClient(int32(log.InfoLevel), io.MultiWriter(newUninstallWarp(resp), svr.LogFile)) - file, err := util.ConvertToTempKubeconfigFile([]byte(req.KubeconfigBytes)) - if err != nil { - return err - } + var file string defer os.Remove(file) var sshConf = ssh.ParseSshFromRPC(req.SshJump) var ctx = plog.WithLogger(resp.Context(), logger) if !sshConf.IsEmpty() { - file, err = ssh.SshJump(ctx, sshConf, file, false) - if err != nil { - return err - } + file, err = ssh.SshJump(ctx, sshConf, []byte(req.KubeconfigBytes), false) + } else { + file, err = util.ConvertToTempKubeconfigFile([]byte(req.KubeconfigBytes), "") + } + if err != nil { + return err } factory := util.InitFactoryByPath(file, req.Namespace) clientset, err := factory.KubernetesClientSet() diff --git a/pkg/ssh/config.go b/pkg/ssh/config.go index 4129b73e..99adfe80 100644 --- a/pkg/ssh/config.go +++ b/pkg/ssh/config.go @@ -51,6 +51,31 @@ func (conf SshConfig) Clone() SshConfig { } } +func (conf *SshConfig) GenKubeconfigIdentify() string { + var prefix string + if conf.ConfigAlias != "" { + prefix = conf.ConfigAlias + } else if conf.Addr != "" { + if host, port, err := net.SplitHostPort(conf.Addr); err == nil { + prefix = fmt.Sprintf("%s_%s", IPToFilename(host), port) + } else { + prefix = IPToFilename(conf.Addr) + } + } else if conf.Jump != "" { + flags := pflag.NewFlagSet("", pflag.ContinueOnError) + var sshConf = &SshConfig{} + AddSshFlags(flags, sshConf) + _ = flags.Parse(strings.Split(conf.Jump, " ")) + prefix = sshConf.GenKubeconfigIdentify() + } + + if prefix == "" { + return filepath.Base(conf.RemoteKubeconfig) + } + + return fmt.Sprintf("%s_%s", prefix, filepath.Base(conf.RemoteKubeconfig)) +} + func ParseSshFromRPC(sshJump *rpc.SshJump) *SshConfig { if sshJump == nil { return &SshConfig{} diff --git a/pkg/ssh/name.go b/pkg/ssh/name.go new file mode 100644 index 00000000..24210481 --- /dev/null +++ b/pkg/ssh/name.go @@ -0,0 +1,103 @@ +package ssh + +import ( + "fmt" + "net" + "strings" + "unicode" +) + +func IPToFilename(ipStr string) string { + ip := net.ParseIP(ipStr) + if ip == nil { + return "invalid-ip" + } + + var filename string + + if ip.To4() != nil { + filename = ip.String() + } else { + filename = convertIPv6(ip) + } + + return sanitizeFilename(filename) +} + +func convertIPv6(ip net.IP) string { + ip = ip.To16() + if ip == nil { + return "invalid-ipv6" + } + + var zone string + if strings.Contains(ip.String(), "%") { + parts := strings.Split(ip.String(), "%") + if len(parts) > 1 { + zone = parts[1] + } + } + + base := fmt.Sprintf("%02x%02x-%02x%02x-%02x%02x-%02x%02x", + ip[0], ip[1], ip[2], ip[3], + ip[4], ip[5], ip[6], ip[7], + ) + + if zone != "" { + zone = sanitizeZone(zone) + return base + "--" + zone + } + return base +} + +func sanitizeZone(zone string) string { + var result strings.Builder + for _, r := range zone { + if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-' || r == '_' { + result.WriteRune(r) + } else { + result.WriteRune('-') + } + } + return result.String() +} + +func sanitizeFilename(name string) string { + var result strings.Builder + lastWasDash := false + + for _, r := range name { + switch { + case unicode.IsLetter(r) || unicode.IsDigit(r): + result.WriteRune(r) + lastWasDash = false + + case r == '-' || r == '_' || r == '.': + if r == '.' { + if !lastWasDash { + result.WriteRune(r) + lastWasDash = true + } + } else { + result.WriteRune(r) + lastWasDash = true + } + + default: + if !lastWasDash { + result.WriteRune('-') + lastWasDash = true + } + } + } + + fname := result.String() + + fname = strings.Trim(fname, "-_.") + + if fname == "" { + return "ip-address" + } + + return fname +} diff --git a/pkg/ssh/name_test.go b/pkg/ssh/name_test.go new file mode 100644 index 00000000..174234f3 --- /dev/null +++ b/pkg/ssh/name_test.go @@ -0,0 +1,21 @@ +package ssh + +import ( + "testing" +) + +func TestName(t *testing.T) { + testIPs := []string{ + "192.168.1.1", + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "::1", + "fe80::1%eth0", + "203.0.113.0", + "invalid-ip", + "::ffff:192.168.1.1", + } + + for _, ip := range testIPs { + t.Logf("%-35s => %s", ip, IPToFilename(ip)) + } +} diff --git a/pkg/ssh/ssh.go b/pkg/ssh/ssh.go index d0fa8b66..b5f84e3c 100644 --- a/pkg/ssh/ssh.go +++ b/pkg/ssh/ssh.go @@ -139,8 +139,7 @@ func PortMapUntil(ctx context.Context, conf *SshConfig, remote, local netip.Addr return nil } -func SshJump(ctx context.Context, conf *SshConfig, kubeconfig string, print bool) (path string, err error) { - var kubeconfigBytes []byte +func SshJump(ctx context.Context, conf *SshConfig, kubeconfigBytes []byte, print bool) (path string, err error) { if len(conf.RemoteKubeconfig) != 0 { var stdout []byte var stderr []byte @@ -169,11 +168,7 @@ func SshJump(ctx context.Context, conf *SshConfig, kubeconfig string, print bool return } kubeconfigBytes = bytes.TrimSpace(stdout) - } else { - kubeconfigBytes, err = os.ReadFile(kubeconfig) - if err != nil { - return - } + path = filepath.Join(config.GetTempPath(), conf.GenKubeconfigIdentify()) } var clientConfig clientcmd.ClientConfig clientConfig, err = clientcmd.NewClientConfigFromBytes(kubeconfigBytes) @@ -289,7 +284,7 @@ func SshJump(ctx context.Context, conf *SshConfig, kubeconfig string, print bool plog.G(ctx).Errorf("failed to marshal config: %v", err) return } - path, err = pkgutil.ConvertToTempKubeconfigFile(marshal) + path, err = pkgutil.ConvertToTempKubeconfigFile(marshal, path) if err != nil { plog.G(ctx).Errorf("failed to write kubeconfig: %v", err) return @@ -297,7 +292,6 @@ func SshJump(ctx context.Context, conf *SshConfig, kubeconfig string, print bool go func() { <-ctx.Done() _ = os.Remove(path) - _ = os.Remove(kubeconfig) }() if print { plog.G(ctx).Infof("Use temp kubeconfig: %s", path) @@ -307,11 +301,8 @@ func SshJump(ctx context.Context, conf *SshConfig, kubeconfig string, print bool return } -func SshJumpAndSetEnv(ctx context.Context, sshConf *SshConfig, file string, print bool) error { - if sshConf.IsEmpty() { - return nil - } - path, err := SshJump(ctx, sshConf, file, print) +func SshJumpAndSetEnv(ctx context.Context, sshConf *SshConfig, kubeconfigBytes []byte, print bool) error { + path, err := SshJump(ctx, sshConf, kubeconfigBytes, print) if err != nil { return err } diff --git a/pkg/util/kube.go b/pkg/util/kube.go index 13891f1d..6da58c4e 100644 --- a/pkg/util/kube.go +++ b/pkg/util/kube.go @@ -85,7 +85,7 @@ func ConvertK8sApiServerToDomain(kubeConfigPath string) (newPath string, err err if err != nil { return } - newPath, err = ConvertToTempKubeconfigFile(marshal) + newPath, err = ConvertToTempKubeconfigFile(marshal, "") if err != nil { return } diff --git a/pkg/util/ns.go b/pkg/util/ns.go index 1f0ef993..31639601 100644 --- a/pkg/util/ns.go +++ b/pkg/util/ns.go @@ -126,34 +126,20 @@ func GetAPIServerFromKubeConfigBytes(kubeconfigBytes []byte) *net.IPNet { return &net.IPNet{IP: ip, Mask: mask} } -func ConvertToTempKubeconfigFile(kubeconfigBytes []byte) (string, error) { - pattern := "*.kubeconfig" - cluster, ns, _ := GetCluster(kubeconfigBytes) - if cluster != "" && !containerPathSeparator(cluster) && !containerPathSeparator(ns) { - pattern = fmt.Sprintf("%s_%s_%s", cluster, ns, pattern) - pattern = strings.ReplaceAll(pattern, string(os.PathSeparator), "-") +func ConvertToTempKubeconfigFile(kubeconfigBytes []byte, path string) (string, error) { + var f *os.File + var err error + if path != "" { + f, err = os.Create(path) + } else { + pattern := "*.kubeconfig" + cluster, ns, _ := GetCluster(kubeconfigBytes) + if cluster != "" && !containerPathSeparator(cluster) && !containerPathSeparator(ns) { + pattern = fmt.Sprintf("%s_%s_%s", cluster, ns, pattern) + pattern = strings.ReplaceAll(pattern, string(os.PathSeparator), "-") + } + f, err = os.CreateTemp(config.GetTempPath(), pattern) } - temp, err := os.CreateTemp(config.GetTempPath(), pattern) - if err != nil { - return "", err - } - _, err = temp.Write(kubeconfigBytes) - if err != nil { - return "", err - } - err = temp.Chmod(0644) - if err != nil { - return "", err - } - err = temp.Close() - if err != nil { - return "", err - } - return temp.Name(), nil -} - -func ConvertToKubeconfigFile(kubeconfigBytes []byte, filename string) (string, error) { - f, err := os.Create(filename) if err != nil { return "", err } @@ -220,7 +206,7 @@ func InitFactory(kubeconfigBytes string, ns string) cmdutil.Factory { } return c } - file, err := ConvertToTempKubeconfigFile([]byte(kubeconfigBytes)) + file, err := ConvertToTempKubeconfigFile([]byte(kubeconfigBytes), "") if err != nil { return nil } @@ -270,7 +256,7 @@ func GetKubeconfigPath(factory cmdutil.Factory) (string, error) { return "", err } - file, err := ConvertToTempKubeconfigFile(kubeconfigJsonBytes) + file, err := ConvertToTempKubeconfigFile(kubeconfigJsonBytes, "") if err != nil { return "", err }