module KubeAutoAnalyzer def self.json_report require 'json' @log.debug("Starting Report") @json_report_file.puts JSON.generate(@results) end def self.html_report logo_path = File.join(__dir__, "data-logo.b64") logo = File.open(logo_path).read @log.debug("Starting HTML Report") @html_report_file << ' Kubernetes Auto Analyzer Report ' @html_report_file.puts '" @html_report_file.puts "

Kubernetes Auto Analyzer

" @html_report_file.puts "
Server Reviewed : #{@options.target_server}" if @options.cis_audit chartkick_path = File.join(__dir__, "js_files/chartkick.js") chartkick = File.open(chartkick_path).read highcharts_path = File.join(__dir__, "js_files/highcharts.js") highcharts = File.open(highcharts_path).read @html_report_file.puts "" @html_report_file.puts "" @html_report_file.puts '

Master Node Results


' #Charting setup counts for the passes and fails api_server_pass = 0 api_server_fail = 0 @results[@options.target_server]['api_server'].each do |test, result| if result == "Pass" api_server_pass = api_server_pass + 1 elsif result == "Fail" api_server_fail = api_server_fail + 1 end end #Not a lot of point in scheduler when there's only one check... #scheduler_pass = 0 #scheduler_fail = 0 #@results[@options.target_server]['scheduler'].each do |test, result| # if result == "Pass" # scheduler_pass = scheduler_pass + 1 # elsif result == "Fail" # scheduler_fail = scheduler_fail + 1 # end #end controller_manager_pass = 0 controller_manager_fail = 0 @results[@options.target_server]['controller_manager'].each do |test, result| if result == "Pass" controller_manager_pass = controller_manager_pass + 1 elsif result == "Fail" controller_manager_fail = controller_manager_fail + 1 end end etcd_pass = 0 etcd_fail = 0 @results[@options.target_server]['etcd'].each do |test, result| if result == "Pass" etcd_pass = etcd_pass + 1 elsif result == "Fail" etcd_fail = etcd_fail + 1 end end #Start of Chart Divs @html_report_file.puts '
' #API Server Chart @html_report_file.puts '
' @html_report_file.puts '' #Scheduler Chart #@html_report_file.puts '
' #@html_report_file.puts '' #Controller Manager Chart @html_report_file.puts '
' @html_report_file.puts '' #etcd Chart @html_report_file.puts '
' @html_report_file.puts '' #End of Chart Divs @html_report_file.puts '
' @html_report_file.puts "

API Server

" @html_report_file.puts "" @results[@options.target_server]['api_server'].each do |test, result| if result == "Fail" result = 'Fail' elsif result == "Pass" result = 'Pass' end @html_report_file.puts "" end @html_report_file.puts "
Checkresult
#{test}#{result}
" @html_report_file.puts "

" @html_report_file.puts "

Scheduler

" @html_report_file.puts "" @results[@options.target_server]['scheduler'].each do |test, result| if result == "Fail" result = 'Fail' elsif result == "Pass" result = 'Pass' end @html_report_file.puts "" end @html_report_file.puts "
Checkresult
#{test}#{result}
" @html_report_file.puts "

" @html_report_file.puts "

Controller Manager

" @html_report_file.puts "" @results[@options.target_server]['controller_manager'].each do |test, result| if result == "Fail" result = 'Fail' elsif result == "Pass" result = 'Pass' end @html_report_file.puts "" end @html_report_file.puts "
Checkresult
#{test}#{result}
" @html_report_file.puts "

" @html_report_file.puts "

etcd

" @html_report_file.puts "" @results[@options.target_server]['etcd'].each do |test, result| if result == "Fail" result = 'Fail' elsif result == "Pass" result = 'Pass' end @html_report_file.puts "" end @html_report_file.puts "
Checkresult
#{test}#{result}
" #Close the master Node Div @html_report_file.puts "
" end if @options.agent_checks @html_report_file.puts '

Worker Node Results

' #Start of Chart Divs @html_report_file.puts '
' @results[@options.target_server]['kubelet_checks'].each do |node, results| node_kubelet_pass = 0 node_kubelet_fail = 0 results.each do |test, result| if result == "Fail" node_kubelet_fail = node_kubelet_fail + 1 elsif result == "Pass" node_kubelet_pass = node_kubelet_pass + 1 end end #Create the Chart @html_report_file.puts '
' @html_report_file.puts '' end #End of Chart Divs @html_report_file.puts '
' @results[@options.target_server]['kubelet_checks'].each do |node, results| @html_report_file.puts "
#{node} Kubelet Checks" @html_report_file.puts "" results.each do |test, result| if result == "Fail" result = 'Fail' elsif result == "Pass" result = 'Pass' end @html_report_file.puts "" end @html_report_file.puts "
Checkresult
#{test}#{result}
" end @html_report_file.puts "

Evidence


" @html_report_file.puts "" @results[@options.target_server]['node_evidence'].each do |node, evidence| evidence.each do |area, data| @html_report_file.puts "" end end @html_report_file.puts "
HostAreaOutput
#{node}#{area}#{data}
" end #Close the Worker Node Div @html_report_file.puts '
' if @options.agent_checks @html_report_file.puts '

Node File Permissions

' @results[@options.target_server]['node_files'].each do |node, results| @html_report_file.puts "
#{node}
" @html_report_file.puts "" results.each do |file| @html_report_file.puts "" end @html_report_file.puts "
fileusergrouppermissions
#{file[0]}#{file[1]}#{file[2]}#{file[3]}
" end end @html_report_file.puts '

Vulnerability Checks

' @html_report_file.puts '

External Unauthenticated Access to the Kubelet

' @html_report_file.puts "" @results[@options.target_server]['vulns']['unauth_kubelet'].each do |node, result| unless (result =~ /Forbidden/ || result =~ /Not Open/ || result =~ /Unauthorized/) output = "Vulnerable" else output = result end @html_report_file.puts "" end @html_report_file.puts "
Node IP AddressResult
#{node}#{output}
" if @options.agent_checks @html_report_file.puts '

Internal Unauthenticated Access to the Kubelet

' @html_report_file.puts "" @results[@options.target_server]['vulns']['internal_kubelet'].each do |node, result| unless (result =~ /Forbidden/ || result =~ /Not Open/) output = "Vulnerable" else output = result end @html_report_file.puts "" end @html_report_file.puts "
Node IP AddressResult
#{node}#{output}
" end @html_report_file.puts '

External Insecure API Port Exposed

' @html_report_file.puts "" @results[@options.target_server]['vulns']['insecure_api_external'].each do |node, result| unless (result =~ /Forbidden/ || result =~ /Not Open/) output = "Vulnerable" else output = result end @html_report_file.puts "" end @html_report_file.puts "
Node IP AddressResult
#{node}#{output}
" if @options.agent_checks @html_report_file.puts '

Internal Insecure API Port Exposed

' @html_report_file.puts "" @results[@options.target_server]['vulns']['insecure_api_internal'].each do |node, result| unless (result =~ /Forbidden/ || result =~ /Not Open/) output = "Vulnerable" else output = result end @html_report_file.puts "" end @html_report_file.puts "
Node IP AddressResult
#{node}#{output}
" end if @options.agent_checks @html_report_file.puts '

Default Service Token In Use

' @html_report_file.puts "" @results[@options.target_server]['vulns']['service_token'].each do |node, result| unless (result =~ /Forbidden/ || result =~ /Not Open/) output = "Vulnerable" else output = result end @html_report_file.puts "" end @html_report_file.puts "
API endpointResult
#{node}#{output}
" end if @options.agent_checks @html_report_file.puts '

Container Configuration checks

' @results[@options.target_server]['vulns']['amicontained'].each do |node, result| @html_report_file.puts "
#{node} Container Checks" @html_report_file.puts "" @html_report_file.puts "" @html_report_file.puts "" @html_report_file.puts "" @html_report_file.puts "" @html_report_file.puts "" @html_report_file.puts "" @html_report_file.puts "" @html_report_file.puts "" @html_report_file.puts "
Container itemResult
Runtime in Use#{result['runtime']}
Host PID namespace used?#{result['hostpid']}
AppArmor Profile#{result['apparmor']}
User Namespaces in use?#{result['uid_map']}
Inherited Capabilities#{result['cap_inh']}
Effective Capabilities#{result['cap_eff']}
Permitted Capabilities#{result['cap_per']}
Bounded Capabilities#{result['cap_bnd']}
" end end @html_report_file.puts "

Vulnerability Evidence


" @html_report_file.puts "" @results[@options.target_server]['vulns']['unauth_kubelet'].each do |node, result| @html_report_file.puts "" end if @options.agent_checks @results[@options.target_server]['vulns']['internal_kubelet'].each do |node, result| @html_report_file.puts "" end end @results[@options.target_server]['vulns']['insecure_api_external'].each do |node, result| @html_report_file.puts "" end if @options.agent_checks @results[@options.target_server]['vulns']['insecure_api_internal'].each do |node, result| @html_report_file.puts "" end end if @options.agent_checks @results[@options.target_server]['vulns']['service_token'].each do |node, result| @html_report_file.puts "" end end if @options.agent_checks @results[@options.target_server]['vulns']['amicontained'].each do |node, result| @html_report_file.puts "" end end @html_report_file.puts "
VulnerabilityHostOutput
External Unauthenticated Kubelet Access#{node}#{result}
Internal Unauthenticated Kubelet Access#{node}#{result}
External Insecure API Server Access#{node}#{result}
Internal Insecure API Server Access#{node}#{result}
Default Service Token In Use#{node}#{result}
Am I Contained Output#{node}#{result}
" #Show what cluster authentication modes are supported. @html_report_file.puts "

Kubernetes Cluster Information

" @html_report_file.puts "

Kubernetes Authentication Options

" @html_report_file.puts "" if @results[@options.target_server][:authn][:basic] == true @html_report_file.puts "" else @html_report_file.puts "" end if @results[@options.target_server][:authn][:token] == true @html_report_file.puts "" else @html_report_file.puts "" end if @results[@options.target_server][:authn][:certificate] == true @html_report_file.puts "" else @html_report_file.puts "" end if @results[@options.target_server][:authn][:oidc] == true @html_report_file.puts "" else @html_report_file.puts "" end if @results[@options.target_server][:authn][:webhook] == true @html_report_file.puts "" else @html_report_file.puts "" end if @results[@options.target_server][:authn][:proxy] == true @html_report_file.puts "" else @html_report_file.puts "" end @html_report_file.puts "
Authentication OptionEnabled?
Basic AuthenticationEnabled
Basic AuthenticationDisabled
Token AuthenticationEnabled
Token AuthenticationDisabled
Client Certificate AuthenticationEnabled
Client Certificate AuthenticationDisabled
OpenID Connect AuthenticationEnabled
OpenID Connect AuthenticationDisabled
Webhook AuthenticationEnabled
Webhook AuthenticationDisabled
Proxy AuthenticationEnabled
Proxy AuthenticationDisabled
" #Show what cluster authorization modes are supported. @html_report_file.puts "

" @html_report_file.puts "

Kubernetes Authorization Options

" @html_report_file.puts "" if @results[@options.target_server][:authz][:rbac] == true @html_report_file.puts "" else @html_report_file.puts "" end if @results[@options.target_server][:authz][:abac] == true @html_report_file.puts "" else @html_report_file.puts "" end if @results[@options.target_server][:authz][:webhook] == true @html_report_file.puts "" else @html_report_file.puts "" end @html_report_file.puts "
Authorization OptionEnabled?
Role Based AuthorizationEnabled
Role Based AuthorizationDisabled
Attribute Based AuthorizationEnabled
Attribute Based AuthorizationDisabled
Webhook AuthorizationEnabled
Webhook AuthorizationDisabled
" @html_report_file.puts "

Evidence


" @html_report_file.puts "" @results[@options.target_server]['evidence'].each do |area, output| @html_report_file.puts "" end @html_report_file.puts "
AreaOutput
#{area}#{output}
" #Only show this section if we were asked to dump the config if @options.dump_config @html_report_file.puts "

" @html_report_file.puts "

Cluster Config Information

" @html_report_file.puts "" @results[@options.target_server][:config][:docker_images].each do |image| @html_report_file.puts "" end @html_report_file.puts "
Docker Images In Use
#{image}
" @html_report_file.puts "

" @html_report_file.puts "" @results[@options.target_server][:config][:pod_info].each do |pod| @html_report_file.puts "" end @html_report_file.puts "
Pod NameNamespaceService AccountHost IPPod IP
#{pod[:name]}#{pod[:namespace]}#{pod[:service_account]}#{pod[:host_ip]}#{pod[:pod_ip]}
" @html_report_file.puts "

" @html_report_file.puts "

" @html_report_file.puts "" @results[@options.target_server][:config][:service_info].each do |service| @html_report_file.puts "" end @html_report_file.puts "
Service NameCluster IPExternal IPPort:Target Port
#{service[:name]}#{service[:cluster_ip]}#{service[:external_ip]}#{service[:ports].join('
')}
" @html_report_file.puts "

" end #Only show this section if we were asked to dump RBAC if @options.audit_rbac @html_report_file.puts "

" @html_report_file.puts "

Cluster Role Information

" @html_report_file.puts "" @results[@options.target_server][:rbac][:cluster_roles].each do |name, info| subjects = '' info[:subjects].each do |subject| subjects << "#{subject[:kind]}:#{subject[:namespace]}:#{subject[:name]}
" end rules = '' info[:rules].each do |rule| unless rule.verbs rule.verbs = Array.new end unless rule.apiGroups rule.apiGroups = Array.new end unless rule.resources rule.resources = Array.new end rules << "Verbs : #{rule.verbs.join(', ')}
API Groups : #{rule.apiGroups.join(', ')}
Resources : #{rule.resources.join(', ')}

" end @html_report_file.puts "" end @html_report_file.puts "
NameDefault?SubjectsRules
#{name}#{info[:default]}#{subjects}#{rules}
" @html_report_file.puts "

" end #Closing the report off @html_report_file.puts '' end end