diff --git a/offline-cluster-analyzer.rb b/offline-cluster-analyzer.rb
index ec75f97..04851fb 100755
--- a/offline-cluster-analyzer.rb
+++ b/offline-cluster-analyzer.rb
@@ -39,7 +39,7 @@ def pod_info
@log.debug("Starting Pod Info")
@cluster_info['pods'] = Array.new
@data['items'].each do |item|
- if item['kind'] == "Pod"
+ if item['kind'] == "Pod" && item['apiVersion'] == "v1"
@cluster_info['pods'] << item
end
end
@@ -50,7 +50,7 @@ def security_context_info
@cluster_info['security_contexts'] = Hash.new
@data['items'].each do |item|
@log.debug("about to do a pod in security context")
- if item['kind'] == "Pod"
+ if item['kind'] == "Pod" && item['apiVersion'] == "v1"
podname = item['metadata']['name']
namespace = item['metadata']['namespace']
#Lets get some security context info out of the containers
@@ -107,18 +107,38 @@ def namespace_info
@log.debug("Starting Namespace Info")
@cluster_info['namespaces'] = Array.new
@data['items'].each do |item|
- if item['kind'] == "Namespace"
+ if item['kind'] == "Namespace" && item['apiVersion'] == "v1"
@cluster_info['namespaces'] << item['metadata']['name']
end
end
@log.debug("Found #{@cluster_info['namespaces'].length.to_s } namespaces")
end
+ def object_counts
+ @log.debug("Starting Object Counts")
+ @cluster_info['object_counts'] = Hash.new
+ @data['items'].each do |item|
+ if item['metadata']['namespace']
+ namespace = item['metadata']['namespace']
+ else
+ namespace = 'cluster_wide'
+ end
+ unless @cluster_info['object_counts'][namespace]
+ @cluster_info['object_counts'][namespace] = Hash.new
+ end
+ object_type = item['apiVersion'] + '/' + item['kind']
+ unless @cluster_info['object_counts'][namespace][object_type]
+ @cluster_info['object_counts'][namespace][object_type] = 0
+ end
+ @cluster_info['object_counts'][namespace][object_type] = @cluster_info['object_counts'][namespace][object_type] + 1
+ end
+ end
+
def node_info
@log.debug("Starting node Info")
@cluster_info['nodes'] = Array.new
@data['items'].each do |item|
- if item['kind'] == "Node"
+ if item['kind'] == "Node" && item['apiVersion'] == "v1"
ip_addresses = Array.new
item['status']['addresses'].each do |add|
if add["type"] == "InternalIP"
@@ -154,30 +174,66 @@ def rbac_info
@cluster_info['clusterrolebindings'] = Array.new
@cluster_info['roles'] = Array.new
@cluster_info['rolebindings'] = Array.new
+ @cluster_info['users'] = Array.new
+ @cluster_info['groups'] = Array.new
+ @cluster_info['service_accounts'] = Array.new
+
@clusterroles.each do |item|
- if item['kind'] == "ClusterRole"
+ if item['kind'] == "ClusterRole" && item['apiVersion'] == "rbac.authorization.k8s.io/v1"
@cluster_info['clusterroles'] << item['metadata']['name']
end
end
@clusterrolebindings.each do |item|
- if item['kind'] == "ClusterRoleBinding"
+ if item['kind'] == "ClusterRoleBinding" && item['apiVersion'] == "rbac.authorization.k8s.io/v1"
@cluster_info['clusterrolebindings'] << item['metadata']['name']
@log.debug("added a clusterrolebinding")
+
+ # Not all Cluster Role Bindings have subjects?
+ if item['subjects']
+ item['subjects'].each do |subject|
+ case subject['kind']
+ when "ServiceAccount"
+ @cluster_info['service_accounts'] << subject['name']
+ when "Group"
+ @cluster_info['groups'] << subject['name']
+ when "User"
+ @cluster_info['users'] << subject['name']
+ end
+ end
+ end
end
end
@roles.each do |item|
- if item['kind'] == "Role"
+ if item['kind'] == "Role" && item['apiVersion'] == "rbac.authorization.k8s.io/v1"
@cluster_info['roles'] << item['metadata']['name']
end
end
@rolebindings.each do |item|
- if item['kind'] == "RoleBinding"
+ if item['kind'] == "RoleBinding" && item['apiVersion'] == "rbac.authorization.k8s.io/v1"
@cluster_info['rolebindings'] << item['metadata']['name']
end
+
+ if item['subjects']
+ item['subjects'].each do |subject|
+ case subject['kind']
+ when "ServiceAccount"
+ @cluster_info['service_accounts'] << subject['name']
+ when "Group"
+ @cluster_info['groups'] << subject['name']
+ when "User"
+ @cluster_info['users'] << subject['name']
+ end
+ end
+ end
end
+
+ @log.debug("Got #{@cluster_info['service_accounts'].length.to_s} service accounts")
+ @log.debug("Got #{@cluster_info['users'].length.to_s} Users")
+ @log.debug("Got #{@cluster_info['groups'].length.to_s} Groups")
+
end
def parseclusterroles
@@ -308,7 +364,7 @@ def service_info
@log.debug("Starting Service Info")
@cluster_info['services'] = Hash.new
@data['items'].each do |item|
- if item['kind'] == "Service"
+ if item['kind'] == "Service" && item['apiVersion'] == "v1"
ports = Array.new
begin
item['spec']['ports'].each do |p|
@@ -328,6 +384,25 @@ def service_info
end
+ def netpol_info
+ @log.debug("Starting Network Policy Info")
+ @cluster_info['netpol'] = Hash.new
+ @data['items'].each do |item|
+ if item['kind'] == "NetworkPolicy" && item['apiVersion'] == "networking.k8s.io/v1"
+ direction = item['spec']['policyTypes']
+ namespace = item['metadata']['namespace']
+ @log.debug("Direction is #{direction} namespace is #{namespace}")
+ unless @cluster_info['netpol'][namespace]
+ @cluster_info['netpol'][namespace] = Hash.new
+ end
+ unless @cluster_info['netpol'][namespace][direction]
+ @cluster_info['netpol'][namespace][direction] = Array.new
+ end
+ @cluster_info['netpol'][namespace][direction] << item['spec']
+ end
+ end
+ end
+
def report
@log.debug("Starting Report")
@html_report_file = File.new(@options.report_file + '.html','w+')
@@ -461,6 +536,25 @@ def report
@html_report_file.puts "
#{@cluster_info['container_images'].sort.join(' ')} |
"
@html_report_file.puts ""
+ # Service Account Section. Unique service accounts that have one or more RBAC rule defined
+ @html_report_file.puts "Unique Service Accounts Used In Cluster which have one or more RBAC rule
"
+ @html_report_file.puts "| Service Account Name |
"
+ @html_report_file.puts "#{@cluster_info['service_accounts'].uniq.sort.join(' ')} |
"
+ @html_report_file.puts "
"
+
+ # User Section. Unique Users that have one or more RBAC rule defined
+ @html_report_file.puts "Unique User Accounts Used In Cluster which have one or more RBAC rule
"
+ @html_report_file.puts "| User Name |
"
+ @html_report_file.puts "#{@cluster_info['users'].uniq.sort.join(' ')} |
"
+ @html_report_file.puts "
"
+
+ # Group Section. Unique Groups that have one or more RBAC rule defined
+ @html_report_file.puts "Unique Groups Used In Cluster which have one or more RBAC rule
"
+ @html_report_file.puts "| Group Name |
"
+ @html_report_file.puts "#{@cluster_info['groups'].uniq.sort.join(' ')} |
"
+ @html_report_file.puts "
"
+
+
@html_report_file.puts "
"
@html_report_file.puts "Privileged Roles
"
@rbac_security_check_results[:get_secrets].each do |namespace, roles|
@@ -572,6 +666,21 @@ def report
@html_report_file.puts ""
@html_report_file.puts "
"
+ @html_report_file.puts "
Network Policy information
"
+ if @cluster_info['netpol'].length == 0
+ @html_report_file.puts "No Network Policy Objects Found"
+ else
+ @cluster_info['netpol'].each do |namespace,dirs|
+ @html_report_file.puts "
Network policies for #{namespace}
"
+ @html_report_file.puts "| Direction | Rule |
"
+ @log.debug("dir is #{dirs.keys}")
+ dirs.each do |dir,pol|
+ @html_report_file.puts "| #{dir} | #{pol.join(' ')} |
"
+ end
+ @html_report_file.puts "
"
+ end
+ end
+
@html_report_file.puts "
Security Context Information
"
@html_report_file.puts "| Namespace | Pod Name | Container Name | Security context |
"
@cluster_info['security_contexts'].each do |name, seccon|
@@ -598,6 +707,20 @@ def report
@html_report_file.puts "| nmap -sT -Pn -p #{ports.join(',')} #{ips.join(' ')} |
"
@html_report_file.puts "
"
+ # Object Counts Section
+
+ @html_report_file.puts "
"
+ @html_report_file.puts "Cluster Object Counts
"
+ @cluster_info['object_counts'].each do |namespace, objects|
+ @log.debug("Object count is is #{objects.length.to_s}")
+ @html_report_file.puts "#{namespace}
"
+ @html_report_file.puts "| Object Type | Object Count |
"
+ objects.each do |object, count|
+ @html_report_file.puts "| #{object} | #{count} |
"
+ end
+ @html_report_file.puts "
"
+ end
+
@html_report_file.puts "