blob: 4efa1d4f8f1a5b1e49c10f7c31cd5371a2ba9ba5 [file] [log] [blame]
Pawel Wieczorek2b5b2e02019-08-06 16:04:53 +02001// Package raw wraps SSH commands necessary for K8s inspection.
2package raw
3
4import (
5 "bytes"
6 "errors"
7 "io/ioutil"
8 "os/user"
9 "path/filepath"
10
11 "golang.org/x/crypto/ssh"
12 kh "golang.org/x/crypto/ssh/knownhosts"
13
14 "check/config"
15)
16
17const (
18 controlplane = "controlplane"
19 etcd = "etcd"
20 worker = "worker"
21
22 k8sProcess = "kube-apiserver"
23 dockerInspectCmd = "docker inspect " + k8sProcess + " --format {{.Args}}"
24
25 knownHostsFile = "~/.ssh/known_hosts"
26)
27
28// GetK8sParams returns parameters of running Kubernetes API servers.
29// It queries only cluster nodes with "controlplane" role.
30func GetK8sParams() ([]string, error) {
31 nodes, err := config.GetNodesInfo()
32 if err != nil {
33 return []string{}, err
34 }
35
36 for _, node := range nodes {
37 if isControlplaneNode(node.Role) {
38 cmd, err := getK8sCmd(node)
39 if err != nil {
40 return []string{}, err
41 }
42
43 if len(cmd) > 0 {
44 i := bytes.Index(cmd, []byte(k8sProcess))
45 if i == -1 {
46 return []string{}, errors.New("missing " + k8sProcess + " command")
47 }
48 return btos(cmd[i+len(k8sProcess):]), nil
49 }
50 }
51 }
52
53 return []string{}, nil
54}
55
56func isControlplaneNode(roles []string) bool {
57 for _, role := range roles {
58 if role == controlplane {
59 return true
60 }
61 }
62 return false
63}
64
65func getK8sCmd(node config.NodeInfo) ([]byte, error) {
66 path, err := expandPath(node.SSHKeyPath)
67 if err != nil {
68 return nil, err
69 }
70
71 pubKey, err := parsePublicKey(path)
72 if err != nil {
73 return nil, err
74 }
75
76 khPath, err := expandPath(knownHostsFile)
77 if err != nil {
78 return nil, err
79 }
80
81 hostKeyCallback, err := kh.New(khPath)
82 if err != nil {
83 return nil, err
84 }
85
86 config := &ssh.ClientConfig{
87 User: node.User,
88 Auth: []ssh.AuthMethod{pubKey},
89 HostKeyCallback: hostKeyCallback,
90 }
91
92 conn, err := ssh.Dial("tcp", node.Address+":"+node.Port, config)
93 if err != nil {
94 return nil, err
95 }
96 defer conn.Close()
97
98 out, err := runCommand(dockerInspectCmd, conn)
99 if err != nil {
100 return nil, err
101 }
102 return out, nil
103}
104
105func expandPath(path string) (string, error) {
106 if len(path) == 0 || path[0] != '~' {
107 return path, nil
108 }
109
110 usr, err := user.Current()
111 if err != nil {
112 return "", err
113 }
114 return filepath.Join(usr.HomeDir, path[1:]), nil
115}
116
117func parsePublicKey(path string) (ssh.AuthMethod, error) {
118 key, err := ioutil.ReadFile(path)
119 if err != nil {
120 return nil, err
121 }
122 signer, err := ssh.ParsePrivateKey(key)
123 if err != nil {
124 return nil, err
125 }
126 return ssh.PublicKeys(signer), nil
127}
128
129func runCommand(cmd string, conn *ssh.Client) ([]byte, error) {
130 sess, err := conn.NewSession()
131 if err != nil {
132 return nil, err
133 }
134 defer sess.Close()
135 out, err := sess.Output(cmd)
136 if err != nil {
137 return nil, err
138 }
139 return out, nil
140}
141
142// btos converts slice of bytes to slice of strings split by white space characters.
143func btos(in []byte) []string {
144 var out []string
145 for _, b := range bytes.Fields(in) {
146 out = append(out, string(b))
147 }
148 return out
149}