kube_auto_analyzer 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 179347b988fdd1f2ad81a2275f688e70f486de38
4
+ data.tar.gz: 758db7c745076d8eaabe55a77b839304ebfa1dd7
5
+ SHA512:
6
+ metadata.gz: 96ec9cbe4b584861de6012d5bbbe09cc195936754984ec36c5d7c143c555f175f8f65c76821111c4fc95f9698f0b8bda4d8732e5a29fde3f2a260dd32b964d51
7
+ data.tar.gz: 0273af5e567d1f78d6cc92ff511fcb8bfdb1fc44996d5ceb86193121d4f700dc998b89887caac2d8cd61759d5eb3ad8eac6a16f422146383409de1e692c2b0dd
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'kube_auto_analyzer'
4
+ require 'ostruct'
5
+ require 'optparse'
6
+ options = OpenStruct.new
7
+
8
+ options.report_directory = Dir.pwd
9
+ options.report_file = 'kube-parse-report'
10
+ options.target_server = 'http://127.0.0.1:8080'
11
+ options.html_report = false
12
+ options.token = ''
13
+ options.token_file = ''
14
+ options.config_file = false
15
+ options.agent_file_checks = false
16
+ options.agent_process_checks = false
17
+
18
+ opts = OptionParser.new do |opts|
19
+ opts.banner = "Kubernetes Auto Analyzer #{KubeAutoAnalyzer::VERSION}"
20
+
21
+ opts.on("-s", "--server [SERVER]", "Target Server") do |serv|
22
+ options.target_server = serv
23
+ end
24
+
25
+ #TODO: Need options for different authentication mechanisms
26
+ opts.on("-c", "--config [CONFIG]", "kubeconfig file to load") do |file|
27
+ options.config_file = file
28
+ end
29
+
30
+ opts.on("-t", "--token [TOKEN]", "Bearer Token to Use") do |token|
31
+ options.token = token
32
+ end
33
+
34
+ opts.on("-f", "--token_file [TOKENFILE]", "Token file to use (provide full path)") do |token_file|
35
+ options.token = token_file
36
+ end
37
+
38
+ opts.on("-r", "--report [REPORT]", "Report Base name") do |rep|
39
+ options.report_file = rep + '_kube'
40
+ end
41
+
42
+ opts.on("--reportDirectory [REPORTDIRECTORY]", "Report Directory") do |rep|
43
+ options.report_directory = rep
44
+ end
45
+
46
+ opts.on("--fileChecks","Carry out File permission Checks (expermimental)") do |fc|
47
+ options.agent_file_checks = true
48
+ end
49
+
50
+ opts.on("--processChecks","Carry out agent based process Checks (expermimental)") do |fc|
51
+ options.agent_process_checks = true
52
+ end
53
+
54
+ opts.on("-h", "--help", "-?", "--?", "Get Help") do |help|
55
+ puts opts
56
+ exit
57
+ end
58
+
59
+ opts.on("-v", "--version", "get Version") do |ver|
60
+ puts "Kubernetes Analyzer Version #{KubernetesAnalyzer::VERSION}"
61
+ exit
62
+ end
63
+ end
64
+
65
+ opts.parse!(ARGV)
66
+
67
+ unless (options.token.length > 1 || options.config_file || options.token_file.length > 1)
68
+ puts "No valid auth mechanism specified"
69
+ puts opts
70
+ exit
71
+ end
72
+
73
+ KubeAutoAnalyzer.execute(options)
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kube_auto_analyzer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "kube_auto_analyzer"
8
+ spec.version = KubeAutoAnalyzer::VERSION
9
+ spec.authors = ["Rory McCune"]
10
+ spec.email = ["rory.mccune@nccgroup.trust"]
11
+ spec.summary = %q{A Gem which provides a script and class analyze the security of a Kubernetes cluster.}
12
+ spec.description = %q{This is a gem used to help when conducting a security analysis of a Kubernetes cluster in-line with the requirements of the CIS Benchmark.}
13
+ spec.homepage = "https://github.com/nccgroup/kube-auto-analyzer"
14
+ spec.license = "AGPL"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 0"
22
+ spec.add_development_dependency "rake", "~> 0"
23
+ spec.add_runtime_dependency "kubeclient", ">= 2.4.0"
24
+ end
@@ -0,0 +1,51 @@
1
+ module KubeAutoAnalyzer
2
+
3
+ def self.check_files
4
+ require 'json'
5
+ @log.debug ("entering File check")
6
+ target = @options.target_server
7
+ @results[target]['worker_files'] = Hash.new
8
+
9
+ #Run on any nodes that aren't NoSchedule
10
+ #Doesn't necessarily mean worker nodes, but a reasonable facsimile for now.
11
+ nodes = Array.new
12
+ @client.get_nodes.each do |node|
13
+ unless node.spec.taints.to_s =~ /NoSchedule/
14
+ nodes << node
15
+ end
16
+ end
17
+ nodes.each do |nod|
18
+ node_hostname = nod.metadata.labels['kubernetes.io/hostname']
19
+ container_name = "kaa" + node_hostname
20
+ pod = Kubeclient::Resource.new
21
+ pod.metadata = {}
22
+ pod.metadata.name = container_name
23
+ pod.metadata.namespace = "default"
24
+ pod.spec = {}
25
+ pod.spec.restartPolicy = "Never"
26
+ pod.spec.containers = {}
27
+ pod.spec.containers = [{name: "kubeautoanalyzerfiletest", image: "raesene/kaa-agent:latest"}]
28
+ pod.spec.volumes = [{name: 'etck8s', hostPath: {path: '/etc'}}]
29
+ pod.spec.containers[0].volumeMounts = [{mountPath: '/etc', name: 'etck8s'}]
30
+ pod.spec.containers[0].args = ["/file-checker.rb","/etc/kubernetes"]
31
+ pod.spec.nodeselector = {}
32
+ pod.spec.nodeselector['kubernetes.io/hostname'] = node_hostname
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
+ 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
+
47
+ end
48
+ @log.debug("Finished Worker File Check")
49
+ end
50
+
51
+ end
@@ -0,0 +1,145 @@
1
+ module KubeAutoAnalyzer
2
+
3
+ def self.check_kubelet_process
4
+ @log.debug("Entering Process Checks")
5
+ target = @options.target_server
6
+ @results[target]['kubelet_checks'] = Hash.new
7
+ @results[target]['node_evidence'] = Hash.new
8
+
9
+
10
+ nodes = Array.new
11
+ @client.get_nodes.each do |node|
12
+ unless node.spec.taints.to_s =~ /NoSchedule/
13
+ nodes << node
14
+ end
15
+ end
16
+
17
+ nodes.each do |nod|
18
+ node_hostname = nod.metadata.labels['kubernetes.io/hostname']
19
+ container_name = "kaa" + node_hostname
20
+ pod = Kubeclient::Resource.new
21
+ pod.metadata = {}
22
+ pod.metadata.name = container_name
23
+ pod.metadata.namespace = "default"
24
+ pod.spec = {}
25
+ pod.spec.restartPolicy = "Never"
26
+ pod.spec.containers = {}
27
+ pod.spec.containers = [{name: "kaakubelettest", image: "raesene/kaa-agent:latest"}]
28
+ pod.spec.containers[0].args = ["/process-checker.rb"]
29
+ pod.spec.hostPID = true
30
+ pod.spec.nodeselector = {}
31
+ pod.spec.nodeselector['kubernetes.io/hostname'] = node_hostname
32
+ @client.create_pod(pod)
33
+ begin
34
+ sleep(5) until @client.get_pod(container_name,"default")['status']['containerStatuses'][0]['state']['terminated']['reason'] == "Completed"
35
+ rescue
36
+ retry
37
+ end
38
+ processes = JSON.parse(@client.get_pod_log(container_name,"default"))
39
+ #puts processes
40
+ kubelet_proc = ''
41
+ processes.each do |proc|
42
+ if proc =~ /kubelet/
43
+ kubelet_proc = proc
44
+ 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
+
54
+ @results[target]['node_evidence'][node_hostname] = Hash.new
55
+ @results[target]['node_evidence'][node_hostname]['kubelet'] = kubelet_proc
56
+
57
+
58
+
59
+ #Checks
60
+ unless kubelet_proc =~ /--allow-privileged=false/
61
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.1 - Ensure that the --allow-privileged argument is set to false'] = "Fail"
62
+ else
63
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.1 - Ensure that the --allow-privileged argument is set to false'] = "Pass"
64
+ end
65
+
66
+ unless kubelet_proc =~ /--anonymous-auth=false/
67
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.2 - Ensure that the --anonymous-auth argument is set to false'] = "Fail"
68
+ else
69
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.2 - Ensure that the --anonymous-auth argument is set to false'] = "Pass"
70
+ end
71
+
72
+ if kubelet_proc =~ /--authorization-mode\S*AlwaysAllow/
73
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.3 - Ensure that the --authorization-mode argument is not set to AlwaysAllow'] = "Fail"
74
+ else
75
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.3 - Ensure that the --authorization-mode argument is not set to AlwaysAllow'] = "Pass"
76
+ end
77
+
78
+ unless kubelet_proc =~ /--client-ca-file/
79
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.4 - Ensure that the --client-ca-file argument is set as appropriate'] = "Fail"
80
+ else
81
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.4 - Ensure that the --client-ca-file argument is set as appropriate'] = "Pass"
82
+ end
83
+
84
+ unless kubelet_proc =~ /--read-only-port=0/
85
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.5 - Ensure that the --read-only-port argument is set to 0'] = "Fail"
86
+ else
87
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.5 - Ensure that the --read-only-port argument is set to 0'] = "Pass"
88
+ end
89
+
90
+ if kubelet_proc =~ /--streaming-connection-idle-timeout=0/
91
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.6 - Ensure that the --streaming-connection-idle-timeout argument is not set to 0'] = "Fail"
92
+ else
93
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.6 - Ensure that the --streaming-connection-idle-timeout argument is not set to 0'] = "Pass"
94
+ end
95
+
96
+ unless kubelet_proc =~ /--protect-kernel-defaults=true/
97
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.7 - Ensure that the --protect-kernel-defaults argument is set to true'] = "Fail"
98
+ else
99
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.7 - Ensure that the --protect-kernel-defaults argument is set to true'] = "Pass"
100
+ end
101
+
102
+ if kubelet_proc =~ /--make-iptables-util-chains=false/
103
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.8 - Ensure that the --make-iptables-util-chains argument is set to true'] = "Fail"
104
+ else
105
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.8 - Ensure that the --make-iptables-util-chains argument is set to true'] = "Pass"
106
+ end
107
+
108
+ unless kubelet_proc =~ /--keep-terminated-pod-volumes=false/
109
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.9 - that the --keep-terminated-pod-volumes argument is set to false'] = "Fail"
110
+ else
111
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.9 - Ensure that the --keep-terminated-pod-volumes argument is set to false'] = "Pass"
112
+ end
113
+
114
+ if kubelet_proc =~ /--hostname-override/
115
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.10 - Ensure that the --hostname-override argument is not set'] = "Fail"
116
+ else
117
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.10 - Ensure that the --hostname-override argument is not set'] = "Pass"
118
+ end
119
+
120
+ unless kubelet_proc =~ /--event-qps=0/
121
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.11 - Ensure that the --event-qps argument is set to 0'] = "Fail"
122
+ else
123
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.11 - Ensure that the --event-qps argument is set to 0'] = "Pass"
124
+ end
125
+
126
+ unless (kubelet_proc =~ /--tls-cert-file/) && (kubelet_proc =~ /--tls-private-key-file/)
127
+ @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"
128
+ else
129
+ @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"
130
+ end
131
+
132
+ unless kubelet_proc =~ /--cadvisor-port=0/
133
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.13 - Ensure that the --cadvisor-port argument is set to 0'] = "Fail"
134
+ else
135
+ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.13 - Ensure that the --cadvisor-port argument is set to 0'] = "Pass"
136
+ end
137
+
138
+ #@results[target]['kubelet_checks'][node_hostname] = files
139
+ @client.delete_pod(container_name,"default")
140
+
141
+ end
142
+
143
+ end
144
+
145
+ end
@@ -0,0 +1,368 @@
1
+ module KubeAutoAnalyzer
2
+
3
+ def self.test_api_server
4
+ @log.debug("Entering the test API Server Method")
5
+ target = @options.target_server
6
+ @log.debug("target is #{target}")
7
+ @results[target]['api_server'] = Hash.new
8
+ @results[target]['evidence'] = Hash.new
9
+ pods = @client.get_pods
10
+ pods.each do |pod|
11
+ #Ok this is a bit naive as a means of hitting the API server but hey it's a start
12
+ if pod['metadata']['name'] =~ /kube-apiserver/
13
+ @api_server = pod
14
+ end
15
+ end
16
+
17
+ unless @api_server
18
+ @results[target]['api_server']['API Server Pod Not Found'] = "Error"
19
+ return
20
+ end
21
+
22
+ api_server_command_line = @api_server['spec']['containers'][0]['command']
23
+
24
+ #Check for Allow Privileged
25
+ unless api_server_command_line.index{|line| line =~ /--allow-privileged=false/}
26
+ @results[target]['api_server']['CIS 1.1.1 - Ensure that the --allow-privileged argument is set to false'] = "Fail"
27
+ else
28
+ @results[target]['api_server']['CIS 1.1.1 - Ensure that the --allow-privileged argument is set to false'] = "Pass"
29
+ end
30
+
31
+ #Check for Anonymous Auth
32
+ unless api_server_command_line.index{|line| line =~ /--anonymous-auth=false/}
33
+ @results[target]['api_server']['CIS 1.1.2 - Ensure that the --anonymous-auth argument is set to false'] = "Fail"
34
+ else
35
+ @results[target]['api_server']['CIS 1.1.2 - Ensure that the --anonymous-auth argument is set to false'] = "Pass"
36
+ end
37
+
38
+ #Check for Basic Auth
39
+ if api_server_command_line.index{|line| line =~ /--basic-auth-file/}
40
+ @results[target]['api_server']['CIS 1.1.3 - Ensure that the --basic-auth-file argument is not set'] = "Fail"
41
+ else
42
+ @results[target]['api_server']['CIS 1.1.3 - Ensure that the --basic-auth-file argument is not set'] = "Pass"
43
+ end
44
+
45
+ #Check for Insecure Allow Any Token
46
+ if api_server_command_line.index{|line| line =~ /--insecure-allow-any-token/}
47
+ @results[target]['api_server']['CIS 1.1.4 - Ensure that the --insecure-allow-any-token argument is not set'] = "Fail"
48
+ else
49
+ @results[target]['api_server']['CIS 1.1.4 - Ensure that the --insecure-allow-any-token argument is not set'] = "Pass"
50
+ end
51
+
52
+ #Check to confirm that Kubelet HTTPS isn't set to false
53
+ if api_server_command_line.index{|line| line =~ /--kubelet-https=false/}
54
+ @results[target]['api_server']['CIS 1.1.5 - Ensure that the --kubelet-https argument is set to true'] = "Fail"
55
+ else
56
+ @results[target]['api_server']['CIS 1.1.5 - Ensure that the --kubelet-https argument is set to true'] = "Pass"
57
+ end
58
+
59
+ #Check for Insecure Bind Address
60
+ if api_server_command_line.index{|line| line =~ /--insecure-bind-address/}
61
+ @results[target]['api_server']['CIS 1.1.6 - Ensure that the --insecure-bind-address argument is not set'] = "Fail"
62
+ else
63
+ @results[target]['api_server']['CIS 1.1.6 - Ensure that the --insecure-bind-address argument is not set'] = "Pass"
64
+ end
65
+
66
+ #Check for Insecure Bind port
67
+ unless api_server_command_line.index{|line| line =~ /--insecure-port=0/}
68
+ @results[target]['api_server']['CIS 1.1.7 - Ensure that the --insecure-port argument is set to 0'] = "Fail"
69
+ else
70
+ @results[target]['api_server']['CIS 1.1.7 - Ensure that the --insecure-port argument is set to 0'] = "Pass"
71
+ end
72
+
73
+ #Check Secure Port isn't set to 0
74
+ if api_server_command_line.index{|line| line =~ /--secure-port=0/}
75
+ @results[target]['api_server']['CIS 1.1.8 - Ensure that the --secure-port argument is not set to 0'] = "Fail"
76
+ else
77
+ @results[target]['api_server']['CIS 1.1.8 - Ensure that the --secure-port argument is not set to 0'] = "Pass"
78
+ end
79
+
80
+ #
81
+ unless api_server_command_line.index{|line| line =~ /--profiling=false/}
82
+ @results[target]['api_server']['CIS 1.1.9 - Ensure that the --profiling argument is set to false'] = "Fail"
83
+ else
84
+ @results[target]['api_server']['CIS 1.1.9 - Ensure that the --profiling argument is set to false'] = "Pass"
85
+ end
86
+
87
+ unless api_server_command_line.index{|line| line =~ /--repair-malformed-updates/}
88
+ @results[target]['api_server']['CIS 1.1.10 - Ensure that the --repair-malformed-updates argument is set to false'] = "Fail"
89
+ else
90
+ @results[target]['api_server']['CIS 1.1.10 - Ensure that the --repair-malformed-updates argument is set to false'] = "Pass"
91
+ end
92
+
93
+ if api_server_command_line.index{|line| line =~ /--admission-control\S*AlwaysAdmit/}
94
+ @results[target]['api_server']['CIS 1.1.11 - Ensure that the admission control policy is not set to AlwaysAdmit'] = "Fail"
95
+ else
96
+ @results[target]['api_server']['CIS 1.1.11 - Ensure that the admission control policy is not set to AlwaysAdmit'] = "Pass"
97
+ end
98
+
99
+ unless api_server_command_line.index{|line| line =~ /--admission-control\S*AlwaysPullImages/}
100
+ @results[target]['api_server']['CIS 1.1.12 - Ensure that the admission control policy is set to AlwaysPullImages'] = "Fail"
101
+ else
102
+ @results[target]['api_server']['CIS 1.1.12 - Ensure that the admission control policy is set to AlwaysPullImages'] = "Pass"
103
+ end
104
+
105
+ unless api_server_command_line.index{|line| line =~ /--admission-control\S*DenyEscalatingExec/}
106
+ @results[target]['api_server']['CIS 1.1.13 - Ensure that the admission control policy is set to DenyEscalatingExec'] = "Fail"
107
+ else
108
+ @results[target]['api_server']['CIS 1.1.13 - Ensure that the admission control policy is set to DenyEscalatingExec'] = "Pass"
109
+ end
110
+
111
+ unless api_server_command_line.index{|line| line =~ /--admission-control\S*SecurityContextDeny/}
112
+ @results[target]['api_server']['CIS 1.1.14 - Ensure that the admission control policy is set to SecurityContextDeny'] = "Fail"
113
+ else
114
+ @results[target]['api_server']['CIS 1.1.14 - Ensure that the admission control policy is set to SecurityContextDeny'] = "Pass"
115
+ end
116
+
117
+ unless api_server_command_line.index{|line| line =~ /--admission-control\S*NamespaceLifecycle/}
118
+ @results[target]['api_server']['CIS 1.1.15 - Ensure that the admission control policy is set to NamespaceLifecycle'] = "Fail"
119
+ else
120
+ @results[target]['api_server']['CIS 1.1.15 - Ensure that the admission control policy is set to NamespaceLifecycle'] = "Pass"
121
+ end
122
+
123
+ unless api_server_command_line.index{|line| line =~ /--audit-log-path/}
124
+ @results[target]['api_server']['CIS 1.1.16 - Ensure that the --audit-log-path argument is set as appropriate'] = "Fail"
125
+ else
126
+ @results[target]['api_server']['CIS 1.1.16 - Ensure that the --audit-log-path argument is set as appropriate'] = "Pass"
127
+ end
128
+
129
+ #TODO: This check needs to do something with the number of days but for now lets just check whether it's present.
130
+ unless api_server_command_line.index{|line| line =~ /--audit-log-maxage/}
131
+ @results[target]['api_server']['CIS 1.1.17 - Ensure that the --audit-log-maxage argument is set to 30 or as appropriate'] = "Fail"
132
+ else
133
+ @results[target]['api_server']['CIS 1.1.17 - Ensure that the --audit-log-maxage argument is set to 30 or as appropriate'] = "Pass"
134
+ end
135
+
136
+ #TODO: This check needs to do something with the number of backups but for now lets just check whether it's present.
137
+ unless api_server_command_line.index{|line| line =~ /--audit-log-maxbackup/}
138
+ @results[target]['api_server']['CIS 1.1.18 - Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate'] = "Fail"
139
+ else
140
+ @results[target]['api_server']['CIS 1.1.18 - Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate'] = "Pass"
141
+ end
142
+
143
+ #TODO: This check needs to do something with the size of backups but for now lets just check whether it's present.
144
+ unless api_server_command_line.index{|line| line =~ /--audit-log-maxsize/}
145
+ @results[target]['api_server']['CIS 1.1.19 - Ensure that the --audit-log-maxsize argument is set to 100 or as appropriate'] = "Fail"
146
+ else
147
+ @results[target]['api_server']['CIS 1.1.19 - Ensure that the --audit-log-maxsize argument is set to 100 or as appropriate'] = "Pass"
148
+ end
149
+
150
+ if api_server_command_line.index{|line| line =~ /--authorization-mode\S*AlwaysAllow/}
151
+ @results[target]['api_server']['CIS 1.1.20 - Ensure that the --authorization-mode argument is not set to AlwaysAllow'] = "Fail"
152
+ else
153
+ @results[target]['api_server']['CIS 1.1.20 - Ensure that the --authorization-mode argument is not set to AlwaysAllow'] = "Pass"
154
+ end
155
+
156
+ if api_server_command_line.index{|line| line =~ /--token-auth-file/}
157
+ @results[target]['api_server']['CIS 1.1.21 - Ensure that the --token-auth-file argument is not set'] = "Fail"
158
+ else
159
+ @results[target]['api_server']['CIS 1.1.21 - Ensure that the --token-auth-file argument is not set'] = "Pass"
160
+ end
161
+
162
+ unless api_server_command_line.index{|line| line =~ /--kubelet-certificate-authority/}
163
+ @results[target]['api_server']['CIS 1.1.22 - Ensure that the --kubelet-certificate-authority argument is set as appropriate'] = "Fail"
164
+ else
165
+ @results[target]['api_server']['CIS 1.1.22 - Ensure that the --kubelet-certificate-authority argument is set as appropriate'] = "Pass"
166
+ end
167
+
168
+ unless (api_server_command_line.index{|line| line =~ /--kubelet-client-certificate/} && api_server_command_line.index{|line| line =~ /--kubelet-client-key/})
169
+ @results[target]['api_server']['CIS 1.1.23 - Ensure that the --kubelet-client-certificate and --kubelet-client-key arguments are set as appropriate'] = "Fail"
170
+ else
171
+ @results[target]['api_server']['CIS 1.1.23 - Ensure that the --kubelet-client-certificate and --kubelet-client-key arguments are set as appropriate'] = "Pass"
172
+ end
173
+
174
+ unless api_server_command_line.index{|line| line =~ /--service-account-lookup=true/}
175
+ @results[target]['api_server']['CIS 1.1.24 - Ensure that the --service-account-lookup argument is set to true'] = "Fail"
176
+ else
177
+ @results[target]['api_server']['CIS 1.1.24 - Ensure that the --service-account-lookup argument is set to true'] = "Pass"
178
+ end
179
+
180
+ unless api_server_command_line.index{|line| line =~ /--admission-control\S*PodSecurityPolicy/}
181
+ @results[target]['api_server']['CIS 1.1.25 - Ensure that the admission control policy is set to PodSecurityPolicy'] = "Fail"
182
+ else
183
+ @results[target]['api_server']['CIS 1.1.25 - Ensure that the admission control policy is set to PodSecurityPolicy'] = "Pass"
184
+ end
185
+
186
+ unless api_server_command_line.index{|line| line =~ /--service-account-key-file/}
187
+ @results[target]['api_server']['CIS 1.1.26 - Ensure that the --service-account-key-file argument is set as appropriate'] = "Fail"
188
+ else
189
+ @results[target]['api_server']['CIS 1.1.26 - Ensure that the --service-account-key-file argument is set as appropriate'] = "Pass"
190
+ end
191
+
192
+ unless (api_server_command_line.index{|line| line =~ /--etcd-certfile/} && api_server_command_line.index{|line| line =~ /--etcd-keyfile/})
193
+ @results[target]['api_server']['CIS 1.1.27 - Ensure that the --etcd-certfile and --etcd-keyfile arguments are set as appropriate'] = "Fail"
194
+ else
195
+ @results[target]['api_server']['CIS 1.1.27 - Ensure that the --etcd-certfile and --etcd-keyfile arguments are set as appropriate'] = "Pass"
196
+ end
197
+
198
+ unless api_server_command_line.index{|line| line =~ /--admission-control\S*ServiceAccount/}
199
+ @results[target]['api_server']['CIS 1.1.28 - Ensure that the admission control policy is set to ServiceAccount'] = "Fail"
200
+ else
201
+ @results[target]['api_server']['CIS 1.1.28 - Ensure that the admission control policy is set to ServiceAccount'] = "Pass"
202
+ end
203
+
204
+ unless (api_server_command_line.index{|line| line =~ /--tls-cert-file/} && api_server_command_line.index{|line| line =~ /--tls-private-key-file/})
205
+ @results[target]['api_server']['CIS 1.1.29 - Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate'] = "Fail"
206
+ else
207
+ @results[target]['api_server']['CIS 1.1.29 - Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate'] = "Pass"
208
+ end
209
+
210
+ unless api_server_command_line.index{|line| line =~ /--client-ca-file/}
211
+ @results[target]['api_server']['CIS 1.1.30 - Ensure that the --client-ca-file argument is set as appropriate'] = "Fail"
212
+ else
213
+ @results[target]['api_server']['CIS 1.1.30 - Ensure that the --client-ca-file argument is set as appropriate'] = "Pass"
214
+ end
215
+
216
+ unless api_server_command_line.index{|line| line =~ /--etcd-cafile/}
217
+ @results[target]['api_server']['CIS 1.1.31 - Ensure that the --etcd-cafile argument is set as appropriate'] = "Fail"
218
+ else
219
+ @results[target]['api_server']['CIS 1.1.31 - Ensure that the --etcd-cafile argument is set as appropriate'] = "Pass"
220
+ end
221
+
222
+ @results[target]['evidence']['API Server'] = api_server_command_line
223
+ end
224
+
225
+ def self.test_scheduler
226
+ target = @options.target_server
227
+ @results[target]['scheduler'] = Hash.new
228
+ pods = @client.get_pods
229
+ pods.each do |pod|
230
+ #Ok this is a bit naive as a means of hitting the API server but hey it's a start
231
+ if pod['metadata']['name'] =~ /kube-scheduler/
232
+ @scheduler = pod
233
+ end
234
+ end
235
+
236
+ unless @scheduler
237
+ @results[target]['scheduler']['Scheduler Pod Not Found'] = "Error"
238
+ return
239
+ end
240
+
241
+ scheduler_command_line = @scheduler['spec']['containers'][0]['command']
242
+
243
+ unless scheduler_command_line.index{|line| line =~ /--profiling=false/}
244
+ @results[target]['scheduler']['CIS 1.2.1 - Ensure that the --profiling argument is set to false'] = "Fail"
245
+ else
246
+ @results[target]['scheduler']['CIS 1.2.1 - Ensure that the --profiling argument is set to false'] = "Pass"
247
+ end
248
+ @results[target]['evidence']['Scheduler'] = scheduler_command_line
249
+ end
250
+
251
+ def self.test_controller_manager
252
+ target = @options.target_server
253
+ @results[target]['controller_manager'] = Hash.new
254
+ pods = @client.get_pods
255
+ pods.each do |pod|
256
+ #Ok this is a bit naive as a means of hitting the API server but hey it's a start
257
+ if pod['metadata']['name'] =~ /kube-controller-manager/
258
+ @controller_manager = pod
259
+ end
260
+ end
261
+
262
+ unless @controller_manager
263
+ @results[target]['controller_manager']['Controller Manager Pod Not Found'] = "Error"
264
+ return
265
+ end
266
+
267
+
268
+ controller_manager_command_line = @controller_manager['spec']['containers'][0]['command']
269
+
270
+ unless controller_manager_command_line.index{|line| line =~ /--terminated-pod-gc-threshold/}
271
+ @results[target]['controller_manager']['CIS 1.3.1 - Ensure that the --terminated-pod-gc-threshold argument is set as appropriate'] = "Fail"
272
+ else
273
+ @results[target]['controller_manager']['CIS 1.3.1 - Ensure that the --terminated-pod-gc-threshold argument is set as appropriate'] = "Pass"
274
+ end
275
+
276
+ unless controller_manager_command_line.index{|line| line =~ /--profiling=false/}
277
+ @results[target]['controller_manager']['CIS 1.3.2 - Ensure that the --profiling argument is set to false'] = "Fail"
278
+ else
279
+ @results[target]['controller_manager']['CIS 1.3.2 - Ensure that the --profiling argument is set to false'] = "Pass"
280
+ end
281
+
282
+ if controller_manager_command_line.index{|line| line =~ /--insecure-experimental-approve-all-kubelet-csrs-for-group/}
283
+ @results[target]['controller_manager']['CIS 1.3.3 - Ensure that the --insecure-experimental-approve-all-kubelet-csrs-for-group argument is not set'] = "Fail"
284
+ else
285
+ @results[target]['controller_manager']['CIS 1.3.3 - Ensure that the --insecure-experimental-approve-all-kubelet-csrs-for-group argument is not set'] = "Pass"
286
+ end
287
+
288
+ unless controller_manager_command_line.index{|line| line =~ /--use-service-account-credentials=true/}
289
+ @results[target]['controller_manager']['CIS 1.3.4 - Ensure that the --use-service-account-credentials argument is set to true'] = "Fail"
290
+ else
291
+ @results[target]['controller_manager']['CIS 1.3.4 - Ensure that the --use-service-account-credentials argument is set to true'] = "Pass"
292
+ end
293
+
294
+ unless controller_manager_command_line.index{|line| line =~ /--service-account-private-key-file/}
295
+ @results[target]['controller_manager']['CIS 1.3.5 - Ensure that the --service-account-private-key-file argument is set as appropriate'] = "Fail"
296
+ else
297
+ @results[target]['controller_manager']['CIS 1.3.5 - Ensure that the --service-account-private-key-file argument is set as appropriate'] = "Pass"
298
+ end
299
+
300
+ unless controller_manager_command_line.index{|line| line =~ /--root-ca-file/}
301
+ @results[target]['controller_manager']['CIS 1.3.6 - Ensure that the --root-ca-file argument is set as appropriate'] = "Fail"
302
+ else
303
+ @results[target]['controller_manager']['CIS 1.3.6 - Ensure that the --root-ca-file argument is set as appropriate'] = "Pass"
304
+ end
305
+
306
+ @results[target]['evidence']['Controller Manager'] = controller_manager_command_line
307
+
308
+ end
309
+
310
+ def self.test_etcd
311
+ target = @options.target_server
312
+ @results[target]['etcd'] = Hash.new
313
+ pods = @client.get_pods
314
+ pods.each do |pod|
315
+ #Ok this is a bit naive as a means of hitting the API server but hey it's a start
316
+ if pod['metadata']['name'] =~ /etcd/
317
+ @etcd = pod
318
+ end
319
+ end
320
+
321
+ unless @etcd
322
+ @results[target]['etcd']['etcd Pod Not Found'] = "Error"
323
+ return
324
+ end
325
+
326
+ etcd_command_line = @etcd['spec']['containers'][0]['command']
327
+
328
+ unless (etcd_command_line.index{|line| line =~ /--cert-file/} && etcd_command_line.index{|line| line =~ /--key-file/})
329
+ @results[target]['etcd']['CIS 1.5.1 - Ensure that the --cert-file and --key-file arguments are set as appropriate'] = "Fail"
330
+ else
331
+ @results[target]['etcd']['CIS 1.5.1 - Ensure that the --cert-file and --key-file arguments are set as appropriate'] = "Pass"
332
+ end
333
+
334
+ unless etcd_command_line.index{|line| line =~ /--client-cert-auth=true/}
335
+ @results[target]['etcd']['CIS 1.5.2 - Ensure that the --client-cert-auth argument is set to true'] = "Fail"
336
+ else
337
+ @results[target]['etcd']['CIS 1.5.2 - Ensure that the --client-cert-auth argument is set to true'] = "Pass"
338
+ end
339
+
340
+ if etcd_command_line.index{|line| line =~ /--auto-tls argument=true/}
341
+ @results[target]['etcd']['CIS 1.5.3 - Ensure that the --auto-tls argument is not set to true'] = "Fail"
342
+ else
343
+ @results[target]['etcd']['CIS 1.5.3 - Ensure that the --auto-tls argument is not set to true'] = "Pass"
344
+ end
345
+
346
+ unless (etcd_command_line.index{|line| line =~ /--peer-cert-file/} && etcd_command_line.index{|line| line =~ /--peer-key-file/})
347
+ @results[target]['etcd']['CIS 1.5.4 - Ensure that the --peer-cert-file and --peer-key-file arguments are set as appropriate'] = "Fail"
348
+ else
349
+ @results[target]['etcd']['CIS 1.5.4 - Ensure that the --peer-cert-file and --peer-key-file arguments are set as appropriate'] = "Pass"
350
+ end
351
+
352
+ unless etcd_command_line.index{|line| line =~ /--peer-client-cert-auth=true/}
353
+ @results[target]['etcd']['CIS 1.5.5 - Ensure that the --peer-client-cert-auth argument is set to true'] = "Fail"
354
+ else
355
+ @results[target]['etcd']['CIS 1.5.5 - Ensure that the --peer-client-cert-auth argument is set to true'] = "Pass"
356
+ end
357
+
358
+ if etcd_command_line.index{|line| line =~ /--peer-auto-tls argument=true/}
359
+ @results[target]['etcd']['CIS 1.5.6 - Ensure that the --peer-auto-tls argument is not set to true'] = "Fail"
360
+ else
361
+ @results[target]['etcd']['CIS 1.5.6 - Ensure that the --peer-auto-tls argument is not set to true'] = "Pass"
362
+ end
363
+
364
+
365
+
366
+ @results[target]['evidence']['etcd'] = etcd_command_line
367
+ end
368
+ end
@@ -0,0 +1 @@
1
+ data:image/png;base64,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
@@ -0,0 +1,211 @@
1
+ module KubeAutoAnalyzer
2
+ def self.report
3
+ @log.debug("Starting Report")
4
+ @report_file.puts "Kubernetes Analyzer"
5
+ @report_file.puts "===================\n\n"
6
+ @report_file.puts "**Server Reviewed** : #{@options.target_server}"
7
+ @report_file.puts "\n\nAPI Server Results"
8
+ @report_file.puts "----------------------\n\n"
9
+ @results[@options.target_server]['api_server'].each do |test, result|
10
+ @report_file.puts '* ' + test + ' - **' + result + '**'
11
+ end
12
+ @report_file.puts "\n\nScheduler Results"
13
+ @report_file.puts "----------------------\n\n"
14
+ @results[@options.target_server]['scheduler'].each do |test, result|
15
+ @report_file.puts '* ' + test + ' - **' + result + '**'
16
+ end
17
+
18
+ @report_file.puts "\n\nController Manager Results"
19
+ @report_file.puts "----------------------\n\n"
20
+ @results[@options.target_server]['controller_manager'].each do |test, result|
21
+ @report_file.puts '* ' + test + ' - **' + result + '**'
22
+ end
23
+
24
+ @report_file.puts "\n\netcd Results"
25
+ @report_file.puts "----------------------\n\n"
26
+ @results[@options.target_server]['etcd'].each do |test, result|
27
+ @report_file.puts '* ' + test + ' - **' + result + '**'
28
+ end
29
+ if @options.agent_file_checks
30
+ @report_file.puts "\n\nWorker Nodes File Permissions"
31
+ @report_file.puts "----------------------\n\n"
32
+ @log.debug("Class is #{@results[@options.target_server]['worker_files'].class}")
33
+ @results[@options.target_server]['worker_files'].each do |node, results|
34
+ @report_file.puts "\n\n#{node}\n"
35
+ results.each do |file|
36
+ @report_file.puts file.join(', ')
37
+ end
38
+ end
39
+ end
40
+
41
+ @report_file.puts "\n\nEvidence"
42
+ @report_file.puts "---------------\n\n"
43
+ @report_file.puts ' ' + @results[@options.target_server]['evidence']['API Server'].to_s
44
+ @report_file.puts "---------------\n\n"
45
+ @report_file.puts ' ' + @results[@options.target_server]['evidence']['Scheduler'].to_s
46
+ @report_file.puts "---------------\n\n"
47
+ @report_file.puts ' ' + @results[@options.target_server]['evidence']['Controller Manager'].to_s
48
+ @report_file.puts "---------------\n\n"
49
+ @report_file.puts ' ' + @results[@options.target_server]['evidence']['etcd'].to_s
50
+ @report_file.close
51
+ end
52
+
53
+ def self.html_report
54
+ base_report = File.open(@report_file_name + '.txt','r').read
55
+ logo_path = File.join(__dir__, "data-logo.b64")
56
+ logo = File.open(logo_path).read
57
+ @log.debug("Starting HTML Report")
58
+ @html_report_file << '
59
+ <!DOCTYPE html>
60
+ <head>
61
+ <title> Kubernetes Auto Analyzer Report</title>
62
+ <meta charset="utf-8">
63
+ <style>
64
+ body {
65
+ font: normal 14px;
66
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
67
+ color: #C41230;
68
+ background: #FFFFFF;
69
+ }
70
+ #kubernetes-analyzer {
71
+ font-weight: bold;
72
+ font-size: 48px;
73
+ color: #C41230;
74
+ }
75
+ .master-node, .worker-node {
76
+ background: #F5F5F5;
77
+ border: 1px solid black;
78
+ padding-left: 6px;
79
+ }
80
+ #api-server-results {
81
+ font-weight: italic;
82
+ font-size: 36px;
83
+ color: #C41230;
84
+ }
85
+ table, th, td {
86
+ border-collapse: collapse;
87
+ border: 1px solid black;
88
+ }
89
+ th {
90
+ font: bold 11px;
91
+ color: #C41230;
92
+ background: #999999;
93
+ letter-spacing: 2px;
94
+ text-transform: uppercase;
95
+ text-align: left;
96
+ padding: 6px 6px 6px 12px;
97
+ }
98
+ td {
99
+ background: #FFFFFF;
100
+ padding: 6px 6px 6px 12px;
101
+ color: #333333;
102
+ }
103
+ </style>
104
+ </head>
105
+ <body>
106
+
107
+ '
108
+ @html_report_file.puts '<img width="100" height="100" align="right"' + " src=#{logo} />"
109
+ @html_report_file.puts "<h1>Kubernetes Auto Analyzer</h1>"
110
+ @html_report_file.puts "<br><b>Server Reviewed : </b> #{@options.target_server}"
111
+ @html_report_file.puts '<br><br><div class="master-node"><h2>Master Node Results</h2><br>'
112
+ @html_report_file.puts "<h2>API Server</h2>"
113
+ @html_report_file.puts "<table><thead><tr><th>Check</th><th>result</th></tr></thead>"
114
+ @results[@options.target_server]['api_server'].each do |test, result|
115
+ if result == "Fail"
116
+ result = '<span style="color:red;">Fail</span>'
117
+ elsif result == "Pass"
118
+ result = '<span style="color:green;">Pass</span>'
119
+ end
120
+ @html_report_file.puts "<tr><td>#{test}</td><td>#{result}</td></tr>"
121
+ end
122
+ @html_report_file.puts "</table>"
123
+ @html_report_file.puts "<br><br>"
124
+ @html_report_file.puts "<br><br><h2>Scheduler</h2>"
125
+ @html_report_file.puts "<table><thead><tr><th>Check</th><th>result</th></tr></thead>"
126
+ @results[@options.target_server]['scheduler'].each do |test, result|
127
+ if result == "Fail"
128
+ result = '<span style="color:red;">Fail</span>'
129
+ elsif result == "Pass"
130
+ result = '<span style="color:green;">Pass</span>'
131
+ end
132
+ @html_report_file.puts "<tr><td>#{test}</td><td>#{result}</td></tr>"
133
+ end
134
+ @html_report_file.puts "</table>"
135
+
136
+ @html_report_file.puts "<br><br>"
137
+ @html_report_file.puts "<br><br><h2>Controller Manager</h2>"
138
+ @html_report_file.puts "<table><thead><tr><th>Check</th><th>result</th></tr></thead>"
139
+ @results[@options.target_server]['controller_manager'].each do |test, result|
140
+ if result == "Fail"
141
+ result = '<span style="color:red;">Fail</span>'
142
+ elsif result == "Pass"
143
+ result = '<span style="color:green;">Pass</span>'
144
+ end
145
+ @html_report_file.puts "<tr><td>#{test}</td><td>#{result}</td></tr>"
146
+ end
147
+ @html_report_file.puts "</table>"
148
+
149
+ @html_report_file.puts "<br><br>"
150
+ @html_report_file.puts "<br><br><h2>etcd</h2>"
151
+ @html_report_file.puts "<table><thead><tr><th>Check</th><th>result</th></tr></thead>"
152
+ @results[@options.target_server]['etcd'].each do |test, result|
153
+ if result == "Fail"
154
+ result = '<span style="color:red;">Fail</span>'
155
+ elsif result == "Pass"
156
+ result = '<span style="color:green;">Pass</span>'
157
+ end
158
+ @html_report_file.puts "<tr><td>#{test}</td><td>#{result}</td></tr>"
159
+ end
160
+ @html_report_file.puts "</table>"
161
+
162
+ @html_report_file.puts "<br><br><h2>Evidence</h2><br>"
163
+ @html_report_file.puts "<table><thead><tr><th>Area</th><th>Output</th></tr></thead>"
164
+ @results[@options.target_server]['evidence'].each do |area, output|
165
+ @html_report_file.puts "<tr><td>#{area}</td><td>#{output}</td></tr>"
166
+ end
167
+ #Close the master Node Div
168
+ @html_report_file.puts "</table></div>"
169
+ @html_report_file.puts '<br><br><div class="worker-node"><h2>Worker Node Results</h2>'
170
+ if @options.agent_process_checks
171
+ @results[@options.target_server]['kubelet_checks'].each do |node, results|
172
+ @html_report_file.puts "<br><b>#{node} Kubelet Checks</b>"
173
+ @html_report_file.puts "<table><thead><tr><th>Check</th><th>result</th></tr></thead>"
174
+ results.each do |test, result|
175
+ if result == "Fail"
176
+ result = '<span style="color:red;">Fail</span>'
177
+ elsif result == "Pass"
178
+ result = '<span style="color:green;">Pass</span>'
179
+ end
180
+ @html_report_file.puts "<tr><td>#{test}</td><td>#{result}</td></tr>"
181
+ end
182
+ @html_report_file.puts "</table>"
183
+ end
184
+
185
+ @html_report_file.puts "<br><br><h2>Evidence</h2><br>"
186
+ @html_report_file.puts "<table><thead><tr><th>Host</th><th>Area</th><th>Output</th></tr></thead>"
187
+ @results[@options.target_server]['node_evidence'].each do |node, evidence|
188
+ evidence.each do |area, data|
189
+ @html_report_file.puts "<tr><td>#{node}</td><td>#{area}</td><td>#{data}</td></tr>"
190
+ end
191
+ end
192
+ @html_report_file.puts "</table>"
193
+
194
+ end
195
+ #Close the Worker Node Div
196
+ @html_report_file.puts '</div>'
197
+ if @options.agent_file_checks
198
+ @html_report_file.puts '<br><h2>File Permissions</h2>'
199
+ @results[@options.target_server]['worker_files'].each do |node, results|
200
+ @html_report_file.puts "<br><b>#{node}</b><br>"
201
+ @html_report_file.puts "<table><thead><tr><th>file</th><th>user</th><th>group</th><th>permissions</th></thead>"
202
+ results.each do |file|
203
+ @html_report_file.puts "<tr><td>#{file[0]}</td><td>#{file[1]}</td><td>#{file[2]}</td><td>#{file[3]}</td></tr>"
204
+ end
205
+ @html_report_file.puts "</table>"
206
+ end
207
+ end
208
+
209
+ @html_report_file.puts '</body></html>'
210
+ end
211
+ end
@@ -0,0 +1,3 @@
1
+ module KubeAutoAnalyzer
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,90 @@
1
+ module KubeAutoAnalyzer
2
+ attr_accessor :execute
3
+ require "kube_auto_analyzer/version"
4
+ require "kube_auto_analyzer/api_checks/master_node"
5
+ require "kube_auto_analyzer/reporting"
6
+ require "kube_auto_analyzer/agent_checks/file_checks"
7
+ require "kube_auto_analyzer/agent_checks/process_checks"
8
+
9
+
10
+ def self.execute(commmand_line_opts)
11
+ @options = commmand_line_opts
12
+ require 'logger'
13
+ begin
14
+ require 'kubeclient'
15
+ rescue LoadError
16
+ puts "You need to install kubeclient for this, try 'gem install kubeclient'"
17
+ exit
18
+ end
19
+
20
+ @base_dir = @options.report_directory
21
+ if !File.exists?(@base_dir)
22
+ Dir.mkdirs(@base_dir)
23
+ end
24
+
25
+ @log = Logger.new(@base_dir + '/kube-analyzer-log.txt')
26
+ @log.level = Logger::DEBUG
27
+ @log.debug("Log created at " + Time.now.to_s)
28
+ @log.debug("Target API Server is " + @options.target_server)
29
+
30
+ @report_file_name = @base_dir + '/' + @options.report_file
31
+ @report_file = File.new(@report_file_name + '.txt','w+')
32
+ @html_report_file = File.new(@report_file_name + '.html','w+')
33
+ @log.debug("New Report File created #{@report_file_name}")
34
+
35
+ @results = Hash.new
36
+ #TODO: Expose this as an option rather than hard-code to off
37
+ unless @options.config_file
38
+ ssl_options = { verify_ssl: OpenSSL::SSL::VERIFY_NONE}
39
+ #TODO: Need to setup the other authentication options
40
+ if @options.token.length > 1
41
+ auth_options = { bearer_token: @options.token}
42
+ elsif @options.token_file.length > 1
43
+ auth_options = { bearer_token_file: @options.token_file}
44
+ else
45
+ #Not sure this will actually work for no auth. needed, try and ooold cluster to check
46
+ auth_options = {}
47
+ end
48
+ @results[@options.target_server] = Hash.new
49
+ @client = Kubeclient::Client.new @options.target_server, 'v1', auth_options: auth_options, ssl_options: ssl_options
50
+ else
51
+ begin
52
+ config = Kubeclient::Config.read(@options.config_file)
53
+ rescue Errno::ENOENT
54
+ puts "Config File could not be read, check the path?"
55
+ exit
56
+ end
57
+ @client = Kubeclient::Client.new(
58
+ config.context.api_endpoint,
59
+ config.context.api_version,
60
+ {
61
+ ssl_options: config.context.ssl_options,
62
+ auth_options: config.context.auth_options
63
+ }
64
+ )
65
+ #We didn't specify the target on the command line so lets get it from the config file
66
+ @options.target_server = config.context.api_endpoint
67
+ @results[config.context.api_endpoint] = Hash.new
68
+ end
69
+ #Test response
70
+ begin
71
+ @client.get_pods.to_s
72
+ rescue
73
+ puts "whoops that didn't go well"
74
+ exit
75
+ end
76
+ test_api_server
77
+ test_scheduler
78
+ test_controller_manager
79
+ test_etcd
80
+ if @options.agent_file_checks
81
+ check_files
82
+ end
83
+ if @options.agent_process_checks
84
+ check_kubelet_process
85
+ end
86
+
87
+ report
88
+ html_report
89
+ end
90
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kube_auto_analyzer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Rory McCune
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-05-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: kubeclient
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 2.4.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 2.4.0
55
+ description: This is a gem used to help when conducting a security analysis of a Kubernetes
56
+ cluster in-line with the requirements of the CIS Benchmark.
57
+ email:
58
+ - rory.mccune@nccgroup.trust
59
+ executables:
60
+ - kubeautoanalyzer
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - Gemfile
65
+ - bin/kubeautoanalyzer
66
+ - kube_auto_analyzer.gemspec
67
+ - lib/kube_auto_analyzer.rb
68
+ - lib/kube_auto_analyzer/agent_checks/file_checks.rb
69
+ - lib/kube_auto_analyzer/agent_checks/process_checks.rb
70
+ - lib/kube_auto_analyzer/api_checks/master_node.rb
71
+ - lib/kube_auto_analyzer/data-logo.b64
72
+ - lib/kube_auto_analyzer/reporting.rb
73
+ - lib/kube_auto_analyzer/version.rb
74
+ homepage: https://github.com/nccgroup/kube-auto-analyzer
75
+ licenses:
76
+ - AGPL
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 2.2.2
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: A Gem which provides a script and class analyze the security of a Kubernetes
98
+ cluster.
99
+ test_files: []
100
+ has_rdoc: