k8s: Add scheduler information collection
[integration.git] / test / security / k8s / src / check / raw / raw.go
1 // Package raw wraps SSH commands necessary for K8s inspection.
2 package raw
3
4 import (
5         "bytes"
6         "fmt"
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"
15         "check/config"
16 )
17
18 const (
19         controlplane = "controlplane"
20         etcd         = "etcd"
21         worker       = "worker"
22
23         knownHostsFile = "~/.ssh/known_hosts"
24 )
25
26 // Raw implements Informer interface.
27 type Raw struct {
28         check.Informer
29 }
30
31 // GetAPIParams returns parameters of running Kubernetes API servers.
32 // It queries only cluster nodes with "controlplane" role.
33 func (r *Raw) GetAPIParams() ([]string, error) {
34         return getProcessParams(check.APIProcess)
35 }
36
37 // GetSchedulerParams returns parameters of running Kubernetes scheduler.
38 // It queries only cluster nodes with "controlplane" role.
39 func (r *Raw) GetSchedulerParams() ([]string, error) {
40         return getProcessParams(check.SchedulerProcess)
41 }
42
43 func getProcessParams(process check.Command) ([]string, error) {
44         nodes, err := config.GetNodesInfo()
45         if err != nil {
46                 return []string{}, err
47         }
48
49         for _, node := range nodes {
50                 if isControlplaneNode(node.Role) {
51                         cmd, err := getInspectCmdOutput(node, process)
52                         if err != nil {
53                                 return []string{}, err
54                         }
55
56                         if len(cmd) > 0 {
57                                 i := bytes.Index(cmd, []byte(process.String()))
58                                 if i == -1 {
59                                         return []string{}, fmt.Errorf("missing %s command", process)
60                                 }
61                                 return btos(cmd[i+len(process.String()):]), nil
62                         }
63                 }
64         }
65
66         return []string{}, nil
67 }
68
69 func isControlplaneNode(roles []string) bool {
70         for _, role := range roles {
71                 if role == controlplane {
72                         return true
73                 }
74         }
75         return false
76 }
77
78 func getInspectCmdOutput(node config.NodeInfo, cmd check.Command) ([]byte, error) {
79         path, err := expandPath(node.SSHKeyPath)
80         if err != nil {
81                 return nil, err
82         }
83
84         pubKey, err := parsePublicKey(path)
85         if err != nil {
86                 return nil, err
87         }
88
89         khPath, err := expandPath(knownHostsFile)
90         if err != nil {
91                 return nil, err
92         }
93
94         hostKeyCallback, err := kh.New(khPath)
95         if err != nil {
96                 return nil, err
97         }
98
99         config := &ssh.ClientConfig{
100                 User:            node.User,
101                 Auth:            []ssh.AuthMethod{pubKey},
102                 HostKeyCallback: hostKeyCallback,
103         }
104
105         conn, err := ssh.Dial("tcp", node.Address+":"+node.Port, config)
106         if err != nil {
107                 return nil, err
108         }
109         defer conn.Close()
110
111         out, err := runCommand(fmt.Sprintf("docker inspect %s --format {{.Args}}", cmd), conn)
112         if err != nil {
113                 return nil, err
114         }
115         return out, nil
116 }
117
118 func expandPath(path string) (string, error) {
119         if len(path) == 0 || path[0] != '~' {
120                 return path, nil
121         }
122
123         usr, err := user.Current()
124         if err != nil {
125                 return "", err
126         }
127         return filepath.Join(usr.HomeDir, path[1:]), nil
128 }
129
130 func parsePublicKey(path string) (ssh.AuthMethod, error) {
131         key, err := ioutil.ReadFile(path)
132         if err != nil {
133                 return nil, err
134         }
135         signer, err := ssh.ParsePrivateKey(key)
136         if err != nil {
137                 return nil, err
138         }
139         return ssh.PublicKeys(signer), nil
140 }
141
142 func runCommand(cmd string, conn *ssh.Client) ([]byte, error) {
143         sess, err := conn.NewSession()
144         if err != nil {
145                 return nil, err
146         }
147         defer sess.Close()
148         out, err := sess.Output(cmd)
149         if err != nil {
150                 return nil, err
151         }
152         return out, nil
153 }
154
155 // btos converts slice of bytes to slice of strings split by white space characters.
156 func btos(in []byte) []string {
157         var out []string
158         for _, b := range bytes.Fields(in) {
159                 out = append(out, string(b))
160         }
161         return out
162 }