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 +7 -0
- data/Gemfile +3 -0
- data/bin/kubeautoanalyzer +73 -0
- data/kube_auto_analyzer.gemspec +24 -0
- data/lib/kube_auto_analyzer/agent_checks/file_checks.rb +51 -0
- data/lib/kube_auto_analyzer/agent_checks/process_checks.rb +145 -0
- data/lib/kube_auto_analyzer/api_checks/master_node.rb +368 -0
- data/lib/kube_auto_analyzer/data-logo.b64 +1 -0
- data/lib/kube_auto_analyzer/reporting.rb +211 -0
- data/lib/kube_auto_analyzer/version.rb +3 -0
- data/lib/kube_auto_analyzer.rb +90 -0
- metadata +100 -0
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,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,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:
|