1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
// Copyright 2020 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package executor
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/joomcode/errorx"
"github.com/pingcap/errors"
"github.com/pingcap/tiup/pkg/cluster/ctxt"
)
// SSHType represent the type of the channel used by ssh
type SSHType string
var (
errNS = errorx.NewNamespace("executor")
// SSHTypeBuiltin is the type of easy ssh executor
// SSHTypeBuiltin SSHType = "builtin" // Removed: replaced by system SSH or native Go SSH client for proxying
// SSHTypeSystem is the type of host ssh client
SSHTypeSystem SSHType = "system"
// SSHTypeNone is the type of local executor (no ssh will be used)
SSHTypeNone SSHType = "none"
executeDefaultTimeout = time.Second * 60
// This command will be execute once the NativeSSHExecutor is created.
// It's used to predict if the connection can establish success in the future.
// Its main purpose is to avoid sshpass hang when user specified a wrong prompt.
connectionTestCommand = "echo connection test, if killed, check the password prompt"
// SSH authorized_keys file
defaultSSHAuthorizedKeys = "~/.ssh/authorized_keys"
)
// New create a new Executor
func New(etype SSHType, sudo bool, c SSHConfig) (ctxt.Executor, error) {
if etype == "" || etype == "builtin" { // Treat old "builtin" as "system"
etype = SSHTypeSystem
}
// set default values
if c.Port <= 0 {
c.Port = 22
}
if c.Timeout == 0 {
c.Timeout = time.Second * 5 // default timeout is 5 sec
}
var executor ctxt.Executor
switch etype {
case SSHTypeSystem:
e := &NativeSSHExecutor{
Config: &c,
Locale: "C",
Sudo: sudo,
}
if c.Password != "" || (c.KeyFile != "" && c.Passphrase != "") {
_, _, e.ConnectionTestResult = e.Execute(context.Background(), connectionTestCommand, false, executeDefaultTimeout)
}
executor = e
case SSHTypeNone:
if err := checkLocalIP(c.Host); err != nil {
return nil, err
}
e := &Local{
Config: &c,
Sudo: sudo,
Locale: "C",
}
executor = e
default:
return nil, errors.Errorf("unregistered executor: %s", etype)
}
return &CheckPointExecutor{executor, &c}, nil
}
// UnwarpCheckPointExecutor unwarp the CheckPointExecutor and return the real executor
//
// Sometimes we just want to get the output of a command, and the CheckPointExecutor will
// always cache the output, it will be a problem when we want to get the real output.
func UnwarpCheckPointExecutor(e ctxt.Executor) ctxt.Executor {
switch e := e.(type) {
case *CheckPointExecutor:
return e.Executor
default:
return e
}
}
func checkLocalIP(ip string) error {
ifaces, err := net.Interfaces()
if err != nil {
return errors.AddStack(err)
}
foundIps := []string{}
for _, i := range ifaces {
addrs, err := i.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
switch v := addr.(type) {
case *net.IPNet:
if ip == v.IP.String() {
return nil
}
foundIps = append(foundIps, v.IP.String())
case *net.IPAddr:
if ip == v.IP.String() {
return nil
}
foundIps = append(foundIps, v.IP.String())
}
}
}
return fmt.Errorf("address %s not found in all interfaces, found ips: %s", ip, strings.Join(foundIps, ","))
}
// FindSSHAuthorizedKeysFile finds the correct path of SSH authorized keys file
func FindSSHAuthorizedKeysFile(ctx context.Context, exec ctxt.Executor) string {
// detect if custom path of authorized keys file is set
// NOTE: we do not yet support:
// - custom config for user (~/.ssh/config)
// - sshd started with custom config (other than /etc/ssh/sshd_config)
// - ssh server implementations other than OpenSSH (such as dropbear)
sshAuthorizedKeys := defaultSSHAuthorizedKeys
cmd := "grep -Ev '^\\s*#|^\\s*$' /etc/ssh/sshd_config"
stdout, _, _ := exec.Execute(ctx, cmd, true) // error ignored as we have default value
for line := range strings.SplitSeq(string(stdout), "\n") {
if !strings.Contains(line, "AuthorizedKeysFile") {
continue
}
fields := strings.Fields(line)
if len(fields) >= 2 {
sshAuthorizedKeys = fields[1]
break
}
}
if !strings.HasPrefix(sshAuthorizedKeys, "/") && !strings.HasPrefix(sshAuthorizedKeys, "~") {
sshAuthorizedKeys = fmt.Sprintf("~/%s", sshAuthorizedKeys)
}
return sshAuthorizedKeys
}
|