mirror of
				https://github.com/TeaOSLab/EdgeAPI.git
				synced 2025-11-04 07:50:25 +08:00 
			
		
		
		
	SSH认证支持sudo
This commit is contained in:
		@@ -73,7 +73,7 @@ func (this *NodeGrantDAO) FindNodeGrantName(tx *dbs.Tx, id uint32) (string, erro
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateGrant 创建认证信息
 | 
			
		||||
func (this *NodeGrantDAO) CreateGrant(tx *dbs.Tx, adminId int64, name string, method string, username string, password string, privateKey string, passphrase string, description string, nodeId int64) (grantId int64, err error) {
 | 
			
		||||
func (this *NodeGrantDAO) CreateGrant(tx *dbs.Tx, adminId int64, name string, method string, username string, password string, privateKey string, passphrase string, description string, nodeId int64, su bool) (grantId int64, err error) {
 | 
			
		||||
	op := NewNodeGrantOperator()
 | 
			
		||||
	op.AdminId = adminId
 | 
			
		||||
	op.Name = name
 | 
			
		||||
@@ -83,12 +83,12 @@ func (this *NodeGrantDAO) CreateGrant(tx *dbs.Tx, adminId int64, name string, me
 | 
			
		||||
	case "user":
 | 
			
		||||
		op.Username = username
 | 
			
		||||
		op.Password = password
 | 
			
		||||
		op.Su = false // TODO 需要做到前端可以配置
 | 
			
		||||
	case "privateKey":
 | 
			
		||||
		op.Username = username
 | 
			
		||||
		op.PrivateKey = privateKey
 | 
			
		||||
		op.Passphrase = passphrase
 | 
			
		||||
	}
 | 
			
		||||
	op.Su = su
 | 
			
		||||
	op.Description = description
 | 
			
		||||
	op.NodeId = nodeId
 | 
			
		||||
	op.State = NodeGrantStateEnabled
 | 
			
		||||
@@ -97,7 +97,7 @@ func (this *NodeGrantDAO) CreateGrant(tx *dbs.Tx, adminId int64, name string, me
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateGrant 修改认证信息
 | 
			
		||||
func (this *NodeGrantDAO) UpdateGrant(tx *dbs.Tx, grantId int64, name string, method string, username string, password string, privateKey string, passphrase string, description string, nodeId int64) error {
 | 
			
		||||
func (this *NodeGrantDAO) UpdateGrant(tx *dbs.Tx, grantId int64, name string, method string, username string, password string, privateKey string, passphrase string, description string, nodeId int64, su bool) error {
 | 
			
		||||
	if grantId <= 0 {
 | 
			
		||||
		return errors.New("invalid grantId")
 | 
			
		||||
	}
 | 
			
		||||
@@ -111,12 +111,12 @@ func (this *NodeGrantDAO) UpdateGrant(tx *dbs.Tx, grantId int64, name string, me
 | 
			
		||||
	case "user":
 | 
			
		||||
		op.Username = username
 | 
			
		||||
		op.Password = password
 | 
			
		||||
		op.Su = false // TODO 需要做到前端可以配置
 | 
			
		||||
	case "privateKey":
 | 
			
		||||
		op.Username = username
 | 
			
		||||
		op.PrivateKey = privateKey
 | 
			
		||||
		op.Passphrase = passphrase
 | 
			
		||||
	}
 | 
			
		||||
	op.Su = su
 | 
			
		||||
	op.Description = description
 | 
			
		||||
	op.NodeId = nodeId
 | 
			
		||||
	err := this.Save(tx, op)
 | 
			
		||||
 
 | 
			
		||||
@@ -8,4 +8,5 @@ type Credentials struct {
 | 
			
		||||
	PrivateKey string
 | 
			
		||||
	Passphrase string
 | 
			
		||||
	Method     string
 | 
			
		||||
	Sudo       bool
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,13 @@ func (this *BaseInstaller) Login(credentials *Credentials) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if credentials.Sudo {
 | 
			
		||||
		client.Sudo(credentials.Password)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.client = client
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -146,6 +152,10 @@ func (this *BaseInstaller) InstallHelper(targetDir string, role nodeconfigs.Node
 | 
			
		||||
		return env, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(uname) == 0 {
 | 
			
		||||
		return nil, errors.New("unable to execute 'uname -a' on this system")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	osName := ""
 | 
			
		||||
	archName := ""
 | 
			
		||||
	if strings.Contains(uname, "Darwin") {
 | 
			
		||||
 
 | 
			
		||||
@@ -97,6 +97,12 @@ func (this *NodeInstaller) Install(dir string, params interface{}, installStatus
 | 
			
		||||
	// 修改配置文件
 | 
			
		||||
	{
 | 
			
		||||
		configFile := dir + "/edge-node/configs/api.yaml"
 | 
			
		||||
 | 
			
		||||
		// sudo之后我们需要修改配置目录才能写入文件
 | 
			
		||||
		if this.client.sudo {
 | 
			
		||||
			_, _, _ = this.client.Exec("chown " + this.client.User() + " " + filepath.Dir(configFile))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var data = []byte(`rpc:
 | 
			
		||||
  endpoints: [ ${endpoints} ]
 | 
			
		||||
nodeId: "${nodeId}"
 | 
			
		||||
@@ -108,7 +114,7 @@ secret: "${nodeSecret}"`)
 | 
			
		||||
 | 
			
		||||
		_, err = this.client.WriteFile(configFile, data)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.New("write 'configs/api.yaml': " + err.Error())
 | 
			
		||||
			return errors.New("write '" + configFile + "': " + err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -97,6 +97,12 @@ func (this *NSNodeInstaller) Install(dir string, params interface{}, installStat
 | 
			
		||||
	// 修改配置文件
 | 
			
		||||
	{
 | 
			
		||||
		configFile := dir + "/edge-dns/configs/api.yaml"
 | 
			
		||||
 | 
			
		||||
		// sudo之后我们需要修改配置目录才能写入文件
 | 
			
		||||
		if this.client.sudo {
 | 
			
		||||
			_, _, _ = this.client.Exec("chown " + this.client.User() + " " + filepath.Dir(configFile))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var data = []byte(`rpc:
 | 
			
		||||
  endpoints: [ ${endpoints} ]
 | 
			
		||||
nodeId: "${nodeId}"
 | 
			
		||||
@@ -108,7 +114,7 @@ secret: "${nodeSecret}"`)
 | 
			
		||||
 | 
			
		||||
		_, err = this.client.WriteFile(configFile, data)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.New("write 'configs/api.yaml': " + err.Error())
 | 
			
		||||
			return errors.New("write '" + configFile + "': " + err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -133,24 +133,6 @@ func (this *NodeQueue) InstallNode(nodeId int64, installStatus *models.NodeInsta
 | 
			
		||||
		return errors.New("can not find user grant with id '" + numberutils.FormatInt64(loginParams.GrantId) + "'")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 安装目录
 | 
			
		||||
	installDir := node.InstallDir
 | 
			
		||||
	if len(installDir) == 0 {
 | 
			
		||||
		clusterId := node.ClusterId
 | 
			
		||||
		cluster, err := models.SharedNodeClusterDAO.FindEnabledNodeCluster(nil, int64(clusterId))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if cluster == nil {
 | 
			
		||||
			return errors.New("can not find cluster, ID:'" + fmt.Sprintf("%d", clusterId) + "'")
 | 
			
		||||
		}
 | 
			
		||||
		installDir = cluster.InstallDir
 | 
			
		||||
		if len(installDir) == 0 {
 | 
			
		||||
			// 默认是 $登录用户/edge-node
 | 
			
		||||
			installDir = "/" + grant.Username + "/edge-node"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// API终端
 | 
			
		||||
	apiNodes, err := models.SharedAPINodeDAO.FindAllEnabledAndOnAPINodes(nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -187,6 +169,7 @@ func (this *NodeQueue) InstallNode(nodeId int64, installStatus *models.NodeInsta
 | 
			
		||||
		PrivateKey: grant.PrivateKey,
 | 
			
		||||
		Passphrase: grant.Passphrase,
 | 
			
		||||
		Method:     grant.Method,
 | 
			
		||||
		Sudo:       grant.Su == 1,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		installStatus.ErrorCode = "SSH_LOGIN_FAILED"
 | 
			
		||||
@@ -196,6 +179,24 @@ func (this *NodeQueue) InstallNode(nodeId int64, installStatus *models.NodeInsta
 | 
			
		||||
		_ = installer.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// 安装目录
 | 
			
		||||
	installDir := node.InstallDir
 | 
			
		||||
	if len(installDir) == 0 {
 | 
			
		||||
		clusterId := node.ClusterId
 | 
			
		||||
		cluster, err := models.SharedNodeClusterDAO.FindEnabledNodeCluster(nil, int64(clusterId))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if cluster == nil {
 | 
			
		||||
			return errors.New("can not find cluster, ID:'" + fmt.Sprintf("%d", clusterId) + "'")
 | 
			
		||||
		}
 | 
			
		||||
		installDir = cluster.InstallDir
 | 
			
		||||
		if len(installDir) == 0 {
 | 
			
		||||
			// 默认是 $登录用户/edge-node
 | 
			
		||||
			installDir = installer.client.UserHome() + "/edge-node"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = installer.Install(installDir, params, installStatus)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
@@ -250,24 +251,6 @@ func (this *NodeQueue) StartNode(nodeId int64) error {
 | 
			
		||||
		return errors.New("can not find user grant with id '" + numberutils.FormatInt64(loginParams.GrantId) + "'")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 安装目录
 | 
			
		||||
	installDir := node.InstallDir
 | 
			
		||||
	if len(installDir) == 0 {
 | 
			
		||||
		clusterId := node.ClusterId
 | 
			
		||||
		cluster, err := models.SharedNodeClusterDAO.FindEnabledNodeCluster(nil, int64(clusterId))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if cluster == nil {
 | 
			
		||||
			return errors.New("can not find cluster, ID:'" + fmt.Sprintf("%d", clusterId) + "'")
 | 
			
		||||
		}
 | 
			
		||||
		installDir = cluster.InstallDir
 | 
			
		||||
		if len(installDir) == 0 {
 | 
			
		||||
			// 默认是 $登录用户/edge-node
 | 
			
		||||
			installDir = "/" + grant.Username + "/edge-node"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	installer := &NodeInstaller{}
 | 
			
		||||
	err = installer.Login(&Credentials{
 | 
			
		||||
		Host:       loginParams.Host,
 | 
			
		||||
@@ -277,6 +260,7 @@ func (this *NodeQueue) StartNode(nodeId int64) error {
 | 
			
		||||
		PrivateKey: grant.PrivateKey,
 | 
			
		||||
		Passphrase: grant.Passphrase,
 | 
			
		||||
		Method:     grant.Method,
 | 
			
		||||
		Sudo:       grant.Su == 1,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -286,16 +270,16 @@ func (this *NodeQueue) StartNode(nodeId int64) error {
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// 检查命令是否存在
 | 
			
		||||
	exeFile := installDir + "/edge-node/bin/edge-node"
 | 
			
		||||
	_, err = installer.client.Stat(exeFile)
 | 
			
		||||
	exe, err := this.lookupNodeExe(node, installer.client)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("edge node is not installed correctly, can not find executable file: " + exeFile)
 | 
			
		||||
		return errors.New("edge node was not installed correctly, can not find executable file")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 我们先尝试Systemd启动
 | 
			
		||||
	_, _, _ = installer.client.Exec("systemctl start edge-node")
 | 
			
		||||
 | 
			
		||||
	_, stderr, err := installer.client.Exec(exeFile + " start")
 | 
			
		||||
	// 执行start
 | 
			
		||||
	_, stderr, err := installer.client.Exec("sudo " + exe + " start")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("start failed: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
@@ -356,24 +340,6 @@ func (this *NodeQueue) StopNode(nodeId int64) error {
 | 
			
		||||
		return errors.New("can not find user grant with id '" + numberutils.FormatInt64(loginParams.GrantId) + "'")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 安装目录
 | 
			
		||||
	installDir := node.InstallDir
 | 
			
		||||
	if len(installDir) == 0 {
 | 
			
		||||
		clusterId := node.ClusterId
 | 
			
		||||
		cluster, err := models.SharedNodeClusterDAO.FindEnabledNodeCluster(nil, int64(clusterId))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if cluster == nil {
 | 
			
		||||
			return errors.New("can not find cluster, ID:'" + fmt.Sprintf("%d", clusterId) + "'")
 | 
			
		||||
		}
 | 
			
		||||
		installDir = cluster.InstallDir
 | 
			
		||||
		if len(installDir) == 0 {
 | 
			
		||||
			// 默认是 $登录用户/edge-node
 | 
			
		||||
			installDir = "/" + grant.Username + "/edge-node"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	installer := &NodeInstaller{}
 | 
			
		||||
	err = installer.Login(&Credentials{
 | 
			
		||||
		Host:       loginParams.Host,
 | 
			
		||||
@@ -383,6 +349,7 @@ func (this *NodeQueue) StopNode(nodeId int64) error {
 | 
			
		||||
		PrivateKey: grant.PrivateKey,
 | 
			
		||||
		Passphrase: grant.Passphrase,
 | 
			
		||||
		Method:     grant.Method,
 | 
			
		||||
		Sudo:       grant.Su == 1,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -392,16 +359,16 @@ func (this *NodeQueue) StopNode(nodeId int64) error {
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// 检查命令是否存在
 | 
			
		||||
	exeFile := installDir + "/edge-node/bin/edge-node"
 | 
			
		||||
	_, err = installer.client.Stat(exeFile)
 | 
			
		||||
	exe, err := this.lookupNodeExe(node, installer.client)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("edge node is not installed correctly, can not find executable file: " + exeFile)
 | 
			
		||||
		return errors.New("edge node was not installed correctly, can not find executable file")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 我们先尝试Systemd停止
 | 
			
		||||
	_, _, _ = installer.client.Exec("systemctl stop edge-node")
 | 
			
		||||
 | 
			
		||||
	_, stderr, err := installer.client.Exec(exeFile + " stop")
 | 
			
		||||
	// 执行stop
 | 
			
		||||
	_, stderr, err := installer.client.Exec(exe + " stop")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("stop failed: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
@@ -411,3 +378,38 @@ func (this *NodeQueue) StopNode(nodeId int64) error {
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *NodeQueue) lookupNodeExe(node *models.Node, client *SSHClient) (string, error) {
 | 
			
		||||
	// 安装目录
 | 
			
		||||
	var nodeDirs = []string{}
 | 
			
		||||
	if len(node.InstallDir) > 0 {
 | 
			
		||||
		nodeDirs = append(nodeDirs, node.InstallDir)
 | 
			
		||||
	}
 | 
			
		||||
	clusterId := node.ClusterId
 | 
			
		||||
	cluster, err := models.SharedNodeClusterDAO.FindEnabledNodeCluster(nil, int64(clusterId))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if cluster == nil {
 | 
			
		||||
		return "", errors.New("can not find cluster, ID:'" + fmt.Sprintf("%d", clusterId) + "'")
 | 
			
		||||
	}
 | 
			
		||||
	if len(cluster.InstallDir) > 0 {
 | 
			
		||||
		nodeDirs = append(nodeDirs, cluster.InstallDir)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 默认是 $登录用户/edge-node
 | 
			
		||||
	nodeDirs = append(nodeDirs, client.UserHome()+"/edge-node")
 | 
			
		||||
 | 
			
		||||
	// edge-boot安装目录
 | 
			
		||||
	nodeDirs = append(nodeDirs, "/usr/local/goedge")
 | 
			
		||||
 | 
			
		||||
	for _, dir := range nodeDirs {
 | 
			
		||||
		var path = dir + "/edge-node/bin/edge-node"
 | 
			
		||||
		_, err := client.sftp.Stat(path)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			return path, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return "", nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestQueue_InstallNode(t *testing.T) {
 | 
			
		||||
	queue := NewQueue()
 | 
			
		||||
	queue := NewNodeQueue()
 | 
			
		||||
	err := queue.InstallNodeProcess(16, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
 
 | 
			
		||||
@@ -133,24 +133,6 @@ func (this *NSNodeQueue) InstallNode(nodeId int64, installStatus *models.NodeIns
 | 
			
		||||
		return errors.New("can not find user grant with id '" + numberutils.FormatInt64(loginParams.GrantId) + "'")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 安装目录
 | 
			
		||||
	installDir := node.InstallDir
 | 
			
		||||
	if len(installDir) == 0 {
 | 
			
		||||
		clusterId := node.ClusterId
 | 
			
		||||
		cluster, err := models.SharedNSClusterDAO.FindEnabledNSCluster(nil, int64(clusterId))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if cluster == nil {
 | 
			
		||||
			return errors.New("can not find cluster, ID:'" + fmt.Sprintf("%d", clusterId) + "'")
 | 
			
		||||
		}
 | 
			
		||||
		installDir = cluster.InstallDir
 | 
			
		||||
		if len(installDir) == 0 {
 | 
			
		||||
			// 默认是 $登录用户/edge-dns
 | 
			
		||||
			installDir = "/" + grant.Username + "/edge-dns"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// API终端
 | 
			
		||||
	apiNodes, err := models.SharedAPINodeDAO.FindAllEnabledAndOnAPINodes(nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -187,6 +169,7 @@ func (this *NSNodeQueue) InstallNode(nodeId int64, installStatus *models.NodeIns
 | 
			
		||||
		PrivateKey: grant.PrivateKey,
 | 
			
		||||
		Passphrase: grant.Passphrase,
 | 
			
		||||
		Method:     grant.Method,
 | 
			
		||||
		Sudo:       grant.Su == 1,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		installStatus.ErrorCode = "SSH_LOGIN_FAILED"
 | 
			
		||||
@@ -196,6 +179,24 @@ func (this *NSNodeQueue) InstallNode(nodeId int64, installStatus *models.NodeIns
 | 
			
		||||
		_ = installer.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// 安装目录
 | 
			
		||||
	installDir := node.InstallDir
 | 
			
		||||
	if len(installDir) == 0 {
 | 
			
		||||
		clusterId := node.ClusterId
 | 
			
		||||
		cluster, err := models.SharedNSClusterDAO.FindEnabledNSCluster(nil, int64(clusterId))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if cluster == nil {
 | 
			
		||||
			return errors.New("can not find cluster, ID:'" + fmt.Sprintf("%d", clusterId) + "'")
 | 
			
		||||
		}
 | 
			
		||||
		installDir = cluster.InstallDir
 | 
			
		||||
		if len(installDir) == 0 {
 | 
			
		||||
			// 默认是 $登录用户/edge-dns
 | 
			
		||||
			installDir = installer.client.UserHome() + "/edge-dns"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = installer.Install(installDir, params, installStatus)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
@@ -250,6 +251,24 @@ func (this *NSNodeQueue) StartNode(nodeId int64) error {
 | 
			
		||||
		return errors.New("can not find user grant with id '" + numberutils.FormatInt64(loginParams.GrantId) + "'")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	installer := &NSNodeInstaller{}
 | 
			
		||||
	err = installer.Login(&Credentials{
 | 
			
		||||
		Host:       loginParams.Host,
 | 
			
		||||
		Port:       loginParams.Port,
 | 
			
		||||
		Username:   grant.Username,
 | 
			
		||||
		Password:   grant.Password,
 | 
			
		||||
		PrivateKey: grant.PrivateKey,
 | 
			
		||||
		Passphrase: grant.Passphrase,
 | 
			
		||||
		Method:     grant.Method,
 | 
			
		||||
		Sudo:       grant.Su == 1,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = installer.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// 安装目录
 | 
			
		||||
	installDir := node.InstallDir
 | 
			
		||||
	if len(installDir) == 0 {
 | 
			
		||||
@@ -264,27 +283,10 @@ func (this *NSNodeQueue) StartNode(nodeId int64) error {
 | 
			
		||||
		installDir = cluster.InstallDir
 | 
			
		||||
		if len(installDir) == 0 {
 | 
			
		||||
			// 默认是 $登录用户/edge-dns
 | 
			
		||||
			installDir = "/" + grant.Username + "/edge-dns"
 | 
			
		||||
			installDir = installer.client.UserHome() + "/edge-dns"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	installer := &NSNodeInstaller{}
 | 
			
		||||
	err = installer.Login(&Credentials{
 | 
			
		||||
		Host:       loginParams.Host,
 | 
			
		||||
		Port:       loginParams.Port,
 | 
			
		||||
		Username:   grant.Username,
 | 
			
		||||
		Password:   grant.Password,
 | 
			
		||||
		PrivateKey: grant.PrivateKey,
 | 
			
		||||
		Passphrase: grant.Passphrase,
 | 
			
		||||
		Method:     grant.Method,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = installer.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// 检查命令是否存在
 | 
			
		||||
	exeFile := installDir + "/edge-dns/bin/edge-dns"
 | 
			
		||||
	_, err = installer.client.Stat(exeFile)
 | 
			
		||||
@@ -356,6 +358,24 @@ func (this *NSNodeQueue) StopNode(nodeId int64) error {
 | 
			
		||||
		return errors.New("can not find user grant with id '" + numberutils.FormatInt64(loginParams.GrantId) + "'")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	installer := &NSNodeInstaller{}
 | 
			
		||||
	err = installer.Login(&Credentials{
 | 
			
		||||
		Host:       loginParams.Host,
 | 
			
		||||
		Port:       loginParams.Port,
 | 
			
		||||
		Username:   grant.Username,
 | 
			
		||||
		Password:   grant.Password,
 | 
			
		||||
		PrivateKey: grant.PrivateKey,
 | 
			
		||||
		Passphrase: grant.Passphrase,
 | 
			
		||||
		Method:     grant.Method,
 | 
			
		||||
		Sudo:       grant.Su == 1,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = installer.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// 安装目录
 | 
			
		||||
	installDir := node.InstallDir
 | 
			
		||||
	if len(installDir) == 0 {
 | 
			
		||||
@@ -370,27 +390,10 @@ func (this *NSNodeQueue) StopNode(nodeId int64) error {
 | 
			
		||||
		installDir = cluster.InstallDir
 | 
			
		||||
		if len(installDir) == 0 {
 | 
			
		||||
			// 默认是 $登录用户/edge-dns
 | 
			
		||||
			installDir = "/" + grant.Username + "/edge-dns"
 | 
			
		||||
			installDir = installer.client.UserHome() + "/edge-dns"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	installer := &NSNodeInstaller{}
 | 
			
		||||
	err = installer.Login(&Credentials{
 | 
			
		||||
		Host:       loginParams.Host,
 | 
			
		||||
		Port:       loginParams.Port,
 | 
			
		||||
		Username:   grant.Username,
 | 
			
		||||
		Password:   grant.Password,
 | 
			
		||||
		PrivateKey: grant.PrivateKey,
 | 
			
		||||
		Passphrase: grant.Passphrase,
 | 
			
		||||
		Method:     grant.Method,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = installer.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// 检查命令是否存在
 | 
			
		||||
	exeFile := installDir + "/edge-dns/bin/edge-dns"
 | 
			
		||||
	_, err = installer.client.Stat(exeFile)
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,9 @@ import (
 | 
			
		||||
type SSHClient struct {
 | 
			
		||||
	raw  *ssh.Client
 | 
			
		||||
	sftp *sftp.Client
 | 
			
		||||
 | 
			
		||||
	sudo         bool
 | 
			
		||||
	sudoPassword string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewSSHClient(raw *ssh.Client) (*SSHClient, error) {
 | 
			
		||||
@@ -30,8 +33,18 @@ func NewSSHClient(raw *ssh.Client) (*SSHClient, error) {
 | 
			
		||||
	return c, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Sudo 设置使用Sudo
 | 
			
		||||
func (this *SSHClient) Sudo(password string) {
 | 
			
		||||
	this.sudo = true
 | 
			
		||||
	this.sudoPassword = password
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Exec 执行shell命令
 | 
			
		||||
func (this *SSHClient) Exec(cmd string) (stdout string, stderr string, err error) {
 | 
			
		||||
	if this.raw.User() != "root" && this.sudo {
 | 
			
		||||
		return this.execSudo(cmd, this.sudoPassword)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	session, err := this.raw.NewSession()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", "", err
 | 
			
		||||
@@ -40,8 +53,8 @@ func (this *SSHClient) Exec(cmd string) (stdout string, stderr string, err error
 | 
			
		||||
		_ = session.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	stdoutBuf := bytes.NewBuffer([]byte{})
 | 
			
		||||
	stderrBuf := bytes.NewBuffer([]byte{})
 | 
			
		||||
	var stdoutBuf = &bytes.Buffer{}
 | 
			
		||||
	var stderrBuf = &bytes.Buffer{}
 | 
			
		||||
	session.Stdout = stdoutBuf
 | 
			
		||||
	session.Stderr = stderrBuf
 | 
			
		||||
	err = session.Run(cmd)
 | 
			
		||||
@@ -51,6 +64,79 @@ func (this *SSHClient) Exec(cmd string) (stdout string, stderr string, err error
 | 
			
		||||
	return strings.TrimRight(stdoutBuf.String(), "\n"), stderrBuf.String(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// execSudo 使用sudo执行shell命令
 | 
			
		||||
func (this *SSHClient) execSudo(cmd string, password string) (stdout string, stderr string, err error) {
 | 
			
		||||
	session, err := this.raw.NewSession()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", "", err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = session.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	modes := ssh.TerminalModes{
 | 
			
		||||
		ssh.ECHO:          0, // disable echo
 | 
			
		||||
		ssh.TTY_OP_ISPEED: 14400,
 | 
			
		||||
		ssh.TTY_OP_OSPEED: 14400,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = session.RequestPty("xterm", 80, 40, modes)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var stderrBuf = &bytes.Buffer{}
 | 
			
		||||
	session.Stderr = stderrBuf
 | 
			
		||||
 | 
			
		||||
	pipeIn, err := session.StdinPipe()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pipeOut, err := session.StdoutPipe()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var resultErr error
 | 
			
		||||
	var stdoutBuf = bytes.NewBuffer([]byte{})
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		var buf = make([]byte, 512)
 | 
			
		||||
		for {
 | 
			
		||||
			n, err := pipeOut.Read(buf)
 | 
			
		||||
			if n > 0 {
 | 
			
		||||
				if strings.Contains(string(buf[:n]), "[sudo] password for") {
 | 
			
		||||
					_, err = pipeIn.Write([]byte(password + "\n"))
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						resultErr = err
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				stdoutBuf.Write(buf[:n])
 | 
			
		||||
			}
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	err = session.Run("sudo " + cmd)
 | 
			
		||||
 | 
			
		||||
	stdout = strings.TrimSpace(stdoutBuf.String())
 | 
			
		||||
	stderr = strings.TrimSpace(stderrBuf.String())
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return stdout, stderr, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if resultErr != nil {
 | 
			
		||||
		return stdout, stderr, resultErr
 | 
			
		||||
	}
 | 
			
		||||
	return stdout, stderr, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *SSHClient) Listen(network string, addr string) (net.Listener, error) {
 | 
			
		||||
	return this.raw.Listen(network, addr)
 | 
			
		||||
}
 | 
			
		||||
@@ -152,3 +238,31 @@ func (this *SSHClient) WriteFile(path string, data []byte) (n int, err error) {
 | 
			
		||||
func (this *SSHClient) Remove(path string) error {
 | 
			
		||||
	return this.sftp.Remove(path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// User 用户名
 | 
			
		||||
func (this *SSHClient) User() string {
 | 
			
		||||
	return this.raw.User()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UserHome 用户地址
 | 
			
		||||
func (this *SSHClient) UserHome() string {
 | 
			
		||||
	homeStdout, _, err := this.Exec("echo $HOME")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return this.defaultUserHome()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var home = strings.TrimSpace(homeStdout)
 | 
			
		||||
	if len(home) > 0 {
 | 
			
		||||
		return home
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return this.defaultUserHome()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *SSHClient) defaultUserHome() string {
 | 
			
		||||
	var user = this.raw.User()
 | 
			
		||||
	if user == "root" {
 | 
			
		||||
		return "/root"
 | 
			
		||||
	}
 | 
			
		||||
	return "/home/" + user
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										82
									
								
								internal/installers/ssh_client_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								internal/installers/ssh_client_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
 | 
			
		||||
 | 
			
		||||
package installers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"golang.org/x/crypto/ssh"
 | 
			
		||||
	"net"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func testSSHClient(t *testing.T, username string, password string) *SSHClient {
 | 
			
		||||
	methods := []ssh.AuthMethod{}
 | 
			
		||||
	{
 | 
			
		||||
		authMethod := ssh.Password(password)
 | 
			
		||||
		methods = append(methods, authMethod)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	{
 | 
			
		||||
		authMethod := ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
 | 
			
		||||
			if len(questions) == 0 {
 | 
			
		||||
				return []string{}, nil
 | 
			
		||||
			}
 | 
			
		||||
			return []string{password}, nil
 | 
			
		||||
		})
 | 
			
		||||
		methods = append(methods, authMethod)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	config := &ssh.ClientConfig{
 | 
			
		||||
		User: username,
 | 
			
		||||
		Auth: methods,
 | 
			
		||||
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Timeout: 5 * time.Second,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sshClient, err := ssh.Dial("tcp", "192.168.2.31:22", config)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := NewSSHClient(sshClient)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	return client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSSHClient_Home(t *testing.T) {
 | 
			
		||||
	var client = testSSHClient(t, "root", "123456")
 | 
			
		||||
	t.Log(client.UserHome())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSSHClient_Exec(t *testing.T) {
 | 
			
		||||
	var client = testSSHClient(t, "liuxiangchao", "123456")
 | 
			
		||||
	stdout, stderr, err := client.Exec("echo 'Hello'")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	t.Log("stdout:", stdout, "stderr:", stderr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSSHClient_SudoExec(t *testing.T) {
 | 
			
		||||
	var client = testSSHClient(t, "liuxiangchao", "123456")
 | 
			
		||||
	client.Sudo("123456")
 | 
			
		||||
	stdout, stderr, err := client.Exec("echo 'Hello'")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	t.Log("stdout:", stdout, "stderr:", stderr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSSHClient_SudoExec2(t *testing.T) {
 | 
			
		||||
	var client = testSSHClient(t, "liuxiangchao", "123456")
 | 
			
		||||
	client.Sudo("123456")
 | 
			
		||||
	stdout, stderr, err := client.Exec("/home/liuxiangchao/edge-node/edge-node/bin/edge-node start")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	t.Log("stdout:", stdout, "stderr:", stderr)
 | 
			
		||||
}
 | 
			
		||||
@@ -26,7 +26,7 @@ func (this *NodeGrantService) CreateNodeGrant(ctx context.Context, req *pb.Creat
 | 
			
		||||
 | 
			
		||||
	tx := this.NullTx()
 | 
			
		||||
 | 
			
		||||
	grantId, err := models.SharedNodeGrantDAO.CreateGrant(tx, adminId, req.Name, req.Method, req.Username, req.Password, req.PrivateKey, req.Passphrase, req.Description, req.NodeId)
 | 
			
		||||
	grantId, err := models.SharedNodeGrantDAO.CreateGrant(tx, adminId, req.Name, req.Method, req.Username, req.Password, req.PrivateKey, req.Passphrase, req.Description, req.NodeId, req.Su)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -48,7 +48,7 @@ func (this *NodeGrantService) UpdateNodeGrant(ctx context.Context, req *pb.Updat
 | 
			
		||||
 | 
			
		||||
	tx := this.NullTx()
 | 
			
		||||
 | 
			
		||||
	err = models.SharedNodeGrantDAO.UpdateGrant(tx, req.NodeGrantId, req.Name, req.Method, req.Username, req.Password, req.PrivateKey, req.Passphrase, req.Description, req.NodeId)
 | 
			
		||||
	err = models.SharedNodeGrantDAO.UpdateGrant(tx, req.NodeGrantId, req.Name, req.Method, req.Username, req.Password, req.PrivateKey, req.Passphrase, req.Description, req.NodeId, req.Su)
 | 
			
		||||
	return this.Success()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -59,6 +59,9 @@ var upgradeFuncs = []*upgradeVersion{
 | 
			
		||||
	{
 | 
			
		||||
		"0.3.3", upgradeV0_3_3,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"0.3.7", upgradeV0_3_7,
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpgradeSQLData 升级SQL数据
 | 
			
		||||
@@ -532,3 +535,13 @@ func upgradeV0_3_3(db *dbs.DB) error {
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// v0.3.7
 | 
			
		||||
func upgradeV0_3_7(db *dbs.DB) error {
 | 
			
		||||
	// 修改所有edgeNodeGrants中的su为0
 | 
			
		||||
	_, err := db.Exec("UPDATE edgeNodeGrants SET su=0 WHERE su=1")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -21,7 +21,6 @@ func TestUpgradeSQLData(t *testing.T) {
 | 
			
		||||
	t.Log("ok")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func TestUpgradeSQLData_v0_3_1(t *testing.T) {
 | 
			
		||||
	db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
 | 
			
		||||
		Driver: "mysql",
 | 
			
		||||
@@ -69,3 +68,19 @@ func TestUpgradeSQLData_v0_3_3(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
	t.Log("ok")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUpgradeSQLData_v0_3_7(t *testing.T) {
 | 
			
		||||
	db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
 | 
			
		||||
		Driver: "mysql",
 | 
			
		||||
		Dsn:    "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
 | 
			
		||||
		Prefix: "edge",
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	err = upgradeV0_3_7(db)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	t.Log("ok")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user