Module: KubeAutoAnalyzer
- Defined in:
- lib/kube_auto_analyzer.rb,
lib/kube_auto_analyzer/version.rb,
lib/kube_auto_analyzer/reporting.rb,
lib/kube_auto_analyzer/utility/network.rb,
lib/kube_auto_analyzer/vuln_checks/kubelet.rb,
lib/kube_auto_analyzer/api_checks/master_node.rb,
lib/kube_auto_analyzer/vuln_checks/api_server.rb,
lib/kube_auto_analyzer/api_checks/rbac_auditor.rb,
lib/kube_auto_analyzer/agent_checks/file_checks.rb,
lib/kube_auto_analyzer/api_checks/config_dumper.rb,
lib/kube_auto_analyzer/vuln_checks/amicontained.rb,
lib/kube_auto_analyzer/vuln_checks/service_token.rb,
lib/kube_auto_analyzer/agent_checks/process_checks.rb,
lib/kube_auto_analyzer/api_checks/authorization_checker.rb,
lib/kube_auto_analyzer/api_checks/authentication_checker.rb
Constant Summary collapse
- VERSION =
"0.0.17"
Instance Attribute Summary collapse
-
#execute ⇒ Object
Returns the value of attribute execute.
Class Method Summary collapse
- .audit_rbac ⇒ Object
-
.check_amicontained ⇒ Object
This is somewhat awkward placement.
- .check_authn ⇒ Object
- .check_authz ⇒ Object
- .check_files ⇒ Object
- .check_kubelet_process ⇒ Object
- .dump_config ⇒ Object
- .execute(commmand_line_opts) ⇒ Object
- .html_report ⇒ Object
- .is_port_open?(ip, port) ⇒ Boolean
- .json_report ⇒ Object
- .test_api_server ⇒ Object
- .test_controller_manager ⇒ Object
- .test_etcd ⇒ Object
- .test_insecure_api_external ⇒ Object
-
.test_insecure_api_internal ⇒ Object
This is somewhat awkward placement.
- .test_scheduler ⇒ Object
-
.test_service_token_internal ⇒ Object
This is somewhat awkward placement.
- .test_unauth_kubelet_external ⇒ Object
-
.test_unauth_kubelet_internal ⇒ Object
This is somewhat awkward placement.
Instance Attribute Details
#execute ⇒ Object
Returns the value of attribute execute.
2 3 4 |
# File 'lib/kube_auto_analyzer.rb', line 2 def execute @execute end |
Class Method Details
.audit_rbac ⇒ Object
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/kube_auto_analyzer/api_checks/rbac_auditor.rb', line 2 def self.audit_rbac @log.debug("Entering the RBAC Auditor") target = @options.target_server @log.debug("Auditing RBAC on #{target}") @results[target][:rbac] = Hash.new cluster_roles = @rbac_client.get_cluster_roles @log.debug("got #{cluster_roles.length.to_s} cluster roles") cluster_role_bindings = @rbac_client.get_cluster_role_bindings @log.debug("got #{cluster_role_bindings.length.to_s} cluster role bindings") @results[target][:rbac][:cluster_roles] = Hash.new cluster_roles.each do |role| role_output = Hash.new role_output[:rules] = role.rules @log.debug("metadata in #{role.[:name]} , #{role.}") begin if role.[:labels]['kubernetes.io/bootstrapping'] == "rbac-defaults" role_output[:default] = true else role_output[:default] = false end rescue NoMethodError #If there's no method, it can't be a default... role_output[:default] = false end role_output[:subjects] = Array.new cluster_role_bindings.each do |binding| #So we're testing if the binding has any subjects and if so whether they apply to this role or not if binding.subjects @log.debug("#{binding.roleRef[:name]} binding has #{binding.subjects.length.to_s} bindings") else @log.debug("#{binding.roleRef[:name]} has no subjects") end @log.debug(binding.roleRef[:kind] + ", " + role.[:name] + ", " + binding.roleRef[:name] + ", " + (binding.subjects ? binding.subjects.length.to_s : "0") ) if binding.roleRef[:kind] == "ClusterRole" @log.debug("Matched the cluster role") if binding.roleRef[:name] == role.[:name] @log.debug("matched the role name") if binding.subjects binding.subjects.each do |subject| @log.debug("added a subject to the list") role_output[:subjects] << subject end end end end end @results[target][:rbac][:cluster_roles][role.[:name]] = role_output end end |
.check_amicontained ⇒ Object
This is somewhat awkward placement. Deployment mechanism sits more with the agent checks But from a “what it’s looking for” perspective, its more with the vuln. checks as there’s not a CIS check for it.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/kube_auto_analyzer/vuln_checks/amicontained.rb', line 5 def self.check_amicontained require 'json' @log.debug("Doing Am I contained check") target = @options.target_server @results[target]['vulns']['amicontained'] = Hash.new nodes = Array.new @client.get_nodes.each do |node| nodes << node end nodes.each do |nod| node_hostname = nod..labels['kubernetes.io/hostname'] node_ip = nod['status']['addresses'][0]['address'] container_name = "kaa" + node_hostname pod = Kubeclient::Resource.new pod. = {} pod..name = container_name pod..namespace = "default" pod.spec = {} pod.spec.restartPolicy = "Never" pod.spec.containers = {} pod.spec.containers = [{name: "kubeautoanalyzerkubelettest", image: "raesene/kaa-agent:latest"}] pod.spec.containers[0].args = ["/amicontained.rb"] #Try the Toleration for Master pod.spec.tolerations = {} #pod.spec.tolerations = [{ key:"key", operator:"Equal", value:"value",effect:"NoSchedule"}] pod.spec.tolerations = [{ operator:"Exists" }] pod.spec.nodeselector = {} pod.spec.nodeselector['kubernetes.io/hostname'] = node_hostname begin @log.debug("About to start amicontained pod") @client.create_pod(pod) @log.debug("Executed the create pod") begin sleep(5) until @client.get_pod(container_name,"default")['status']['containerStatuses'][0]['state']['terminated']['reason'] == "Completed" rescue retry end @log.debug ("started amicontained pod") results = JSON.parse(@client.get_pod_log(container_name,"default")) @results[target]['vulns']['amicontained'][node_ip] = results ensure @client.delete_pod(container_name,"default") end end end |
.check_authn ⇒ Object
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/kube_auto_analyzer/api_checks/authentication_checker.rb', line 2 def self.check_authn @log.debug("Entering the Authentication Checker") target = @options.target_server @log.debug("Checking enabled Authentication Options on #{target}") @results[target][:authn] = Hash.new @results[target]['evidence'] = Hash.new pods = @client.get_pods pods.each do |pod| if pod['metadata']['name'] =~ /kube-apiserver/ @api_server = pod end end api_server_command_line = @api_server['spec']['containers'][0]['command'] if api_server_command_line.index{|line| line =~ /--basic-auth-file/} @results[target][:authn][:basic] = true else @results[target][:authn][:basic] = false end if api_server_command_line.index{|line| line =~ /--token-auth-file/} @results[target][:authn][:token] = true else @results[target][:authn][:token] = false end if api_server_command_line.index{|line| line =~ /--client-ca-file/} @results[target][:authn][:certificate] = true else @results[target][:authn][:certificate] = false end if api_server_command_line.index{|line| line =~ /--oidc-issuer-url/} @results[target][:authn][:oidc] = true else @results[target][:authn][:oidc] = false end if api_server_command_line.index{|line| line =~ /--authentication-token-webhook-config-file/} @results[target][:authn][:webhook] = true else @results[target][:authn][:webhook] = false end if api_server_command_line.index{|line| line =~ /--requestheader-username-headers/} @results[target][:authn][:proxy] = true else @results[target][:authn][:proxy] = false end #Gather evidence for the API server @results[target]['evidence']['API Server'] = api_server_command_line end |
.check_authz ⇒ Object
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/kube_auto_analyzer/api_checks/authorization_checker.rb', line 2 def self.check_authz @log.debug("Entering the authorization checker") target = @options.target_server @log.debug("Checking enabled authorization options on #{target}") @results[target][:authz] = Hash.new pods = @client.get_pods pods.each do |pod| if pod['metadata']['name'] =~ /kube-apiserver/ @api_server = pod end end api_server_command_line = @api_server['spec']['containers'][0]['command'] if api_server_command_line.index{|line| line =~ /--authorization-mode\S*RBAC/} @results[target][:authz][:rbac] = true else @results[target][:authz][:rbac] = false end if api_server_command_line.index{|line| line =~ /--authorization-mode\S*ABAC/} @results[target][:authz][:abac] = true else @results[target][:authz][:abac] = false end if api_server_command_line.index{|line| line =~ /--authorization-mode\S*Webhook/} @results[target][:authz][:webhook] = true else @results[target][:authz][:webhook] = false end end |
.check_files ⇒ Object
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/kube_auto_analyzer/agent_checks/file_checks.rb', line 3 def self.check_files require 'json' @log.debug ("entering File check") target = @options.target_server @results[target]['node_files'] = Hash.new nodes = Array.new @client.get_nodes.each do |node| nodes << node end nodes.each do |nod| node_hostname = nod..labels['kubernetes.io/hostname'] container_name = "kaa" + node_hostname pod = Kubeclient::Resource.new pod. = {} pod..name = container_name pod..namespace = "default" pod.spec = {} pod.spec.restartPolicy = "Never" pod.spec.containers = {} pod.spec.containers = [{name: "kubeautoanalyzerfiletest", image: "raesene/kaa-agent:latest"}] #Try the Toleration for Master pod.spec.tolerations = {} #Old version doesn't work with 1.8 #pod.spec.tolerations = [{ key:"key", operator:"Equal", value:"value",effect:"NoSchedule"}] pod.spec.tolerations = [{ operator:"Exists" }] pod.spec.volumes = [{name: 'etck8s', hostPath: {path: '/etc'}}] pod.spec.containers[0].volumeMounts = [{mountPath: '/etc', name: 'etck8s'}] pod.spec.containers[0].args = ["/file-checker.rb","/etc/kubernetes"] pod.spec.nodeselector = {} begin pod.spec.nodeselector['kubernetes.io/hostname'] = node_hostname @client.create_pod(pod) begin sleep(5) until @client.get_pod(container_name,"default")['status']['containerStatuses'][0]['state']['terminated']['reason'] == "Completed" rescue retry end files = JSON.parse(@client.get_pod_log(container_name,"default")) @results[target]['node_files'][node_hostname] = files ensure @client.delete_pod(container_name,"default") end end @log.debug("Finished Node File Check") end |
.check_kubelet_process ⇒ Object
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/kube_auto_analyzer/agent_checks/process_checks.rb', line 3 def self.check_kubelet_process @log.debug("Entering Process Checks") target = @options.target_server @results[target]['kubelet_checks'] = Hash.new @results[target]['node_evidence'] = Hash.new nodes = Array.new @client.get_nodes.each do |node| # unless node.spec.taints.to_s =~ /NoSchedule/ nodes << node # end end nodes.each do |nod| node_hostname = nod..labels['kubernetes.io/hostname'] container_name = "kaa" + node_hostname pod = Kubeclient::Resource.new pod. = {} pod..name = container_name pod..namespace = "default" pod.spec = {} pod.spec.restartPolicy = "Never" pod.spec.containers = {} pod.spec.containers = [{name: "kaakubelettest", image: "raesene/kaa-agent:latest"}] #Try the Toleration for Master pod.spec.tolerations = {} #pod.spec.tolerations = [{ key:"key", operator:"Equal", value:"value",effect:"NoSchedule"}] pod.spec.tolerations = [{ operator:"Exists" }] pod.spec.containers[0].args = ["/process-checker.rb"] pod.spec.hostPID = true pod.spec.nodeselector = {} pod.spec.nodeselector['kubernetes.io/hostname'] = node_hostname begin @client.create_pod(pod) begin sleep(5) until @client.get_pod(container_name,"default")['status']['containerStatuses'][0]['state']['terminated']['reason'] == "Completed" rescue retry end processes = JSON.parse(@client.get_pod_log(container_name,"default")) #If we didn't get more than one process, we're probably not reading the host ones #So either it's a bug or we don't have rights if processes.length < 2 @log.debug("Process Check failed didn't get the node process list") @results[target]['kubelet_checks'][node_hostname]['Kubelet Not Found'] = "Error - couldn't see host process list" @client.delete_pod(container_name,"default") return end #puts processes kubelet_proc = '' processes.each do |proc| if proc =~ /kubelet/ kubelet_proc = proc end end @results[target]['kubelet_checks'][node_hostname] = Hash.new unless kubelet_proc.length > 1 @results[target]['kubelet_checks'][node_hostname]['Kubelet Not Found'] = "Error" @log.debug(processes) @client.delete_pod(container_name,"default") return end @results[target]['node_evidence'][node_hostname] = Hash.new @results[target]['node_evidence'][node_hostname]['kubelet'] = kubelet_proc #Checks unless kubelet_proc =~ /--allow-privileged=false/ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.1 - Ensure that the --allow-privileged argument is set to false'] = "Fail" else @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.1 - Ensure that the --allow-privileged argument is set to false'] = "Pass" end unless kubelet_proc =~ /--anonymous-auth=false/ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.2 - Ensure that the --anonymous-auth argument is set to false'] = "Fail" else @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.2 - Ensure that the --anonymous-auth argument is set to false'] = "Pass" end if kubelet_proc =~ /--authorization-mode\S*AlwaysAllow/ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.3 - Ensure that the --authorization-mode argument is not set to AlwaysAllow'] = "Fail" else @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.3 - Ensure that the --authorization-mode argument is not set to AlwaysAllow'] = "Pass" end unless kubelet_proc =~ /--client-ca-file/ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.4 - Ensure that the --client-ca-file argument is set as appropriate'] = "Fail" else @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.4 - Ensure that the --client-ca-file argument is set as appropriate'] = "Pass" end unless kubelet_proc =~ /--read-only-port=0/ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.5 - Ensure that the --read-only-port argument is set to 0'] = "Fail" else @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.5 - Ensure that the --read-only-port argument is set to 0'] = "Pass" end if kubelet_proc =~ /--streaming-connection-idle-timeout=0/ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.6 - Ensure that the --streaming-connection-idle-timeout argument is not set to 0'] = "Fail" else @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.6 - Ensure that the --streaming-connection-idle-timeout argument is not set to 0'] = "Pass" end unless kubelet_proc =~ /--protect-kernel-defaults=true/ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.7 - Ensure that the --protect-kernel-defaults argument is set to true'] = "Fail" else @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.7 - Ensure that the --protect-kernel-defaults argument is set to true'] = "Pass" end if kubelet_proc =~ /--make-iptables-util-chains=false/ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.8 - Ensure that the --make-iptables-util-chains argument is set to true'] = "Fail" else @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.8 - Ensure that the --make-iptables-util-chains argument is set to true'] = "Pass" end unless kubelet_proc =~ /--keep-terminated-pod-volumes=false/ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.9 - Ensure that the --keep-terminated-pod-volumes argument is set to false'] = "Fail" else @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.9 - Ensure that the --keep-terminated-pod-volumes argument is set to false'] = "Pass" end if kubelet_proc =~ /--hostname-override/ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.10 - Ensure that the --hostname-override argument is not set'] = "Fail" else @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.10 - Ensure that the --hostname-override argument is not set'] = "Pass" end unless kubelet_proc =~ /--event-qps=0/ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.11 - Ensure that the --event-qps argument is set to 0'] = "Fail" else @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.11 - Ensure that the --event-qps argument is set to 0'] = "Pass" end unless (kubelet_proc =~ /--tls-cert-file/) && (kubelet_proc =~ /--tls-private-key-file/) @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" else @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" end unless kubelet_proc =~ /--cadvisor-port=0/ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.13 - Ensure that the --cadvisor-port argument is set to 0'] = "Fail" else @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.13 - Ensure that the --cadvisor-port argument is set to 0'] = "Pass" end unless kubelet_proc =~ /--feature-gates=RotateKubeletClientCertificate=true/ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.14 - Ensure that the RotateKubeletClientCertificate argument is set to true'] = "Fail" else @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.14 - Ensure that the RotateKubeletClientCertificate argument is set to true'] = "Pass" end unless kubelet_proc =~ /--feature-gates=RotateKubeletServerCertificate=true/ @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.15 - Ensure that the RotateKubeletServerCertificate argument is set to true'] = "Fail" else @results[target]['kubelet_checks'][node_hostname]['CIS 2.1.15 - Ensure that the RotateKubeletServerCertificate argument is set to true'] = "Pass" end #Need an ensure block here to make sure that the pod is deleted after its run ensure @client.delete_pod(container_name,"default") end end end |
.dump_config ⇒ Object
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/kube_auto_analyzer/api_checks/config_dumper.rb', line 2 def self.dump_config @log.debug("Entering the config dumper module") target = @options.target_server @log.debug("dumping the config for #{target}") @results[target][:config] = Hash.new pods = @client.get_pods services = @client.get_services docker_images = Array.new #Specific requirement here in that it's useful to know what Docker images are in use on the cluster. pods.each do |pod| docker_images << pod.status[:containerStatuses][0][:image] end @log.debug("logged #{docker_images.length} docker images") @results[target][:config][:docker_images] = docker_images.uniq @results[target][:config][:pod_info] = Array.new #Lets record some information about each pod pods.each do |pod| currpod = Hash.new currpod[:name] = pod.[:name] currpod[:namespace] = pod.[:namespace] currpod[:service_account] = pod.spec[:serviceAccount] currpod[:host_ip] = pod[:status][:hostIP] currpod[:pod_ip] = pod[:status][:podIP] @results[target][:config][:pod_info] << currpod end @results[target][:config][:service_info] = Array.new services.each do |service| currserv = Hash.new currserv[:name] = service.[:name] currserv[:cluster_ip] = service.spec[:clusterIP] if service.spec[:externalIP] currserv[:external_ip] = service.spec[:externalIP] else currserv[:external_ip] = "None" end if service.spec[:ports] currserv[:ports] = Array.new service.spec[:ports].each do |port| currserv[:ports] << "#{port[:port]}/#{port[:protocol]}:#{port[:targetPort]}/#{port[:protocol]}" end else currserv[:ports] = "None" end @results[target][:config][:service_info] << currserv end end |
.execute(commmand_line_opts) ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/kube_auto_analyzer.rb', line 19 def self.execute(commmand_line_opts) @options = commmand_line_opts require 'logger' begin require 'kubeclient' rescue LoadError puts "You need to install kubeclient for this, try 'gem install kubeclient'" exit end @base_dir = @options.report_directory if !File.exists?(@base_dir) Dir.mkdirs(@base_dir) end @log = Logger.new(@base_dir + '/kube-analyzer-log.txt') @log.level = Logger::DEBUG @log.debug("Log created at " + Time.now.to_s) @log.debug("Target API Server is " + @options.target_server) @report_file_name = @base_dir + '/' + @options.report_file if @options.json_report @json_report_file = File.new(@report_file_name + '.json','w+') end if @options.html_report @html_report_file = File.new(@report_file_name + '.html','w+') end @log.debug("New Report File created #{@report_file_name}") @results = Hash.new #TODO: Expose this as an option rather than hard-code to off unless @options.config_file = { verify_ssl: OpenSSL::SSL::VERIFY_NONE} #TODO: Need to setup the other authentication options if @options.token.length > 1 = { bearer_token: @options.token} elsif @options.token_file.length > 1 = { bearer_token_file: @options.token_file} elsif @options.insecure #Not sure this will actually work for no auth. needed, try and ooold cluster to check = {} end @results[@options.target_server] = Hash.new @client = Kubeclient::Client.new @options.target_server, 'v1', auth_options: , ssl_options: @rbac_client = Kubeclient::Client.new @options.target_server + '/apis/rbac.authorization.k8s.io', 'v1', auth_options: , ssl_options: else begin config = Kubeclient::Config.read(@options.config_file) if @options.context context = config.context(@options.context) else context = config.context end rescue Errno::ENOENT puts "Config File could not be read, check the path?" exit end if @options.nosslverify @client = Kubeclient::Client.new( context.api_endpoint, context.api_version, { ssl_options: {client_cert: context.[:client_cert], client_key: context.[:client_key],verify_ssl: OpenSSL::SSL::VERIFY_NONE}, auth_options: context. } ) @rbac_client = Kubeclient::Client.new( context.api_endpoint + '/apis/rbac.authorization.k8s.io', context.api_version, { ssl_options: {client_cert: context.[:client_cert], client_key: context.[:client_key],verify_ssl: OpenSSL::SSL::VERIFY_NONE}, auth_options: context. } ) else @client = Kubeclient::Client.new( context.api_endpoint, context.api_version, { ssl_options: context., auth_options: context. } ) @rbac_client = Kubeclient::Client.new( context.api_endpoint + '/apis/rbac.authorization.k8s.io', context.api_version, { ssl_options: context., auth_options: context. } ) end #We didn't specify the target on the command line so lets get it from the config file @options.target_server = context.api_endpoint @log.debug("target is " + @options.target_server) @results[context.api_endpoint] = Hash.new end #Test response begin @client.get_pods.to_s rescue => error puts error puts "Check of API connection failed." puts "try using kubectl with the same connection details" puts "to see what's going wrong." exit end if @options.cis_audit test_api_server test_scheduler test_controller_manager test_etcd end check_authn check_authz test_unauth_kubelet_external test_insecure_api_external if @options.agent_checks test_unauth_kubelet_internal test_insecure_api_internal test_service_token_internal if @options.cis_audit check_files check_kubelet_process end check_amicontained end if @options.dump_config dump_config end if @options.audit_rbac audit_rbac end if @options.html_report html_report end if @options.json_report json_report end end |
.html_report ⇒ Object
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 |
# File 'lib/kube_auto_analyzer/reporting.rb', line 10 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 << ' <!DOCTYPE html> <head> <title> Kubernetes Auto Analyzer Report</title> <meta charset="utf-8"> <style> body { font: normal 14px; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; color: #C41230; background: #FFFFFF; } #kubernetes-analyzer { font-weight: bold; font-size: 48px; color: #C41230; } .master-node, .worker-node, .vuln-node { background: #F5F5F5; border: 1px solid black; padding-left: 6px; } #api-server-results { font-weight: italic; font-size: 36px; color: #C41230; } table, th, td { border-collapse: collapse; border: 1px solid black; } th { font: bold 11px; color: #C41230; background: #999999; letter-spacing: 2px; text-transform: uppercase; text-align: left; padding: 6px 6px 6px 12px; } td { background: #FFFFFF; padding: 6px 6px 6px 12px; color: #333333; } .container{ display: flex; } .fixed{ width: 300px; } .flex-item{ flex-grow: 1; } </style> </head> <body> ' @html_report_file.puts '<img width="100" height="100" align="right"' + " src=#{logo} />" @html_report_file.puts "<h1>Kubernetes Auto Analyzer</h1>" @html_report_file.puts "<br><b>Server Reviewed : </b> #{@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 "<script>#{chartkick}</script>" @html_report_file.puts "<script>#{highcharts}</script>" @html_report_file.puts '<br><br><div class="master-node"><h2>Master Node Results</h2><br>' #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 '<div class="container">' #API Server Chart @html_report_file.puts '<div class="fixed" id="chart-1" style="height: 300px; width: 300px; text-align: center; color: #999; line-height: 300px; font-size: 14px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;"></div>' @html_report_file.puts '<script>new Chartkick.PieChart("chart-1", {"pass": ' + api_server_pass.to_s + ', "fail": ' + api_server_fail.to_s + '}, {"colors":["green","red"], "library":{"title":{"text":"API Server Results"},"chart":{"backgroundColor":"#F5F5F5"}}})</script>' #Scheduler Chart #@html_report_file.puts '<div class="flex-item" id="chart-2" style="height: 300px; width: 300px; text-align: center; color: #999; line-height: 300px; font-size: 14px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;"></div>' #@html_report_file.puts '<script>new Chartkick.PieChart("chart-2", {"pass": ' + scheduler_pass.to_s + ', "fail": ' + scheduler_fail.to_s + '}, {"colors":["green","red"], "library":{"title":{"text":"Scheduler Results"},"chart":{"backgroundColor":"#F5F5F5"}}})</script>' #Controller Manager Chart @html_report_file.puts '<div class="fixed" id="chart-2" style="height: 300px; width: 300px; text-align: center; color: #999; line-height: 300px; font-size: 14px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;"></div>' @html_report_file.puts '<script>new Chartkick.PieChart("chart-2", {"pass": ' + controller_manager_pass.to_s + ', "fail": ' + controller_manager_fail.to_s + '}, {"colors":["green","red"], "library":{"title":{"text":"Controller Manager Results"},"chart":{"backgroundColor":"#F5F5F5"}}})</script>' #etcd Chart @html_report_file.puts '<div class="fixed" id="chart-3" style="height: 300px; width: 300px; text-align: center; color: #999; line-height: 300px; font-size: 14px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;"></div>' @html_report_file.puts '<script>new Chartkick.PieChart("chart-3", {"pass": ' + etcd_pass.to_s + ', "fail": ' + etcd_fail.to_s + '}, {"colors":["green","red"], "library":{"title":{"text":"etcd Results"},"chart":{"backgroundColor":"#F5F5F5"}}})</script>' #End of Chart Divs @html_report_file.puts '</div>' @html_report_file.puts "<h2>API Server</h2>" @html_report_file.puts "<table><thead><tr><th>Check</th><th>result</th></tr></thead>" @results[@options.target_server]['api_server'].each do |test, result| if result == "Fail" result = '<span style="color:red;">Fail</span>' elsif result == "Pass" result = '<span style="color:green;">Pass</span>' end @html_report_file.puts "<tr><td>#{test}</td><td>#{result}</td></tr>" end @html_report_file.puts "</table>" @html_report_file.puts "<br><br>" @html_report_file.puts "<br><br><h2>Scheduler</h2>" @html_report_file.puts "<table><thead><tr><th>Check</th><th>result</th></tr></thead>" @results[@options.target_server]['scheduler'].each do |test, result| if result == "Fail" result = '<span style="color:red;">Fail</span>' elsif result == "Pass" result = '<span style="color:green;">Pass</span>' end @html_report_file.puts "<tr><td>#{test}</td><td>#{result}</td></tr>" end @html_report_file.puts "</table>" @html_report_file.puts "<br><br>" @html_report_file.puts "<br><br><h2>Controller Manager</h2>" @html_report_file.puts "<table><thead><tr><th>Check</th><th>result</th></tr></thead>" @results[@options.target_server]['controller_manager'].each do |test, result| if result == "Fail" result = '<span style="color:red;">Fail</span>' elsif result == "Pass" result = '<span style="color:green;">Pass</span>' end @html_report_file.puts "<tr><td>#{test}</td><td>#{result}</td></tr>" end @html_report_file.puts "</table>" @html_report_file.puts "<br><br>" @html_report_file.puts "<br><br><h2>etcd</h2>" @html_report_file.puts "<table><thead><tr><th>Check</th><th>result</th></tr></thead>" @results[@options.target_server]['etcd'].each do |test, result| if result == "Fail" result = '<span style="color:red;">Fail</span>' elsif result == "Pass" result = '<span style="color:green;">Pass</span>' end @html_report_file.puts "<tr><td>#{test}</td><td>#{result}</td></tr>" end @html_report_file.puts "</table>" #Close the master Node Div @html_report_file.puts "</table></div>" end if @options.agent_checks @html_report_file.puts '<br><br><div class="worker-node"><h2>Worker Node Results</h2>' #Start of Chart Divs @html_report_file.puts '<div class="container">' @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 '<div class="fixed" id="' + node + '" style="height: 300px; width: 300px; text-align: center; color: #999; line-height: 300px; font-size: 14px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;"></div>' @html_report_file.puts '<script>new Chartkick.PieChart("' + node + '", {"pass": ' + node_kubelet_pass.to_s + ', "fail": ' + node_kubelet_fail.to_s + '}, {"colors":["green","red"], "library":{"title":{"text":"' + node + ' Kubelet Results"},"chart":{"backgroundColor":"#F5F5F5"}}})</script>' end #End of Chart Divs @html_report_file.puts '</div>' @results[@options.target_server]['kubelet_checks'].each do |node, results| @html_report_file.puts "<br><b>#{node} Kubelet Checks</b>" @html_report_file.puts "<table><thead><tr><th>Check</th><th>result</th></tr></thead>" results.each do |test, result| if result == "Fail" result = '<span style="color:red;">Fail</span>' elsif result == "Pass" result = '<span style="color:green;">Pass</span>' end @html_report_file.puts "<tr><td>#{test}</td><td>#{result}</td></tr>" end @html_report_file.puts "</table>" end @html_report_file.puts "<br><br><h2>Evidence</h2><br>" @html_report_file.puts "<table><thead><tr><th>Host</th><th>Area</th><th>Output</th></tr></thead>" @results[@options.target_server]['node_evidence'].each do |node, evidence| evidence.each do |area, data| @html_report_file.puts "<tr><td>#{node}</td><td>#{area}</td><td>#{data}</td></tr>" end end @html_report_file.puts "</table>" end #Close the Worker Node Div @html_report_file.puts '</div>' if @options.agent_checks @html_report_file.puts '<br><h2>Node File Permissions</h2>' @results[@options.target_server]['node_files'].each do |node, results| @html_report_file.puts "<br><b>#{node}</b><br>" @html_report_file.puts "<table><thead><tr><th>file</th><th>user</th><th>group</th><th>permissions</th></thead>" results.each do |file| @html_report_file.puts "<tr><td>#{file[0]}</td><td>#{file[1]}</td><td>#{file[2]}</td><td>#{file[3]}</td></tr>" end @html_report_file.puts "</table>" end end @html_report_file.puts '<br><h2>Vulnerability Checks</h2>' @html_report_file.puts '<br><h3>External Unauthenticated Access to the Kubelet</h3>' @html_report_file.puts "<table><thead><tr><th>Node IP Address</th><th>Result</th></thead>" @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 "<tr><td>#{node}</td><td>#{output}</td></tr>" end @html_report_file.puts "</table>" if @options.agent_checks @html_report_file.puts '<br><h3>Internal Unauthenticated Access to the Kubelet</h3>' @html_report_file.puts "<table><thead><tr><th>Node IP Address</th><th>Result</th></thead>" @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 "<tr><td>#{node}</td><td>#{output}</td></tr>" end @html_report_file.puts "</table>" end @html_report_file.puts '<br><h3>External Insecure API Port Exposed</h3>' @html_report_file.puts "<table><thead><tr><th>Node IP Address</th><th>Result</th></thead>" @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 "<tr><td>#{node}</td><td>#{output}</td></tr>" end @html_report_file.puts "</table>" if @options.agent_checks @html_report_file.puts '<br><h3>Internal Insecure API Port Exposed</h3>' @html_report_file.puts "<table><thead><tr><th>Node IP Address</th><th>Result</th></thead>" @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 "<tr><td>#{node}</td><td>#{output}</td></tr>" end @html_report_file.puts "</table>" end if @options.agent_checks @html_report_file.puts '<br><h3>Default Service Token In Use</h3>' @html_report_file.puts "<table><thead><tr><th>API endpoint</th><th>Result</th></thead>" @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 "<tr><td>#{node}</td><td>#{output}</td></tr>" end @html_report_file.puts "</table>" end if @options.agent_checks @html_report_file.puts '<br><h3>Container Configuration checks</h3>' @results[@options.target_server]['vulns']['amicontained'].each do |node, result| @html_report_file.puts "<br><b>#{node} Container Checks</b>" @html_report_file.puts "<table><thead><tr><th>Container item</th><th>Result</th></thead>" @html_report_file.puts "<tr><td>Runtime in Use</td><td>#{result['runtime']}</td></tr>" @html_report_file.puts "<tr><td>Host PID namespace used?</td><td>#{result['hostpid']}</td></tr>" @html_report_file.puts "<tr><td>AppArmor Profile</td><td>#{result['apparmor']}</td></tr>" @html_report_file.puts "<tr><td>User Namespaces in use?</td><td>#{result['uid_map']}</td></tr>" @html_report_file.puts "<tr><td>Inherited Capabilities</td><td>#{result['cap_inh']}</td></tr>" @html_report_file.puts "<tr><td>Effective Capabilities</td><td>#{result['cap_eff']}</td></tr>" @html_report_file.puts "<tr><td>Permitted Capabilities</td><td>#{result['cap_per']}</td></tr>" @html_report_file.puts "<tr><td>Bounded Capabilities</td><td>#{result['cap_bnd']}</td></tr>" @html_report_file.puts "</table>" end end @html_report_file.puts "<br><br><h2>Vulnerability Evidence</h2><br>" @html_report_file.puts "<table><thead><tr><th>Vulnerability</th><th>Host</th><th>Output</th></tr></thead>" @results[@options.target_server]['vulns']['unauth_kubelet'].each do |node, result| @html_report_file.puts "<tr><td>External Unauthenticated Kubelet Access</td><td>#{node}</td><td>#{result}</td></tr>" end if @options.agent_checks @results[@options.target_server]['vulns']['internal_kubelet'].each do |node, result| @html_report_file.puts "<tr><td>Internal Unauthenticated Kubelet Access</td><td>#{node}</td><td>#{result}</td></tr>" end end @results[@options.target_server]['vulns']['insecure_api_external'].each do |node, result| @html_report_file.puts "<tr><td>External Insecure API Server Access</td><td>#{node}</td><td>#{result}</td></tr>" end if @options.agent_checks @results[@options.target_server]['vulns']['insecure_api_internal'].each do |node, result| @html_report_file.puts "<tr><td>Internal Insecure API Server Access</td><td>#{node}</td><td>#{result}</td></tr>" end end if @options.agent_checks @results[@options.target_server]['vulns']['service_token'].each do |node, result| @html_report_file.puts "<tr><td>Default Service Token In Use</td><td>#{node}</td><td>#{result}</td></tr>" end end if @options.agent_checks @results[@options.target_server]['vulns']['amicontained'].each do |node, result| @html_report_file.puts "<tr><td>Am I Contained Output</td><td>#{node}</td><td>#{result}</td></tr>" end end @html_report_file.puts "</table>" #Show what cluster authentication modes are supported. @html_report_file.puts "<br><br><h1>Kubernetes Cluster Information</h1>" @html_report_file.puts "<br><br><h2>Kubernetes Authentication Options</h2>" @html_report_file.puts "<table><thead><tr><th>Authentication Option</th><th>Enabled?</th></tr></thead>" if @results[@options.target_server][:authn][:basic] == true @html_report_file.puts "<tr><td>Basic Authentication</td><td>Enabled</td></tr>" else @html_report_file.puts "<tr><td>Basic Authentication</td><td>Disabled</td></tr>" end if @results[@options.target_server][:authn][:token] == true @html_report_file.puts "<tr><td>Token Authentication</td><td>Enabled</td></tr>" else @html_report_file.puts "<tr><td>Token Authentication</td><td>Disabled</td></tr>" end if @results[@options.target_server][:authn][:certificate] == true @html_report_file.puts "<tr><td>Client Certificate Authentication</td><td>Enabled</td></tr>" else @html_report_file.puts "<tr><td>Client Certificate Authentication</td><td>Disabled</td></tr>" end if @results[@options.target_server][:authn][:oidc] == true @html_report_file.puts "<tr><td>OpenID Connect Authentication</td><td>Enabled</td></tr>" else @html_report_file.puts "<tr><td>OpenID Connect Authentication</td><td>Disabled</td></tr>" end if @results[@options.target_server][:authn][:webhook] == true @html_report_file.puts "<tr><td>Webhook Authentication</td><td>Enabled</td></tr>" else @html_report_file.puts "<tr><td>Webhook Authentication</td><td>Disabled</td></tr>" end if @results[@options.target_server][:authn][:proxy] == true @html_report_file.puts "<tr><td>Proxy Authentication</td><td>Enabled</td></tr>" else @html_report_file.puts "<tr><td>Proxy Authentication</td><td>Disabled</td></tr>" end @html_report_file.puts "</table>" #Show what cluster authorization modes are supported. @html_report_file.puts "<br><br>" @html_report_file.puts "<br><br><h2>Kubernetes Authorization Options</h2>" @html_report_file.puts "<table><thead><tr><th>Authorization Option</th><th>Enabled?</th></tr></thead>" if @results[@options.target_server][:authz][:rbac] == true @html_report_file.puts "<tr><td>Role Based Authorization</td><td>Enabled</td></tr>" else @html_report_file.puts "<tr><td>Role Based Authorization</td><td>Disabled</td></tr>" end if @results[@options.target_server][:authz][:abac] == true @html_report_file.puts "<tr><td>Attribute Based Authorization</td><td>Enabled</td></tr>" else @html_report_file.puts "<tr><td>Attribute Based Authorization</td><td>Disabled</td></tr>" end if @results[@options.target_server][:authz][:webhook] == true @html_report_file.puts "<tr><td>Webhook Authorization</td><td>Enabled</td></tr>" else @html_report_file.puts "<tr><td>Webhook Authorization</td><td>Disabled</td></tr>" end @html_report_file.puts "</table>" @html_report_file.puts "<br><br><h2>Evidence</h2><br>" @html_report_file.puts "<table><thead><tr><th>Area</th><th>Output</th></tr></thead>" @results[@options.target_server]['evidence'].each do |area, output| @html_report_file.puts "<tr><td>#{area}</td><td>#{output}</td></tr>" end @html_report_file.puts "</table>" #Only show this section if we were asked to dump the config if @options.dump_config @html_report_file.puts "<br><br>" @html_report_file.puts "<br><br><h2>Cluster Config Information</h2>" @html_report_file.puts "<table><thead><tr><th>Docker Images In Use</th></tr></thead>" @results[@options.target_server][:config][:docker_images].each do |image| @html_report_file.puts "<tr><td>#{image}</td></tr>" end @html_report_file.puts "</table>" @html_report_file.puts "<br><br>" @html_report_file.puts "<table><thead><tr><th>Pod Name</th><th>Namespace</th><th>Service Account</th><th>Host IP</th><th>Pod IP</th></tr></thead>" @results[@options.target_server][:config][:pod_info].each do |pod| @html_report_file.puts "<tr><td>#{pod[:name]}</td><td>#{pod[:namespace]}</td><td>#{pod[:service_account]}</td><td>#{pod[:host_ip]}</td><td>#{pod[:pod_ip]}</td></tr>" end @html_report_file.puts "</table>" @html_report_file.puts "<br><br>" @html_report_file.puts "<br><br>" @html_report_file.puts "<table><thead><tr><th>Service Name</th><th>Cluster IP</th><th>External IP</th><th>Port:Target Port</th></tr></thead>" @results[@options.target_server][:config][:service_info].each do |service| @html_report_file.puts "<tr><td>#{service[:name]}</td><td>#{service[:cluster_ip]}</td><td>#{service[:external_ip]}</td><td>#{service[:ports].join('<br>')}</td></tr>" end @html_report_file.puts "</table>" @html_report_file.puts "<br><br>" end #Only show this section if we were asked to dump RBAC if @options.audit_rbac @html_report_file.puts "<br><br>" @html_report_file.puts "<br><br><h2>Cluster Role Information</h2>" @html_report_file.puts "<table><thead><tr><th>Name</th><th>Default?</th><th>Subjects</th><th>Rules</th></tr></thead>" @results[@options.target_server][:rbac][:cluster_roles].each do |name, info| subjects = '' info[:subjects].each do |subject| subjects << "#{subject[:kind]}:#{subject[:namespace]}:#{subject[:name]}<br>" 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(', ')}<br>API Groups : #{rule.apiGroups.join(', ')}<br>Resources : #{rule.resources.join(', ')}<br><hr>" end @html_report_file.puts "<tr><td>#{name}</td><td>#{info[:default]}</td><td>#{subjects}</td><td>#{rules}</td></tr>" end @html_report_file.puts "</table>" @html_report_file.puts "<br><br>" end #Closing the report off @html_report_file.puts '</body></html>' end |
.is_port_open?(ip, port) ⇒ Boolean
4 5 6 7 8 9 10 11 12 13 14 15 |
# File 'lib/kube_auto_analyzer/utility/network.rb', line 4 def self.is_port_open?(ip, port) begin Socket.tcp(ip, port, connect_timeout: 2) rescue Errno::ECONNREFUSED return false rescue Errno::ETIMEDOUT return false rescue Errno::ENETUNREACH return false end true end |
.json_report ⇒ Object
3 4 5 6 7 8 |
# File 'lib/kube_auto_analyzer/reporting.rb', line 3 def self.json_report require 'json' @log.debug("Starting Report") @json_report_file.puts JSON.generate(@results) end |
.test_api_server ⇒ Object
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/kube_auto_analyzer/api_checks/master_node.rb', line 3 def self.test_api_server @log.debug("Entering the test API Server Method") target = @options.target_server @log.debug("target is #{target}") @results[target]['api_server'] = Hash.new pods = @client.get_pods pods.each do |pod| #Ok this is a bit naive as a means of hitting the API server but hey it's a start if pod['metadata']['name'] =~ /kube-apiserver/ @api_server = pod end end unless @api_server @results[target]['api_server']['API Server Pod Not Found'] = "Error" return end api_server_command_line = @api_server['spec']['containers'][0]['command'] #Check for Anonymous Auth unless api_server_command_line.index{|line| line =~ /--anonymous-auth=false/} @results[target]['api_server']['CIS 1.1.1 - Ensure that the --anonymous-auth argument is set to false'] = "Fail" else @results[target]['api_server']['CIS 1.1.1 - Ensure that the --anonymous-auth argument is set to false'] = "Pass" end #Check for Basic Auth if api_server_command_line.index{|line| line =~ /--basic-auth-file/} @results[target]['api_server']['CIS 1.1.2 - Ensure that the --basic-auth-file argument is not set'] = "Fail" else @results[target]['api_server']['CIS 1.1.2 - Ensure that the --basic-auth-file argument is not set'] = "Pass" end #Check for Insecure Allow Any Token if api_server_command_line.index{|line| line =~ /--insecure-allow-any-token/} @results[target]['api_server']['CIS 1.1.3 - Ensure that the --insecure-allow-any-token argument is not set'] = "Fail" else @results[target]['api_server']['CIS 1.1.3 - Ensure that the --insecure-allow-any-token argument is not set'] = "Pass" end #Check to confirm that Kubelet HTTPS isn't set to false if api_server_command_line.index{|line| line =~ /--kubelet-https=false/} @results[target]['api_server']['CIS 1.1.4 - Ensure that the --kubelet-https argument is set to true'] = "Fail" else @results[target]['api_server']['CIS 1.1.4 - Ensure that the --kubelet-https argument is set to true'] = "Pass" end #Check for Insecure Bind Address if api_server_command_line.index{|line| line =~ /--insecure-bind-address/} @results[target]['api_server']['CIS 1.1.5 - Ensure that the --insecure-bind-address argument is not set'] = "Fail" else @results[target]['api_server']['CIS 1.1.5 - Ensure that the --insecure-bind-address argument is not set'] = "Pass" end #Check for Insecure Bind port unless api_server_command_line.index{|line| line =~ /--insecure-port=0/} @results[target]['api_server']['CIS 1.1.6 - Ensure that the --insecure-port argument is set to 0'] = "Fail" else @results[target]['api_server']['CIS 1.1.6 - Ensure that the --insecure-port argument is set to 0'] = "Pass" end #Check Secure Port isn't set to 0 if api_server_command_line.index{|line| line =~ /--secure-port=0/} @results[target]['api_server']['CIS 1.1.7 - Ensure that the --secure-port argument is not set to 0'] = "Fail" else @results[target]['api_server']['CIS 1.1.7 - Ensure that the --secure-port argument is not set to 0'] = "Pass" end # unless api_server_command_line.index{|line| line =~ /--profiling=false/} @results[target]['api_server']['CIS 1.1.8 - Ensure that the --profiling argument is set to false'] = "Fail" else @results[target]['api_server']['CIS 1.1.8 - Ensure that the --profiling argument is set to false'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--repair-malformed-updates/} @results[target]['api_server']['CIS 1.1.9 - Ensure that the --repair-malformed-updates argument is set to false'] = "Fail" else @results[target]['api_server']['CIS 1.1.9 - Ensure that the --repair-malformed-updates argument is set to false'] = "Pass" end if api_server_command_line.index{|line| line =~ /--admission-control\S*AlwaysAdmit/} @results[target]['api_server']['CIS 1.1.10 - Ensure that the admission control policy is not set to AlwaysAdmit'] = "Fail" else @results[target]['api_server']['CIS 1.1.10 - Ensure that the admission control policy is not set to AlwaysAdmit'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--admission-control\S*AlwaysPullImages/} @results[target]['api_server']['CIS 1.1.11 - Ensure that the admission control policy is set to AlwaysPullImages'] = "Fail" else @results[target]['api_server']['CIS 1.1.11 - Ensure that the admission control policy is set to AlwaysPullImages'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--admission-control\S*DenyEscalatingExec/} @results[target]['api_server']['CIS 1.1.12 - Ensure that the admission control policy is set to DenyEscalatingExec'] = "Fail" else @results[target]['api_server']['CIS 1.1.12 - Ensure that the admission control policy is set to DenyEscalatingExec'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--admission-control\S*SecurityContextDeny/} @results[target]['api_server']['CIS 1.1.13 - Ensure that the admission control policy is set to SecurityContextDeny'] = "Fail" else @results[target]['api_server']['CIS 1.1.13 - Ensure that the admission control policy is set to SecurityContextDeny'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--admission-control\S*NamespaceLifecycle/} @results[target]['api_server']['CIS 1.1.14 - Ensure that the admission control policy is set to NamespaceLifecycle'] = "Fail" else @results[target]['api_server']['CIS 1.1.14 - Ensure that the admission control policy is set to NamespaceLifecycle'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--audit-log-path/} @results[target]['api_server']['CIS 1.1.15 - Ensure that the --audit-log-path argument is set as appropriate'] = "Fail" else @results[target]['api_server']['CIS 1.1.15 - Ensure that the --audit-log-path argument is set as appropriate'] = "Pass" end #TODO: This check needs to do something with the number of days but for now lets just check whether it's present. unless api_server_command_line.index{|line| line =~ /--audit-log-maxage/} @results[target]['api_server']['CIS 1.1.16 - Ensure that the --audit-log-maxage argument is set to 30 or as appropriate'] = "Fail" else @results[target]['api_server']['CIS 1.1.16 - Ensure that the --audit-log-maxage argument is set to 30 or as appropriate'] = "Pass" end #TODO: This check needs to do something with the number of backups but for now lets just check whether it's present. unless api_server_command_line.index{|line| line =~ /--audit-log-maxbackup/} @results[target]['api_server']['CIS 1.1.17 - Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate'] = "Fail" else @results[target]['api_server']['CIS 1.1.17 - Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate'] = "Pass" end #TODO: This check needs to do something with the size of backups but for now lets just check whether it's present. unless api_server_command_line.index{|line| line =~ /--audit-log-maxsize/} @results[target]['api_server']['CIS 1.1.18 - Ensure that the --audit-log-maxsize argument is set to 100 or as appropriate'] = "Fail" else @results[target]['api_server']['CIS 1.1.18 - Ensure that the --audit-log-maxsize argument is set to 100 or as appropriate'] = "Pass" end if api_server_command_line.index{|line| line =~ /--authorization-mode\S*AlwaysAllow/} @results[target]['api_server']['CIS 1.1.19 - Ensure that the --authorization-mode argument is not set to AlwaysAllow'] = "Fail" else @results[target]['api_server']['CIS 1.1.19 - Ensure that the --authorization-mode argument is not set to AlwaysAllow'] = "Pass" end if api_server_command_line.index{|line| line =~ /--token-auth-file/} @results[target]['api_server']['CIS 1.1.20 - Ensure that the --token-auth-file argument is not set'] = "Fail" else @results[target]['api_server']['CIS 1.1.20 - Ensure that the --token-auth-file argument is not set'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--kubelet-certificate-authority/} @results[target]['api_server']['CIS 1.1.21 - Ensure that the --kubelet-certificate-authority argument is set as appropriate'] = "Fail" else @results[target]['api_server']['CIS 1.1.21 - Ensure that the --kubelet-certificate-authority argument is set as appropriate'] = "Pass" end unless (api_server_command_line.index{|line| line =~ /--kubelet-client-certificate/} && api_server_command_line.index{|line| line =~ /--kubelet-client-key/}) @results[target]['api_server']['CIS 1.1.22 - Ensure that the --kubelet-client-certificate and --kubelet-client-key arguments are set as appropriate'] = "Fail" else @results[target]['api_server']['CIS 1.1.22 - Ensure that the --kubelet-client-certificate and --kubelet-client-key arguments are set as appropriate'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--service-account-lookup=true/} @results[target]['api_server']['CIS 1.1.23 - Ensure that the --service-account-lookup argument is set to true'] = "Fail" else @results[target]['api_server']['CIS 1.1.23 - Ensure that the --service-account-lookup argument is set to true'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--admission-control\S*PodSecurityPolicy/} @results[target]['api_server']['CIS 1.1.24 - Ensure that the admission control policy is set to PodSecurityPolicy'] = "Fail" else @results[target]['api_server']['CIS 1.1.24 - Ensure that the admission control policy is set to PodSecurityPolicy'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--service-account-key-file/} @results[target]['api_server']['CIS 1.1.25 - Ensure that the --service-account-key-file argument is set as appropriate'] = "Fail" else @results[target]['api_server']['CIS 1.1.25 - Ensure that the --service-account-key-file argument is set as appropriate'] = "Pass" end unless (api_server_command_line.index{|line| line =~ /--etcd-certfile/} && api_server_command_line.index{|line| line =~ /--etcd-keyfile/}) @results[target]['api_server']['CIS 1.1.26 - Ensure that the --etcd-certfile and --etcd-keyfile arguments are set as appropriate'] = "Fail" else @results[target]['api_server']['CIS 1.1.26 - Ensure that the --etcd-certfile and --etcd-keyfile arguments are set as appropriate'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--admission-control\S*ServiceAccount/} @results[target]['api_server']['CIS 1.1.27 - Ensure that the admission control policy is set to ServiceAccount'] = "Fail" else @results[target]['api_server']['CIS 1.1.27 - Ensure that the admission control policy is set to ServiceAccount'] = "Pass" end unless (api_server_command_line.index{|line| line =~ /--tls-cert-file/} && api_server_command_line.index{|line| line =~ /--tls-private-key-file/}) @results[target]['api_server']['CIS 1.1.28 - Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate'] = "Fail" else @results[target]['api_server']['CIS 1.1.28 - Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--client-ca-file/} @results[target]['api_server']['CIS 1.1.29 - Ensure that the --client-ca-file argument is set as appropriate'] = "Fail" else @results[target]['api_server']['CIS 1.1.29 - Ensure that the --client-ca-file argument is set as appropriate'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--etcd-cafile/} @results[target]['api_server']['CIS 1.1.30 - Ensure that the --etcd-cafile argument is set as appropriate'] = "Fail" else @results[target]['api_server']['CIS 1.1.30 - Ensure that the --etcd-cafile argument is set as appropriate'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--authorization-mode\S*Node/} @results[target]['api_server']['CIS 1.1.31 - Ensure that the --authorization-mode argument is set to Node'] = "Fail" else @results[target]['api_server']['CIS 1.1.31 - Ensure that the --authorization-mode argument is set to Node'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--admission-control\S*NodeRestriction/} @results[target]['api_server']['CIS 1.1.32 - Ensure that the admission control policy is set to NodeRestriction'] = "Fail" else @results[target]['api_server']['CIS 1.1.32 - Ensure that the admission control policy is set to NodeRestriction'] = "Pass" end unless api_server_command_line.index{|line| line =~ /--experimental-encryption-provider-config/} @results[target]['api_server']['CIS 1.1.33 - Ensure that the --experimental-encryption-provider-config argument is set as appropriate'] = "Fail" else @results[target]['api_server']['CIS 1.1.33 - Ensure that the --experimental-encryption-provider-config argument is set as appropriate'] = "Pass" end #1.1.34 can't be checked using this methodology so it's TBD unless api_server_command_line.index{|line| line =~ /--admission-control\S*EventRateLimit/} @results[target]['api_server']['CIS 1.1.35 - Ensure that the admission control policy is set to EventRateLimit'] = "Fail" else @results[target]['api_server']['CIS 1.1.35 - Ensure that the admission control policy is set to EventRateLimit'] = "Pass" end if api_server_command_line.index{|line| line =~ /--feature-gates=AdvancedAuditing=false/} @results[target]['api_server']['CIS 1.1.36 - Ensure that the AdvancedAuditing argument is not set to false'] = "Fail" else @results[target]['api_server']['CIS 1.1.36 - Ensure that the AdvancedAuditing argument is not set to false'] = "Pass" end #1.1.37 This one is dubious for a pass/fail test as the value should be evaluated against the relity of the cluster. end |
.test_controller_manager ⇒ Object
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 |
# File 'lib/kube_auto_analyzer/api_checks/master_node.rb', line 277 def self.test_controller_manager target = @options.target_server @results[target]['controller_manager'] = Hash.new pods = @client.get_pods pods.each do |pod| #Ok this is a bit naive as a means of hitting the API server but hey it's a start if pod['metadata']['name'] =~ /kube-controller-manager/ @controller_manager = pod end end unless @controller_manager @results[target]['controller_manager']['Controller Manager Pod Not Found'] = "Error" return end controller_manager_command_line = @controller_manager['spec']['containers'][0]['command'] unless controller_manager_command_line.index{|line| line =~ /--terminated-pod-gc-threshold/} @results[target]['controller_manager']['CIS 1.3.1 - Ensure that the --terminated-pod-gc-threshold argument is set as appropriate'] = "Fail" else @results[target]['controller_manager']['CIS 1.3.1 - Ensure that the --terminated-pod-gc-threshold argument is set as appropriate'] = "Pass" end unless controller_manager_command_line.index{|line| line =~ /--profiling=false/} @results[target]['controller_manager']['CIS 1.3.2 - Ensure that the --profiling argument is set to false'] = "Fail" else @results[target]['controller_manager']['CIS 1.3.2 - Ensure that the --profiling argument is set to false'] = "Pass" end if controller_manager_command_line.index{|line| line =~ /--insecure-experimental-approve-all-kubelet-csrs-for-group/} @results[target]['controller_manager']['CIS 1.3.3 - Ensure that the --insecure-experimental-approve-all-kubelet-csrs-for-group argument is not set'] = "Fail" else @results[target]['controller_manager']['CIS 1.3.3 - Ensure that the --insecure-experimental-approve-all-kubelet-csrs-for-group argument is not set'] = "Pass" end unless controller_manager_command_line.index{|line| line =~ /--use-service-account-credentials=true/} @results[target]['controller_manager']['CIS 1.3.3 - Ensure that the --use-service-account-credentials argument is set to true'] = "Fail" else @results[target]['controller_manager']['CIS 1.3.3 - Ensure that the --use-service-account-credentials argument is set to true'] = "Pass" end unless controller_manager_command_line.index{|line| line =~ /--service-account-private-key-file/} @results[target]['controller_manager']['CIS 1.3.4 - Ensure that the --service-account-private-key-file argument is set as appropriate'] = "Fail" else @results[target]['controller_manager']['CIS 1.3.4 - Ensure that the --service-account-private-key-file argument is set as appropriate'] = "Pass" end unless controller_manager_command_line.index{|line| line =~ /--root-ca-file/} @results[target]['controller_manager']['CIS 1.3.5 - Ensure that the --root-ca-file argument is set as appropriate'] = "Fail" else @results[target]['controller_manager']['CIS 1.3.5 - Ensure that the --root-ca-file argument is set as appropriate'] = "Pass" end unless controller_manager_command_line.index{|line| line =~ /RotateKubeletServerCertificate=true/} @results[target]['controller_manager']['CIS 1.3.7 - Ensure that the RotateKubeletServerCertificate argument is set to true'] = "Fail" else @results[target]['controller_manager']['CIS 1.3.7 - Ensure that the RotateKubeletServerCertificate argument is set to true'] = "Pass" end @results[target]['evidence']['Controller Manager'] = controller_manager_command_line end |
.test_etcd ⇒ Object
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 |
# File 'lib/kube_auto_analyzer/api_checks/master_node.rb', line 342 def self.test_etcd target = @options.target_server @results[target]['etcd'] = Hash.new pods = @client.get_pods pods.each do |pod| #Ok this is a bit naive as a means of hitting the API server but hey it's a start if pod['metadata']['name'] =~ /etcd/ @etcd = pod end end unless @etcd @results[target]['etcd']['etcd Pod Not Found'] = "Error" return end etcd_command_line = @etcd['spec']['containers'][0]['command'] unless (etcd_command_line.index{|line| line =~ /--cert-file/} && etcd_command_line.index{|line| line =~ /--key-file/}) @results[target]['etcd']['CIS 1.5.1 - Ensure that the --cert-file and --key-file arguments are set as appropriate'] = "Fail" else @results[target]['etcd']['CIS 1.5.1 - Ensure that the --cert-file and --key-file arguments are set as appropriate'] = "Pass" end unless etcd_command_line.index{|line| line =~ /--client-cert-auth=true/} @results[target]['etcd']['CIS 1.5.2 - Ensure that the --client-cert-auth argument is set to true'] = "Fail" else @results[target]['etcd']['CIS 1.5.2 - Ensure that the --client-cert-auth argument is set to true'] = "Pass" end if etcd_command_line.index{|line| line =~ /--auto-tls argument=true/} @results[target]['etcd']['CIS 1.5.3 - Ensure that the --auto-tls argument is not set to true'] = "Fail" else @results[target]['etcd']['CIS 1.5.3 - Ensure that the --auto-tls argument is not set to true'] = "Pass" end unless (etcd_command_line.index{|line| line =~ /--peer-cert-file/} && etcd_command_line.index{|line| line =~ /--peer-key-file/}) @results[target]['etcd']['CIS 1.5.4 - Ensure that the --peer-cert-file and --peer-key-file arguments are set as appropriate'] = "Fail" else @results[target]['etcd']['CIS 1.5.4 - Ensure that the --peer-cert-file and --peer-key-file arguments are set as appropriate'] = "Pass" end unless etcd_command_line.index{|line| line =~ /--peer-client-cert-auth=true/} @results[target]['etcd']['CIS 1.5.5 - Ensure that the --peer-client-cert-auth argument is set to true'] = "Fail" else @results[target]['etcd']['CIS 1.5.5 - Ensure that the --peer-client-cert-auth argument is set to true'] = "Pass" end if etcd_command_line.index{|line| line =~ /--peer-auto-tls argument=true/} @results[target]['etcd']['CIS 1.5.6 - Ensure that the --peer-auto-tls argument is not set to true'] = "Fail" else @results[target]['etcd']['CIS 1.5.6 - Ensure that the --peer-auto-tls argument is not set to true'] = "Pass" end #This isn't quite right as we should really check the dir. but as that's not easily done lets start with an existence check unless etcd_command_line.index{|line| line =~ /--wal-dir/} @results[target]['etcd']['CIS 1.5.7 - Ensure that the --wal-dir argument is set as appropriate'] = "Fail" else @results[target]['etcd']['CIS 1.5.7 - Ensure that the --wal-dir argument is set as appropriate'] = "Pass" end unless etcd_command_line.index{|line| line =~ /--max-wals=0/} @results[target]['etcd']['CIS 1.5.8 - Ensure that the --max-wals argument is set to 0'] = "Fail" else @results[target]['etcd']['CIS 1.5.8 - Ensure that the --max-wals argument is set to 0'] = "Pass" end @results[target]['evidence']['etcd'] = etcd_command_line end |
.test_insecure_api_external ⇒ Object
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/kube_auto_analyzer/vuln_checks/api_server.rb', line 3 def self.test_insecure_api_external @log.debug("Doing the external Insecure API check") target = @options.target_server unless @results[target]['vulns'] @results[target]['vulns'] = Hash.new end @results[target]['vulns']['insecure_api_external'] = Hash.new #Check for whether the Insecure API port is visible outside the cluster nodes = Array.new @client.get_nodes.each do |node| nodes << node['status']['addresses'][0]['address'] end nodes.each do |nod| if is_port_open?(nod, 8080) begin pods_resp = RestClient::Request.execute(:url => "http://#{nod}:8080/api",:method => :get) rescue RestClient::Forbidden pods_resp = "Not Vulnerable - Request Forbidden" rescue RestClient::NotFound pods_resp = "Not Vulnerable - Request Not Found" end @results[target]['vulns']['insecure_api_external'][nod] = pods_resp else @results[target]['vulns']['insecure_api_external'][nod] = "Not Vulnerable - Port Not Open" end end end |
.test_insecure_api_internal ⇒ Object
This is somewhat awkward placement. Deployment mechanism sits more with the agent checks But from a “what it’s looking for” perspective, as a weakness in API Server, it makes more sense here.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/kube_auto_analyzer/vuln_checks/api_server.rb', line 33 def self.test_insecure_api_internal require 'json' @log.debug("Doing the internal Insecure API Server check") target = @options.target_server @results[target]['vulns']['insecure_api_internal'] = Hash.new nodes = Array.new @client.get_nodes.each do |node| nodes << node['status']['addresses'][0]['address'] end container_name = "kaainsecureapitest" pod = Kubeclient::Resource.new pod. = {} pod..name = container_name pod..namespace = "default" pod.spec = {} pod.spec.restartPolicy = "Never" pod.spec.containers = {} pod.spec.containers = [{name: "kubeautoanalyzerapitest", image: "raesene/kaa-agent:latest"}] pod.spec.containers[0].args = ["/api-server-checker.rb",nodes.join(',')] begin @log.debug("About to start API Server check pod") @client.create_pod(pod) @log.debug("Executed the create pod") sleep_count = 0 begin sleep(5) until @client.get_pod(container_name,"default")['status']['containerStatuses'][0]['state']['terminated']['reason'] == "Completed" sleep_count = sleep_count + 1 @log.debug("Waited #{(5 * sleep_count).to_s} seconds for the API Server Check Pod") rescue retry end @log.debug ("started Kube API Check pod") results = JSON.parse(@client.get_pod_log(container_name,"default")) results.each do |node, results| @results[target]['vulns']['insecure_api_internal'][node] = results end ensure @client.delete_pod(container_name,"default") end end |
.test_scheduler ⇒ Object
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/kube_auto_analyzer/api_checks/master_node.rb', line 251 def self.test_scheduler target = @options.target_server @results[target]['scheduler'] = Hash.new pods = @client.get_pods pods.each do |pod| #Ok this is a bit naive as a means of hitting the API server but hey it's a start if pod['metadata']['name'] =~ /kube-scheduler/ @scheduler = pod end end unless @scheduler @results[target]['scheduler']['Scheduler Pod Not Found'] = "Error" return end scheduler_command_line = @scheduler['spec']['containers'][0]['command'] unless scheduler_command_line.index{|line| line =~ /--profiling=false/} @results[target]['scheduler']['CIS 1.2.1 - Ensure that the --profiling argument is set to false'] = "Fail" else @results[target]['scheduler']['CIS 1.2.1 - Ensure that the --profiling argument is set to false'] = "Pass" end @results[target]['evidence']['Scheduler'] = scheduler_command_line end |
.test_service_token_internal ⇒ Object
This is somewhat awkward placement. Deployment mechanism sits more with the agent checks But from a “what it’s looking for” perspective, as a weakness in Kubelet, it makes more sense here.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/kube_auto_analyzer/vuln_checks/service_token.rb', line 5 def self.test_service_token_internal require 'json' @log.debug("Doing the internal Service Token check") target = @options.target_server @results[target]['vulns']['service_token'] = Hash.new api_server_url = @client.api_endpoint.to_s container_name = "kaakubeletunauthtest" pod = Kubeclient::Resource.new pod. = {} pod..name = container_name pod..namespace = "default" pod.spec = {} pod.spec.restartPolicy = "Never" pod.spec.containers = {} pod.spec.containers = [{name: "kubeautoanalyzerservicetokentest", image: "raesene/kaa-agent:latest"}] pod.spec.containers[0].args = ["/service-token-checker.rb",api_server_url] begin @log.debug("About to start Service Token Check pod") @client.create_pod(pod) @log.debug("Executed the create pod") begin sleep(5) until @client.get_pod(container_name,"default")['status']['containerStatuses'][0]['state']['terminated']['reason'] == "Completed" rescue retry end @log.debug ("started Service Token Check pod") results = JSON.parse(@client.get_pod_log(container_name,"default")) results.each do |node, results| @results[target]['vulns']['service_token'][api_server_url] = results end ensure @client.delete_pod(container_name,"default") end end |
.test_unauth_kubelet_external ⇒ Object
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/kube_auto_analyzer/vuln_checks/kubelet.rb', line 3 def self.test_unauth_kubelet_external @log.debug("Doing the external kubelet check") target = @options.target_server unless @results[target]['vulns'] @results[target]['vulns'] = Hash.new end @results[target]['vulns']['unauth_kubelet'] = Hash.new #Check for whether the Kubelet port is visible outside the cluster nodes = Array.new @client.get_nodes.each do |node| nodes << node['status']['addresses'][0]['address'] end nodes.each do |nod| if is_port_open?(nod, 10250) begin pods_resp = RestClient::Request.execute(:url => "https://#{nod}:10250/runningpods",:method => :get, :verify_ssl => false) rescue RestClient::Forbidden pods_resp = "Not Vulnerable - Request Forbidden" rescue RestClient::Unauthorized pods_resp = "Not Vulnerable - Request Unauthorized" end @results[target]['vulns']['unauth_kubelet'][nod] = pods_resp else @results[target]['vulns']['unauth_kubelet'][nod] = "Not Vulnerable - Port Not Open" end end end |
.test_unauth_kubelet_internal ⇒ Object
This is somewhat awkward placement. Deployment mechanism sits more with the agent checks But from a “what it’s looking for” perspective, as a weakness in Kubelet, it makes more sense here.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/kube_auto_analyzer/vuln_checks/kubelet.rb', line 33 def self.test_unauth_kubelet_internal require 'json' @log.debug("Doing the internal kubelet check") target = @options.target_server @results[target]['vulns']['internal_kubelet'] = Hash.new nodes = Array.new @client.get_nodes.each do |node| nodes << node['status']['addresses'][0]['address'] end container_name = "kaakubeletunauthtest" pod = Kubeclient::Resource.new pod. = {} pod..name = container_name pod..namespace = "default" pod.spec = {} pod.spec.restartPolicy = "Never" pod.spec.containers = {} pod.spec.containers = [{name: "kubeautoanalyzerkubelettest", image: "raesene/kaa-agent:latest"}] pod.spec.containers[0].args = ["/kubelet-checker.rb",nodes.join(',')] begin @log.debug("About to start Kubelet check pod") @client.create_pod(pod) @log.debug("Executed the create pod") begin sleep(5) until @client.get_pod(container_name,"default")['status']['containerStatuses'][0]['state']['terminated']['reason'] == "Completed" rescue retry end @log.debug ("started Kubelet Check pod") results = JSON.parse(@client.get_pod_log(container_name,"default")) results.each do |node, results| @results[target]['vulns']['internal_kubelet'][node] = results end ensure @client.delete_pod(container_name,"default") end end |