2a9f0a17f53ae7c5a631bd8eb2a507acc39ae735
[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 func getProcessParams(process check.Command) ([]string, error) {
38         nodes, err := config.GetNodesInfo()
39         if err != nil {
40                 return []string{}, err
41         }
42
43         for _, node := range nodes {
44                 if isControlplaneNode(node.Role) {
45                         cmd, err := getInspectCmdOutput(node, process)
46                         if err != nil {
47                                 return []string{}, err
48                         }
49
50                         if len(cmd) > 0 {
51                                 i := bytes.Index(cmd, []byte(process.String()))
52                                 if i == -1 {
53                                         return []string{}, fmt.Errorf("missing %s command", process)
54                                 }
55                                 return btos(cmd[i+len(process.String()):]), nil
56                         }
57                 }
58         }
59
60         return []string{}, nil
61 }
62
63 func isControlplaneNode(roles []string) bool {
64         for _, role := range roles {
65                 if role == controlplane {
66                         return true
67                 }
68         }
69         return false
70 }
71
72 func getInspectCmdOutput(node config.NodeInfo, cmd check.Command) ([]byte, error) {
73         path, err := expandPath(node.SSHKeyPath)
74         if err != nil {
75                 return nil, err
76         }
77
78         pubKey, err := parsePublicKey(path)
79         if err != nil {
80                 return nil, err
81         }
82
83         khPath, err := expandPath(knownHostsFile)
84         if err != nil {
85                 return nil, err
86         }
87
88         hostKeyCallback, err := kh.New(khPath)
89         if err != nil {
90                 return nil, err
91         }
92
93         config := &ssh.ClientConfig{
94                 User:            node.User,
95                 Auth:            []ssh.AuthMethod{pubKey},
96                 HostKeyCallback: hostKeyCallback,
97         }
98
99         conn, err := ssh.Dial("tcp", node.Address+":"+node.Port, config)
100         if err != nil {
101                 return nil, err
102         }
103         defer conn.Close()
104
105         out, err := runCommand(fmt.Sprintf("docker inspect %s --format {{.Args}}", cmd), conn)
106         if err != nil {
107                 return nil, err
108         }
109         return out, nil
110 }
111
112 func expandPath(path string) (string, error) {
113         if len(path) == 0 || path[0] != '~' {
114                 return path, nil
115         }
116
117         usr, err := user.Current()
118         if err != nil {
119                 return "", err
120         }
121         return filepath.Join(usr.HomeDir, path[1:]), nil
122 }
123
124 func parsePublicKey(path string) (ssh.AuthMethod, error) {
125         key, err := ioutil.ReadFile(path)
126         if err != nil {
127                 return nil, err
128         }
129         signer, err := ssh.ParsePrivateKey(key)
130         if err != nil {
131                 return nil, err
132         }
133         return ssh.PublicKeys(signer), nil
134 }
135
136 func runCommand(cmd string, conn *ssh.Client) ([]byte, error) {
137         sess, err := conn.NewSession()
138         if err != nil {
139                 return nil, err
140         }
141         defer sess.Close()
142         out, err := sess.Output(cmd)
143         if err != nil {
144                 return nil, err
145         }
146         return out, nil
147 }
148
149 // btos converts slice of bytes to slice of strings split by white space characters.
150 func btos(in []byte) []string {
151         var out []string
152         for _, b := range bytes.Fields(in) {
153                 out = append(out, string(b))
154         }
155         return out
156 }