kube_auto_analyzer 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/kubeautoanalyzer +9 -11
- data/lib/kube_auto_analyzer/agent_checks/file_checks.rb +12 -12
- data/lib/kube_auto_analyzer/agent_checks/process_checks.rb +100 -90
- data/lib/kube_auto_analyzer/reporting.rb +78 -3
- data/lib/kube_auto_analyzer/utility/network.rb +15 -0
- data/lib/kube_auto_analyzer/version.rb +1 -1
- data/lib/kube_auto_analyzer/vuln_checks/api_server.rb +69 -0
- data/lib/kube_auto_analyzer/vuln_checks/kubelet.rb +69 -0
- data/lib/kube_auto_analyzer.rb +14 -6
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8eef7acfe04e5060eb6da88237f8e5f719815625
|
4
|
+
data.tar.gz: 6fc041eaefc06167d3bc444c278447d3f1109d06
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d98679c3423bf84bbcfd24420b119cb0a23c191f20aa251d5a708c4e41c000785d6fa9d9d20ff08bf06abeca4f07b3519d98f27b43df15d81c477d41f70a009f
|
7
|
+
data.tar.gz: 0b36c63d370ca496e80887ede180407552b923a27a63baab4ec51c251a529c3bad0e3f09cb3ece71f82eaf690dfeff83b0fb80ba7b20240d53e42811edd7f4a9
|
data/bin/kubeautoanalyzer
CHANGED
@@ -12,21 +12,23 @@
|
|
12
12
|
options.token = ''
|
13
13
|
options.token_file = ''
|
14
14
|
options.config_file = false
|
15
|
-
options.
|
16
|
-
|
15
|
+
options.agent_checks = false
|
16
|
+
|
17
17
|
|
18
18
|
opts = OptionParser.new do |opts|
|
19
19
|
opts.banner = "Kubernetes Auto Analyzer #{KubeAutoAnalyzer::VERSION}"
|
20
20
|
|
21
|
-
|
22
|
-
options.target_server = serv
|
23
|
-
end
|
21
|
+
|
24
22
|
|
25
23
|
#TODO: Need options for different authentication mechanisms
|
26
24
|
opts.on("-c", "--config [CONFIG]", "kubeconfig file to load") do |file|
|
27
25
|
options.config_file = file
|
28
26
|
end
|
29
27
|
|
28
|
+
opts.on("-s", "--server [SERVER]", "Target Server") do |serv|
|
29
|
+
options.target_server = serv
|
30
|
+
end
|
31
|
+
|
30
32
|
opts.on("-t", "--token [TOKEN]", "Bearer Token to Use") do |token|
|
31
33
|
options.token = token
|
32
34
|
end
|
@@ -43,12 +45,8 @@
|
|
43
45
|
options.report_directory = rep
|
44
46
|
end
|
45
47
|
|
46
|
-
opts.on("--
|
47
|
-
options.
|
48
|
-
end
|
49
|
-
|
50
|
-
opts.on("--processChecks","Carry out agent based process Checks (expermimental)") do |fc|
|
51
|
-
options.agent_process_checks = true
|
48
|
+
opts.on("--agentChecks","Carry out Agent Based Checks ") do |fc|
|
49
|
+
options.agent_checks = true
|
52
50
|
end
|
53
51
|
|
54
52
|
opts.on("-h", "--help", "-?", "--?", "Get Help") do |help|
|
@@ -29,20 +29,20 @@ module KubeAutoAnalyzer
|
|
29
29
|
pod.spec.containers[0].volumeMounts = [{mountPath: '/etc', name: 'etck8s'}]
|
30
30
|
pod.spec.containers[0].args = ["/file-checker.rb","/etc/kubernetes"]
|
31
31
|
pod.spec.nodeselector = {}
|
32
|
-
pod.spec.nodeselector['kubernetes.io/hostname'] = node_hostname
|
33
|
-
@client.create_pod(pod)
|
34
32
|
begin
|
35
|
-
|
36
|
-
|
37
|
-
|
33
|
+
pod.spec.nodeselector['kubernetes.io/hostname'] = node_hostname
|
34
|
+
@client.create_pod(pod)
|
35
|
+
begin
|
36
|
+
sleep(5) until @client.get_pod(container_name,"default")['status']['containerStatuses'][0]['state']['terminated']['reason'] == "Completed"
|
37
|
+
rescue
|
38
|
+
retry
|
39
|
+
end
|
40
|
+
files = JSON.parse(@client.get_pod_log(container_name,"default"))
|
41
|
+
|
42
|
+
@results[target]['worker_files'][node_hostname] = files
|
43
|
+
ensure
|
44
|
+
@client.delete_pod(container_name,"default")
|
38
45
|
end
|
39
|
-
files = JSON.parse(@client.get_pod_log(container_name,"default"))
|
40
|
-
#files.each do |file|
|
41
|
-
#Need to replace the mounted path with the real host path
|
42
|
-
# file[0].sub! "/hostetck8s", "/etc/kubernetes"
|
43
|
-
#end
|
44
|
-
@results[target]['worker_files'][node_hostname] = files
|
45
|
-
@client.delete_pod(container_name,"default")
|
46
46
|
|
47
47
|
end
|
48
48
|
@log.debug("Finished Worker File Check")
|
@@ -29,115 +29,125 @@ module KubeAutoAnalyzer
|
|
29
29
|
pod.spec.hostPID = true
|
30
30
|
pod.spec.nodeselector = {}
|
31
31
|
pod.spec.nodeselector['kubernetes.io/hostname'] = node_hostname
|
32
|
-
@client.create_pod(pod)
|
33
32
|
begin
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
33
|
+
@client.create_pod(pod)
|
34
|
+
begin
|
35
|
+
sleep(5) until @client.get_pod(container_name,"default")['status']['containerStatuses'][0]['state']['terminated']['reason'] == "Completed"
|
36
|
+
rescue
|
37
|
+
retry
|
38
|
+
end
|
39
|
+
processes = JSON.parse(@client.get_pod_log(container_name,"default"))
|
40
|
+
#If we didn't get more than one process, we're probably not reading the host ones
|
41
|
+
#So either it's a bug or we don't have rights
|
42
|
+
if processes.length < 2
|
43
|
+
@log.debug("Process Check failed didn't get the node process list")
|
44
|
+
@results[target]['kubelet_checks'][node_hostname]['Kubelet Not Found'] = "Error - couldn't see host process list"
|
45
|
+
@client.delete_pod(container_name,"default")
|
46
|
+
return
|
47
|
+
end
|
48
|
+
#puts processes
|
49
|
+
kubelet_proc = ''
|
50
|
+
processes.each do |proc|
|
51
|
+
if proc =~ /kubelet/
|
52
|
+
kubelet_proc = proc
|
53
|
+
end
|
54
|
+
end
|
55
|
+
@results[target]['kubelet_checks'][node_hostname] = Hash.new
|
56
|
+
unless kubelet_proc.length > 1
|
57
|
+
@results[target]['kubelet_checks'][node_hostname]['Kubelet Not Found'] = "Error"
|
58
|
+
@log.debug(processes)
|
59
|
+
@client.delete_pod(container_name,"default")
|
60
|
+
return
|
44
61
|
end
|
45
|
-
end
|
46
|
-
@results[target]['kubelet_checks'][node_hostname] = Hash.new
|
47
|
-
unless kubelet_proc.length > 1
|
48
|
-
@results[target]['kubelet_checks'][node_hostname]['Kubelet Not Found'] = "Error"
|
49
|
-
@log.debug(processes)
|
50
|
-
@client.delete_pod(container_name,"default")
|
51
|
-
return
|
52
|
-
end
|
53
62
|
|
54
|
-
|
55
|
-
|
63
|
+
@results[target]['node_evidence'][node_hostname] = Hash.new
|
64
|
+
@results[target]['node_evidence'][node_hostname]['kubelet'] = kubelet_proc
|
56
65
|
|
57
66
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
67
|
+
|
68
|
+
#Checks
|
69
|
+
unless kubelet_proc =~ /--allow-privileged=false/
|
70
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.1 - Ensure that the --allow-privileged argument is set to false'] = "Fail"
|
71
|
+
else
|
72
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.1 - Ensure that the --allow-privileged argument is set to false'] = "Pass"
|
73
|
+
end
|
65
74
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
75
|
+
unless kubelet_proc =~ /--anonymous-auth=false/
|
76
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.2 - Ensure that the --anonymous-auth argument is set to false'] = "Fail"
|
77
|
+
else
|
78
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.2 - Ensure that the --anonymous-auth argument is set to false'] = "Pass"
|
79
|
+
end
|
71
80
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
81
|
+
if kubelet_proc =~ /--authorization-mode\S*AlwaysAllow/
|
82
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.3 - Ensure that the --authorization-mode argument is not set to AlwaysAllow'] = "Fail"
|
83
|
+
else
|
84
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.3 - Ensure that the --authorization-mode argument is not set to AlwaysAllow'] = "Pass"
|
85
|
+
end
|
77
86
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
87
|
+
unless kubelet_proc =~ /--client-ca-file/
|
88
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.4 - Ensure that the --client-ca-file argument is set as appropriate'] = "Fail"
|
89
|
+
else
|
90
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.4 - Ensure that the --client-ca-file argument is set as appropriate'] = "Pass"
|
91
|
+
end
|
83
92
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
93
|
+
unless kubelet_proc =~ /--read-only-port=0/
|
94
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.5 - Ensure that the --read-only-port argument is set to 0'] = "Fail"
|
95
|
+
else
|
96
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.5 - Ensure that the --read-only-port argument is set to 0'] = "Pass"
|
97
|
+
end
|
89
98
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
99
|
+
if kubelet_proc =~ /--streaming-connection-idle-timeout=0/
|
100
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.6 - Ensure that the --streaming-connection-idle-timeout argument is not set to 0'] = "Fail"
|
101
|
+
else
|
102
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.6 - Ensure that the --streaming-connection-idle-timeout argument is not set to 0'] = "Pass"
|
103
|
+
end
|
95
104
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
105
|
+
unless kubelet_proc =~ /--protect-kernel-defaults=true/
|
106
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.7 - Ensure that the --protect-kernel-defaults argument is set to true'] = "Fail"
|
107
|
+
else
|
108
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.7 - Ensure that the --protect-kernel-defaults argument is set to true'] = "Pass"
|
109
|
+
end
|
101
110
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
111
|
+
if kubelet_proc =~ /--make-iptables-util-chains=false/
|
112
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.8 - Ensure that the --make-iptables-util-chains argument is set to true'] = "Fail"
|
113
|
+
else
|
114
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.8 - Ensure that the --make-iptables-util-chains argument is set to true'] = "Pass"
|
115
|
+
end
|
107
116
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
117
|
+
unless kubelet_proc =~ /--keep-terminated-pod-volumes=false/
|
118
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.9 - that the --keep-terminated-pod-volumes argument is set to false'] = "Fail"
|
119
|
+
else
|
120
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.9 - Ensure that the --keep-terminated-pod-volumes argument is set to false'] = "Pass"
|
121
|
+
end
|
113
122
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
123
|
+
if kubelet_proc =~ /--hostname-override/
|
124
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.10 - Ensure that the --hostname-override argument is not set'] = "Fail"
|
125
|
+
else
|
126
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.10 - Ensure that the --hostname-override argument is not set'] = "Pass"
|
127
|
+
end
|
119
128
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
129
|
+
unless kubelet_proc =~ /--event-qps=0/
|
130
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.11 - Ensure that the --event-qps argument is set to 0'] = "Fail"
|
131
|
+
else
|
132
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.11 - Ensure that the --event-qps argument is set to 0'] = "Pass"
|
133
|
+
end
|
125
134
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
135
|
+
unless (kubelet_proc =~ /--tls-cert-file/) && (kubelet_proc =~ /--tls-private-key-file/)
|
136
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.12 - Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate'] = "Fail"
|
137
|
+
else
|
138
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.12 - Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate'] = "Pass"
|
139
|
+
end
|
131
140
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
141
|
+
unless kubelet_proc =~ /--cadvisor-port=0/
|
142
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.13 - Ensure that the --cadvisor-port argument is set to 0'] = "Fail"
|
143
|
+
else
|
144
|
+
@results[target]['kubelet_checks'][node_hostname]['CIS 2.1.13 - Ensure that the --cadvisor-port argument is set to 0'] = "Pass"
|
145
|
+
end
|
146
|
+
#Need an ensure block here to make sure that the pod is deleted after its run
|
147
|
+
ensure
|
148
|
+
@client.delete_pod(container_name,"default")
|
136
149
|
end
|
137
150
|
|
138
|
-
#@results[target]['kubelet_checks'][node_hostname] = files
|
139
|
-
@client.delete_pod(container_name,"default")
|
140
|
-
|
141
151
|
end
|
142
152
|
|
143
153
|
end
|
@@ -166,8 +166,8 @@ module KubeAutoAnalyzer
|
|
166
166
|
end
|
167
167
|
#Close the master Node Div
|
168
168
|
@html_report_file.puts "</table></div>"
|
169
|
-
@
|
170
|
-
|
169
|
+
if @options.agent_checks
|
170
|
+
@html_report_file.puts '<br><br><div class="worker-node"><h2>Worker Node Results</h2>'
|
171
171
|
@results[@options.target_server]['kubelet_checks'].each do |node, results|
|
172
172
|
@html_report_file.puts "<br><b>#{node} Kubelet Checks</b>"
|
173
173
|
@html_report_file.puts "<table><thead><tr><th>Check</th><th>result</th></tr></thead>"
|
@@ -194,7 +194,7 @@ module KubeAutoAnalyzer
|
|
194
194
|
end
|
195
195
|
#Close the Worker Node Div
|
196
196
|
@html_report_file.puts '</div>'
|
197
|
-
if @options.
|
197
|
+
if @options.agent_checks
|
198
198
|
@html_report_file.puts '<br><h2>File Permissions</h2>'
|
199
199
|
@results[@options.target_server]['worker_files'].each do |node, results|
|
200
200
|
@html_report_file.puts "<br><b>#{node}</b><br>"
|
@@ -206,6 +206,81 @@ module KubeAutoAnalyzer
|
|
206
206
|
end
|
207
207
|
end
|
208
208
|
|
209
|
+
@html_report_file.puts '<br><h2>Vulnerability Checks</h2>'
|
210
|
+
@html_report_file.puts '<br><h3>External Unauthenticated Access to the Kubelet</h3>'
|
211
|
+
@html_report_file.puts "<table><thead><tr><th>Node IP Address</th><th>Result</th></thead>"
|
212
|
+
@results[@options.target_server]['vulns']['unauth_kubelet'].each do |node, result|
|
213
|
+
unless (result =~ /Forbidden/ || result =~ /Not Open/)
|
214
|
+
output = "Vulnerable"
|
215
|
+
else
|
216
|
+
output = result
|
217
|
+
end
|
218
|
+
@html_report_file.puts "<tr><td>#{node}</td><td>#{output}</td></tr>"
|
219
|
+
end
|
220
|
+
@html_report_file.puts "</table>"
|
221
|
+
if @options.agent_checks
|
222
|
+
@html_report_file.puts '<br><h3>Internal Unauthenticated Access to the Kubelet</h3>'
|
223
|
+
@html_report_file.puts "<table><thead><tr><th>Node IP Address</th><th>Result</th></thead>"
|
224
|
+
@results[@options.target_server]['vulns']['internal_kubelet'].each do |node, result|
|
225
|
+
unless (result =~ /Forbidden/ || result =~ /Not Open/)
|
226
|
+
output = "Vulnerable"
|
227
|
+
else
|
228
|
+
output = result
|
229
|
+
end
|
230
|
+
@html_report_file.puts "<tr><td>#{node}</td><td>#{output}</td></tr>"
|
231
|
+
end
|
232
|
+
@html_report_file.puts "</table>"
|
233
|
+
end
|
234
|
+
|
235
|
+
@html_report_file.puts '<br><h3>External Insecure API Port Exposed</h3>'
|
236
|
+
@html_report_file.puts "<table><thead><tr><th>Node IP Address</th><th>Result</th></thead>"
|
237
|
+
@results[@options.target_server]['vulns']['insecure_api_external'].each do |node, result|
|
238
|
+
unless (result =~ /Forbidden/ || result =~ /Not Open/)
|
239
|
+
output = "Vulnerable"
|
240
|
+
else
|
241
|
+
output = result
|
242
|
+
end
|
243
|
+
@html_report_file.puts "<tr><td>#{node}</td><td>#{output}</td></tr>"
|
244
|
+
end
|
245
|
+
@html_report_file.puts "</table>"
|
246
|
+
if @options.agent_checks
|
247
|
+
@html_report_file.puts '<br><h3>Internal Insecure API Port Exposed</h3>'
|
248
|
+
@html_report_file.puts "<table><thead><tr><th>Node IP Address</th><th>Result</th></thead>"
|
249
|
+
@results[@options.target_server]['vulns']['insecure_api_internal'].each do |node, result|
|
250
|
+
unless (result =~ /Forbidden/ || result =~ /Not Open/)
|
251
|
+
output = "Vulnerable"
|
252
|
+
else
|
253
|
+
output = result
|
254
|
+
end
|
255
|
+
@html_report_file.puts "<tr><td>#{node}</td><td>#{output}</td></tr>"
|
256
|
+
end
|
257
|
+
@html_report_file.puts "</table>"
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
|
262
|
+
@html_report_file.puts "<br><br><h2>Vulnerability Evidence</h2><br>"
|
263
|
+
@html_report_file.puts "<table><thead><tr><th>Vulnerability</th><th>Host</th><th>Output</th></tr></thead>"
|
264
|
+
@results[@options.target_server]['vulns']['unauth_kubelet'].each do |node, result|
|
265
|
+
@html_report_file.puts "<tr><td>External Unauthenticated Kubelet Access</td><td>#{node}</td><td>#{result}</td></tr>"
|
266
|
+
end
|
267
|
+
if @options.agent_checks
|
268
|
+
@results[@options.target_server]['vulns']['internal_kubelet'].each do |node, result|
|
269
|
+
@html_report_file.puts "<tr><td>Internal Unauthenticated Kubelet Access</td><td>#{node}</td><td>#{result}</td></tr>"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
@results[@options.target_server]['vulns']['insecure_api_external'].each do |node, result|
|
273
|
+
@html_report_file.puts "<tr><td>External Insecure API Server Access</td><td>#{node}</td><td>#{result}</td></tr>"
|
274
|
+
end
|
275
|
+
if @options.agent_checks
|
276
|
+
@results[@options.target_server]['vulns']['insecure_api_internal'].each do |node, result|
|
277
|
+
@html_report_file.puts "<tr><td>Internal Insecure API Server Access</td><td>#{node}</td><td>#{result}</td></tr>"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
@html_report_file.puts "</table>"
|
281
|
+
|
282
|
+
|
283
|
+
#Closing the report off
|
209
284
|
@html_report_file.puts '</body></html>'
|
210
285
|
end
|
211
286
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module KubeAutoAnalyzer
|
2
|
+
|
3
|
+
def self.test_insecure_api_external
|
4
|
+
@log.debug("Doing the external Insecure API check")
|
5
|
+
target = @options.target_server
|
6
|
+
unless @results[target]['vulns']
|
7
|
+
@results[target]['vulns'] = Hash.new
|
8
|
+
end
|
9
|
+
@results[target]['vulns']['insecure_api_external'] = Hash.new
|
10
|
+
#Check for whether the Insecure API port is visible outside the cluster
|
11
|
+
nodes = Array.new
|
12
|
+
@client.get_nodes.each do |node|
|
13
|
+
nodes << node['status']['addresses'][0]['address']
|
14
|
+
end
|
15
|
+
nodes.each do |nod|
|
16
|
+
if is_port_open?(nod, 8080)
|
17
|
+
begin
|
18
|
+
pods_resp = RestClient::Request.execute(:url => "http://#{nod}:8080/api",:method => :get)
|
19
|
+
rescue RestClient::Forbidden
|
20
|
+
pods_resp = "Not Vulnerable - Request Forbidden"
|
21
|
+
end
|
22
|
+
@results[target]['vulns']['insecure_api_external'][nod] = pods_resp
|
23
|
+
else
|
24
|
+
@results[target]['vulns']['insecure_api_external'][nod] = "Not Vulnerable - Port Not Open"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
#This is somewhat awkward placement. Deployment mechanism sits more with the agent checks
|
30
|
+
#But from a "what it's looking for" perspective, as a weakness in API Server, it makes more sense here.
|
31
|
+
def self.test_insecure_api_internal
|
32
|
+
require 'json'
|
33
|
+
|
34
|
+
@log.debug("Doing the internal Insecure API Server check")
|
35
|
+
target = @options.target_server
|
36
|
+
@results[target]['vulns']['insecure_api_internal'] = Hash.new
|
37
|
+
nodes = Array.new
|
38
|
+
@client.get_nodes.each do |node|
|
39
|
+
nodes << node['status']['addresses'][0]['address']
|
40
|
+
end
|
41
|
+
container_name = "kaainsecureapitest"
|
42
|
+
pod = Kubeclient::Resource.new
|
43
|
+
pod.metadata = {}
|
44
|
+
pod.metadata.name = container_name
|
45
|
+
pod.metadata.namespace = "default"
|
46
|
+
pod.spec = {}
|
47
|
+
pod.spec.restartPolicy = "Never"
|
48
|
+
pod.spec.containers = {}
|
49
|
+
pod.spec.containers = [{name: "kubeautoanalyzerapitest", image: "raesene/kaa-agent:latest"}]
|
50
|
+
pod.spec.containers[0].args = ["/api-server-checker.rb",nodes.join(',')]
|
51
|
+
begin
|
52
|
+
@log.debug("About to start API Server check pod")
|
53
|
+
@client.create_pod(pod)
|
54
|
+
@log.debug("Executed the create pod")
|
55
|
+
begin
|
56
|
+
sleep(5) until @client.get_pod(container_name,"default")['status']['containerStatuses'][0]['state']['terminated']['reason'] == "Completed"
|
57
|
+
rescue
|
58
|
+
retry
|
59
|
+
end
|
60
|
+
@log.debug ("started Kube API Check pod")
|
61
|
+
results = JSON.parse(@client.get_pod_log(container_name,"default"))
|
62
|
+
results.each do |node, results|
|
63
|
+
@results[target]['vulns']['insecure_api_internal'][node] = results
|
64
|
+
end
|
65
|
+
ensure
|
66
|
+
@client.delete_pod(container_name,"default")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module KubeAutoAnalyzer
|
2
|
+
|
3
|
+
def self.test_unauth_kubelet_external
|
4
|
+
@log.debug("Doing the external kubelet check")
|
5
|
+
target = @options.target_server
|
6
|
+
unless @results[target]['vulns']
|
7
|
+
@results[target]['vulns'] = Hash.new
|
8
|
+
end
|
9
|
+
@results[target]['vulns']['unauth_kubelet'] = Hash.new
|
10
|
+
#Check for whether the Kubelet port is visible outside the cluster
|
11
|
+
nodes = Array.new
|
12
|
+
@client.get_nodes.each do |node|
|
13
|
+
nodes << node['status']['addresses'][0]['address']
|
14
|
+
end
|
15
|
+
nodes.each do |nod|
|
16
|
+
if is_port_open?(nod, 10250)
|
17
|
+
begin
|
18
|
+
pods_resp = RestClient::Request.execute(:url => "https://#{nod}:10250/runningpods",:method => :get, :verify_ssl => false)
|
19
|
+
rescue RestClient::Forbidden
|
20
|
+
pods_resp = "Not Vulnerable - Request Forbidden"
|
21
|
+
end
|
22
|
+
@results[target]['vulns']['unauth_kubelet'][nod] = pods_resp
|
23
|
+
else
|
24
|
+
@results[target]['vulns']['unauth_kubelet'][nod] = "Not Vulnerable - Port Not Open"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
#This is somewhat awkward placement. Deployment mechanism sits more with the agent checks
|
30
|
+
#But from a "what it's looking for" perspective, as a weakness in Kubelet, it makes more sense here.
|
31
|
+
def self.test_unauth_kubelet_internal
|
32
|
+
require 'json'
|
33
|
+
|
34
|
+
@log.debug("Doing the internal kubelet check")
|
35
|
+
target = @options.target_server
|
36
|
+
@results[target]['vulns']['internal_kubelet'] = Hash.new
|
37
|
+
nodes = Array.new
|
38
|
+
@client.get_nodes.each do |node|
|
39
|
+
nodes << node['status']['addresses'][0]['address']
|
40
|
+
end
|
41
|
+
container_name = "kaakubeletunauthtest"
|
42
|
+
pod = Kubeclient::Resource.new
|
43
|
+
pod.metadata = {}
|
44
|
+
pod.metadata.name = container_name
|
45
|
+
pod.metadata.namespace = "default"
|
46
|
+
pod.spec = {}
|
47
|
+
pod.spec.restartPolicy = "Never"
|
48
|
+
pod.spec.containers = {}
|
49
|
+
pod.spec.containers = [{name: "kubeautoanalyzerkubelettest", image: "raesene/kaa-agent:latest"}]
|
50
|
+
pod.spec.containers[0].args = ["/kubelet-checker.rb",nodes.join(',')]
|
51
|
+
begin
|
52
|
+
@log.debug("About to start Kubelet check pod")
|
53
|
+
@client.create_pod(pod)
|
54
|
+
@log.debug("Executed the create pod")
|
55
|
+
begin
|
56
|
+
sleep(5) until @client.get_pod(container_name,"default")['status']['containerStatuses'][0]['state']['terminated']['reason'] == "Completed"
|
57
|
+
rescue
|
58
|
+
retry
|
59
|
+
end
|
60
|
+
@log.debug ("started Kubelet Check pod")
|
61
|
+
results = JSON.parse(@client.get_pod_log(container_name,"default"))
|
62
|
+
results.each do |node, results|
|
63
|
+
@results[target]['vulns']['internal_kubelet'][node] = results
|
64
|
+
end
|
65
|
+
ensure
|
66
|
+
@client.delete_pod(container_name,"default")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/kube_auto_analyzer.rb
CHANGED
@@ -5,6 +5,9 @@ module KubeAutoAnalyzer
|
|
5
5
|
require "kube_auto_analyzer/reporting"
|
6
6
|
require "kube_auto_analyzer/agent_checks/file_checks"
|
7
7
|
require "kube_auto_analyzer/agent_checks/process_checks"
|
8
|
+
require "kube_auto_analyzer/vuln_checks/kubelet"
|
9
|
+
require "kube_auto_analyzer/vuln_checks/api_server"
|
10
|
+
require "kube_auto_analyzer/utility/network"
|
8
11
|
|
9
12
|
|
10
13
|
def self.execute(commmand_line_opts)
|
@@ -64,27 +67,32 @@ module KubeAutoAnalyzer
|
|
64
67
|
)
|
65
68
|
#We didn't specify the target on the command line so lets get it from the config file
|
66
69
|
@options.target_server = config.context.api_endpoint
|
70
|
+
@log.debug("target is " + @options.target_server)
|
67
71
|
@results[config.context.api_endpoint] = Hash.new
|
68
72
|
end
|
69
73
|
#Test response
|
70
74
|
begin
|
71
75
|
@client.get_pods.to_s
|
72
76
|
rescue
|
73
|
-
puts "
|
77
|
+
puts "Check of API connection failed."
|
78
|
+
puts "try using kubectl with the same connection details"
|
79
|
+
puts "to see what's going wrong."
|
74
80
|
exit
|
75
81
|
end
|
76
82
|
test_api_server
|
77
83
|
test_scheduler
|
78
84
|
test_controller_manager
|
79
85
|
test_etcd
|
80
|
-
|
86
|
+
test_unauth_kubelet_external
|
87
|
+
test_insecure_api_external
|
88
|
+
if @options.agent_checks
|
89
|
+
test_unauth_kubelet_internal
|
90
|
+
test_insecure_api_internal
|
81
91
|
check_files
|
82
|
-
end
|
83
|
-
if @options.agent_process_checks
|
84
92
|
check_kubelet_process
|
85
93
|
end
|
86
|
-
|
87
|
-
report
|
88
94
|
html_report
|
89
95
|
end
|
96
|
+
|
97
|
+
|
90
98
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kube_auto_analyzer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rory McCune
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-06-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -70,7 +70,10 @@ files:
|
|
70
70
|
- lib/kube_auto_analyzer/api_checks/master_node.rb
|
71
71
|
- lib/kube_auto_analyzer/data-logo.b64
|
72
72
|
- lib/kube_auto_analyzer/reporting.rb
|
73
|
+
- lib/kube_auto_analyzer/utility/network.rb
|
73
74
|
- lib/kube_auto_analyzer/version.rb
|
75
|
+
- lib/kube_auto_analyzer/vuln_checks/api_server.rb
|
76
|
+
- lib/kube_auto_analyzer/vuln_checks/kubelet.rb
|
74
77
|
homepage: https://github.com/nccgroup/kube-auto-analyzer
|
75
78
|
licenses:
|
76
79
|
- AGPL
|