From 4ab79a556fc71e45143e3274c7cbe0358b388b7d Mon Sep 17 00:00:00 2001 From: f1v3-dev Date: Mon, 10 Aug 2026 16:12:10 +0900 Subject: [PATCH] FIX: Use OpenSSH and prevent false-positive process detection --- go.mod | 1 - go.sum | 2 - internal/cluster/delete.go | 2 +- internal/cluster/server.go | 6 +++ internal/cluster/status.go | 2 +- internal/ssh/ssh.go | 105 ++++++------------------------------- internal/zk/delete.go | 2 +- 7 files changed, 24 insertions(+), 96 deletions(-) diff --git a/go.mod b/go.mod index f3c88f7..b3aec68 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.21.0 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.50.0 golang.org/x/term v0.42.0 ) diff --git a/go.sum b/go.sum index d094604..5a3827c 100644 --- a/go.sum +++ b/go.sum @@ -49,8 +49,6 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= diff --git a/internal/cluster/delete.go b/internal/cluster/delete.go index cfa4906..7e7c813 100644 --- a/internal/cluster/delete.go +++ b/internal/cluster/delete.go @@ -83,7 +83,7 @@ func verifyAllStopped( ) error { for _, server := range topo.Servers { pidFile := pidFilePath(server.Address, topo.Path, version) - cmd := fmt.Sprintf("pgrep -f %q > /dev/null 2>&1", pidFile) + cmd := processRunningCommand(pidFile) if err := ssh.Run(server.Host(), cmd); err == nil { return fmt.Errorf( "cache server %q is still running; stop the cluster before delete", diff --git a/internal/cluster/server.go b/internal/cluster/server.go index 0c60307..ea9acd4 100644 --- a/internal/cluster/server.go +++ b/internal/cluster/server.go @@ -3,6 +3,7 @@ package cluster import ( "fmt" "path" + "regexp" "strings" "github.com/jam2in/arcusctl/internal/topology" @@ -21,6 +22,11 @@ func pidFilePath( return path.Join(installPath, fmt.Sprintf("memcached-%s.pid", listenPort(serverAddress))) } +func processRunningCommand(pidFile string) string { + pattern := fmt.Sprintf("[m]emcached.*-P %s", regexp.QuoteMeta(pidFile)) + return fmt.Sprintf("pgrep -f %q > /dev/null 2>&1", pattern) +} + func listenPort(address string) string { parts := strings.SplitN(address, ":", 2) if len(parts) < 2 { diff --git a/internal/cluster/status.go b/internal/cluster/status.go index b911b7e..8a7589b 100644 --- a/internal/cluster/status.go +++ b/internal/cluster/status.go @@ -57,7 +57,7 @@ func processStatus( version string, ) string { pidFile := pidFilePath(server.Address, topoPath, version) - cmd := fmt.Sprintf("pgrep -f %q > /dev/null 2>&1", pidFile) + cmd := processRunningCommand(pidFile) if err := ssh.Run(server.Host(), cmd); err == nil { return "running" } diff --git a/internal/ssh/ssh.go b/internal/ssh/ssh.go index 4e638cf..648e1ef 100644 --- a/internal/ssh/ssh.go +++ b/internal/ssh/ssh.go @@ -5,114 +5,39 @@ import ( "fmt" "os" "os/exec" - "os/user" - "path/filepath" - - gossh "golang.org/x/crypto/ssh" ) -func currentUser() (string, error) { - u, err := user.Current() - if err != nil { - return "", err - } - - return u.Username, nil -} - -func newClient(host string) (*gossh.Client, error) { - username, err := currentUser() - if err != nil { - return nil, err - } - - homeDir, err := os.UserHomeDir() - if err != nil { - return nil, err - } - - keyPath := filepath.Join(homeDir, ".ssh", "id_rsa") - key, err := os.ReadFile(keyPath) - if err != nil { - return nil, err - } - - signer, err := gossh.ParsePrivateKey(key) - if err != nil { - return nil, err - } - - config := &gossh.ClientConfig{ - User: username, - Auth: []gossh.AuthMethod{ - gossh.PublicKeys(signer), - }, - HostKeyCallback: gossh.InsecureIgnoreHostKey(), - } - - return gossh.Dial("tcp", host+":22", config) +func withOptions(args ...string) []string { + return append([]string{ + "-o", "BatchMode=yes", + "-o", "ConnectTimeout=10", + }, args...) } func Run(host string, command string) error { - client, err := newClient(host) - if err != nil { - return err - } - defer client.Close() - - session, err := client.NewSession() - if err != nil { - return err - } - defer session.Close() - - session.Stdout = os.Stdout - session.Stderr = os.Stderr - return session.Run(command) + cmd := exec.Command("ssh", withOptions(host, command)...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() } func Copy(localPath string, host string, remotePath string) error { - username, err := currentUser() - if err != nil { - return err - } - - homeDir, err := os.UserHomeDir() - if err != nil { - return err - } - - dest := fmt.Sprintf("%s@%s:%s", username, host, remotePath) - cmd := exec.Command("scp", - "-i", filepath.Join(homeDir, ".ssh", "id_rsa"), - "-o", "StrictHostKeyChecking=no", - localPath, - dest) + dest := fmt.Sprintf("%s:%s", host, remotePath) + cmd := exec.Command("scp", withOptions(localPath, dest)...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() } func FileExists(host string, remotePath string) (bool, error) { - client, err := newClient(host) - if err != nil { - return false, err - } - defer client.Close() - - session, err := client.NewSession() - if err != nil { - return false, err - } - defer session.Close() - - err = session.Run(fmt.Sprintf("test -f %s", remotePath)) + cmd := exec.Command("ssh", withOptions(host, fmt.Sprintf("test -f %s", remotePath))...) + err := cmd.Run() if err == nil { return true, nil } - var exitErr *gossh.ExitError - if errors.As(err, &exitErr) && exitErr.ExitStatus() == 1 { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 { return false, nil } diff --git a/internal/zk/delete.go b/internal/zk/delete.go index 62515da..a59af44 100644 --- a/internal/zk/delete.go +++ b/internal/zk/delete.go @@ -59,7 +59,7 @@ func verifyTopology(servers []topology.ZKServer, topoPath string) error { func verifyAllStopped(servers []topology.ZKServer, topoPath string) error { for _, server := range servers { confPath := zkConfigPath(topoPath, server.MyID) - cmd := fmt.Sprintf("pgrep -f 'QuorumPeerMain.*%s' > /dev/null 2>&1", confPath) + cmd := fmt.Sprintf("pgrep -f '[Q]uorumPeerMain.*%s' > /dev/null 2>&1", confPath) if err := ssh.Run(server.Host(), cmd); err == nil { return fmt.Errorf("server %s (myid=%d) is still running. stop the ensemble before delete", server.Host(), server.MyID)