// Package raw wraps SSH commands necessary for K8s inspection. package raw import ( "bytes" "fmt" "io/ioutil" "os/user" "path/filepath" "golang.org/x/crypto/ssh" kh "golang.org/x/crypto/ssh/knownhosts" "check" "check/config" ) const ( controlplane = "controlplane" etcd = "etcd" worker = "worker" knownHostsFile = "~/.ssh/known_hosts" ) // Raw implements Informer interface. type Raw struct { check.Informer } // GetAPIParams returns parameters of running Kubernetes API servers. // It queries only cluster nodes with "controlplane" role. func (r *Raw) GetAPIParams() ([]string, error) { return getProcessParams(check.APIProcess) } func getProcessParams(process check.Command) ([]string, error) { nodes, err := config.GetNodesInfo() if err != nil { return []string{}, err } for _, node := range nodes { if isControlplaneNode(node.Role) { cmd, err := getInspectCmdOutput(node, process) if err != nil { return []string{}, err } if len(cmd) > 0 { i := bytes.Index(cmd, []byte(process.String())) if i == -1 { return []string{}, fmt.Errorf("missing %s command", process) } return btos(cmd[i+len(process.String()):]), nil } } } return []string{}, nil } func isControlplaneNode(roles []string) bool { for _, role := range roles { if role == controlplane { return true } } return false } func getInspectCmdOutput(node config.NodeInfo, cmd check.Command) ([]byte, error) { path, err := expandPath(node.SSHKeyPath) if err != nil { return nil, err } pubKey, err := parsePublicKey(path) if err != nil { return nil, err } khPath, err := expandPath(knownHostsFile) if err != nil { return nil, err } hostKeyCallback, err := kh.New(khPath) if err != nil { return nil, err } config := &ssh.ClientConfig{ User: node.User, Auth: []ssh.AuthMethod{pubKey}, HostKeyCallback: hostKeyCallback, } conn, err := ssh.Dial("tcp", node.Address+":"+node.Port, config) if err != nil { return nil, err } defer conn.Close() out, err := runCommand(fmt.Sprintf("docker inspect %s --format {{.Args}}", cmd), conn) if err != nil { return nil, err } return out, nil } func expandPath(path string) (string, error) { if len(path) == 0 || path[0] != '~' { return path, nil } usr, err := user.Current() if err != nil { return "", err } return filepath.Join(usr.HomeDir, path[1:]), nil } func parsePublicKey(path string) (ssh.AuthMethod, error) { key, err := ioutil.ReadFile(path) if err != nil { return nil, err } signer, err := ssh.ParsePrivateKey(key) if err != nil { return nil, err } return ssh.PublicKeys(signer), nil } func runCommand(cmd string, conn *ssh.Client) ([]byte, error) { sess, err := conn.NewSession() if err != nil { return nil, err } defer sess.Close() out, err := sess.Output(cmd) if err != nil { return nil, err } return out, nil } // btos converts slice of bytes to slice of strings split by white space characters. func btos(in []byte) []string { var out []string for _, b := range bytes.Fields(in) { out = append(out, string(b)) } return out }