kubenumerate 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ExtensiveRoleCheck.py +449 -0
- formatter.py +60 -0
- kubenumerate-2.0.0.dist-info/METADATA +926 -0
- kubenumerate-2.0.0.dist-info/RECORD +10 -0
- kubenumerate-2.0.0.dist-info/WHEEL +5 -0
- kubenumerate-2.0.0.dist-info/entry_points.txt +2 -0
- kubenumerate-2.0.0.dist-info/licenses/LICENSE +674 -0
- kubenumerate-2.0.0.dist-info/top_level.txt +4 -0
- kubenumerate.py +3004 -0
- version.py +190 -0
ExtensiveRoleCheck.py
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import argparse
|
|
3
|
+
import logging
|
|
4
|
+
from colorama import init, Fore
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def str2bool(v):
|
|
8
|
+
if isinstance(v, bool):
|
|
9
|
+
return v
|
|
10
|
+
if v.lower() in ("yes", "true", "t", "y", "1"):
|
|
11
|
+
return True
|
|
12
|
+
elif v.lower() in ("no", "false", "f", "n", "0"):
|
|
13
|
+
return False
|
|
14
|
+
else:
|
|
15
|
+
raise argparse.ArgumentTypeError("Boolean value expected.")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_argument_parser():
|
|
19
|
+
parser = argparse.ArgumentParser()
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"--clusterRole",
|
|
22
|
+
type=str,
|
|
23
|
+
required=False,
|
|
24
|
+
help="ClusterRoles JSON file",
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument("--role", type=str, required=False, help="roles JSON file")
|
|
27
|
+
parser.add_argument("--rolebindings", type=str, required=False, help="RoleBindings JSON file")
|
|
28
|
+
parser.add_argument("--clusterrolebindings", type=str, required=False, help="ClusterRoleBindings JSON file")
|
|
29
|
+
parser.add_argument("--pods", type=str, required=False, help="pods JSON file")
|
|
30
|
+
parser.add_argument("--outputjson", type=str, required=False, help="Output JSON file")
|
|
31
|
+
|
|
32
|
+
return parser.parse_args()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Read data from files
|
|
36
|
+
def open_file(file_path):
|
|
37
|
+
with open(file_path) as f:
|
|
38
|
+
return json.load(f)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ExtensiveRolesChecker(object):
|
|
42
|
+
def __init__(self, json_file, role_kind):
|
|
43
|
+
init()
|
|
44
|
+
self._role = logging.getLogger(role_kind)
|
|
45
|
+
self._role_handler = logging.StreamHandler()
|
|
46
|
+
self._role_format = logging.Formatter(f"{Fore.YELLOW}[!][%(name)s]{Fore.WHITE}\u2192 %(message)s")
|
|
47
|
+
self._role_handler.setFormatter(self._role_format)
|
|
48
|
+
self._role.addHandler(self._role_handler)
|
|
49
|
+
self._json_file = json_file
|
|
50
|
+
self._results = {}
|
|
51
|
+
self._generate()
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def results(self):
|
|
55
|
+
return self._results
|
|
56
|
+
|
|
57
|
+
def add_result(self, name, value):
|
|
58
|
+
if not name:
|
|
59
|
+
return
|
|
60
|
+
if not (name in self._results.keys()):
|
|
61
|
+
self._results[name] = [value]
|
|
62
|
+
else:
|
|
63
|
+
self._results[name].append(value)
|
|
64
|
+
|
|
65
|
+
def _generate(self):
|
|
66
|
+
for entity in self._json_file["items"]:
|
|
67
|
+
role_name = entity["metadata"]["name"]
|
|
68
|
+
try:
|
|
69
|
+
iter(entity["rules"])
|
|
70
|
+
except TypeError:
|
|
71
|
+
continue
|
|
72
|
+
for rule in entity["rules"]:
|
|
73
|
+
if not rule.get("resources", None):
|
|
74
|
+
continue
|
|
75
|
+
self.get_read_secrets(rule, role_name)
|
|
76
|
+
self.get_read_configmaps(rule, role_name)
|
|
77
|
+
self.clusteradmin_role(rule, role_name)
|
|
78
|
+
self.any_resources(rule, role_name)
|
|
79
|
+
self.any_verb(rule, role_name)
|
|
80
|
+
self.high_risk_roles(rule, role_name)
|
|
81
|
+
self.role_and_roleBindings(rule, role_name)
|
|
82
|
+
self.create_pods(rule, role_name)
|
|
83
|
+
self.pods_exec(rule, role_name)
|
|
84
|
+
self.pods_attach(rule, role_name)
|
|
85
|
+
|
|
86
|
+
# Read cluster secrets:
|
|
87
|
+
def get_read_secrets(self, rule, role_name):
|
|
88
|
+
verbs = ["*", "get", "list"]
|
|
89
|
+
if "secrets" in rule["resources"] and any([sign for sign in verbs if sign in rule["verbs"]]):
|
|
90
|
+
filtered_name = self.get_non_default_name(role_name)
|
|
91
|
+
if filtered_name:
|
|
92
|
+
self._role.warning(f"{Fore.GREEN}{filtered_name}" + f"{Fore.RED} Has permission to list secrets!")
|
|
93
|
+
self.add_result(filtered_name, "Has permission to list secrets!")
|
|
94
|
+
|
|
95
|
+
# Read configmaps, devs store creds in configmaps all the time
|
|
96
|
+
def get_read_configmaps(self, rule, role_name):
|
|
97
|
+
verbs = ["*", "get", "list"]
|
|
98
|
+
if "configmaps" in rule["resources"] and any([sign for sign in verbs if sign in rule["verbs"]]):
|
|
99
|
+
filtered_name = self.get_non_default_name(role_name)
|
|
100
|
+
if filtered_name:
|
|
101
|
+
self._role.warning(f"{Fore.GREEN}{filtered_name}" + f"{Fore.RED} Has permission to list configmaps!")
|
|
102
|
+
self.add_result(filtered_name, "Has permission to list configmaps!")
|
|
103
|
+
|
|
104
|
+
# Any Any roles
|
|
105
|
+
def clusteradmin_role(self, rule, role_name):
|
|
106
|
+
if "*" in rule["resources"] and "*" in rule["verbs"]:
|
|
107
|
+
filtered_name = self.get_non_default_name(role_name)
|
|
108
|
+
if filtered_name:
|
|
109
|
+
self._role.warning(f"{Fore.GREEN}{filtered_name}" + f"{Fore.RED} Has Admin-Cluster permission!")
|
|
110
|
+
self.add_result(filtered_name, "Has Admin-Cluster permission!")
|
|
111
|
+
|
|
112
|
+
# get ANY verbs:
|
|
113
|
+
def any_verb(self, rule, role_name):
|
|
114
|
+
resources = [
|
|
115
|
+
"secrets",
|
|
116
|
+
"configmaps",
|
|
117
|
+
"pods",
|
|
118
|
+
"deployments",
|
|
119
|
+
"daemonsets",
|
|
120
|
+
"statefulsets",
|
|
121
|
+
"replicationcontrollers",
|
|
122
|
+
"replicasets",
|
|
123
|
+
"cronjobs",
|
|
124
|
+
"jobs",
|
|
125
|
+
"roles",
|
|
126
|
+
"clusterroles",
|
|
127
|
+
"rolebindings",
|
|
128
|
+
"clusterrolebindings",
|
|
129
|
+
"users",
|
|
130
|
+
"groups",
|
|
131
|
+
]
|
|
132
|
+
found_sign = [sign for sign in resources if sign in rule["resources"]]
|
|
133
|
+
if not found_sign:
|
|
134
|
+
return
|
|
135
|
+
if "*" in rule["verbs"]:
|
|
136
|
+
filtered_name = self.get_non_default_name(role_name)
|
|
137
|
+
if filtered_name:
|
|
138
|
+
self._role.warning(
|
|
139
|
+
f"{Fore.GREEN}{filtered_name}"
|
|
140
|
+
+ f"{Fore.RED} Has permission to access {found_sign[0]} with any verb!"
|
|
141
|
+
)
|
|
142
|
+
self.add_result(filtered_name, f"Has permission to access {found_sign[0]} with any verb!")
|
|
143
|
+
|
|
144
|
+
def any_resources(self, rule, role_name):
|
|
145
|
+
verbs = ["delete", "deletecollection", "create", "list", "get", "impersonate"]
|
|
146
|
+
found_sign = [sign for sign in verbs if sign in rule["verbs"]]
|
|
147
|
+
if not found_sign:
|
|
148
|
+
return
|
|
149
|
+
if "*" in rule["resources"]:
|
|
150
|
+
filtered_name = self.get_non_default_name(role_name)
|
|
151
|
+
if filtered_name:
|
|
152
|
+
self._role.warning(
|
|
153
|
+
f"{Fore.GREEN}{filtered_name}"
|
|
154
|
+
+ f"{Fore.RED} Has permission to use {found_sign[0]} on any resource!"
|
|
155
|
+
)
|
|
156
|
+
self.add_result(filtered_name, f"Has permission to use {found_sign[0]} on any resource")
|
|
157
|
+
|
|
158
|
+
def high_risk_roles(self, rule, role_name):
|
|
159
|
+
verb_actions = ["create", "update"]
|
|
160
|
+
# adding pods to cover privilege pod scenarios and pods being used for mining example
|
|
161
|
+
resources_attributes = [
|
|
162
|
+
"pods",
|
|
163
|
+
"deployments",
|
|
164
|
+
"daemonsets",
|
|
165
|
+
"statefulsets",
|
|
166
|
+
"replicationcontrollers",
|
|
167
|
+
"replicasets",
|
|
168
|
+
"jobs",
|
|
169
|
+
"cronjobs",
|
|
170
|
+
]
|
|
171
|
+
found_attribute = [attribute for attribute in resources_attributes if attribute in rule["resources"]]
|
|
172
|
+
if not (found_attribute):
|
|
173
|
+
return
|
|
174
|
+
found_actions = [action for action in verb_actions if action in rule["verbs"]]
|
|
175
|
+
if not (found_actions):
|
|
176
|
+
return
|
|
177
|
+
filtered_name = self.get_non_default_name(role_name)
|
|
178
|
+
if filtered_name:
|
|
179
|
+
self._role.warning(
|
|
180
|
+
f"{Fore.GREEN}{filtered_name}"
|
|
181
|
+
+ f"{Fore.RED} Has permission to {found_actions[0]} {found_attribute[0]}!"
|
|
182
|
+
)
|
|
183
|
+
self.add_result(filtered_name, f"Has permission to {found_actions[0]} {found_attribute[0]}!")
|
|
184
|
+
|
|
185
|
+
def role_and_roleBindings(self, rule, role_name):
|
|
186
|
+
resources_attributes = ["rolebindings", "roles", "clusterrolebindings"]
|
|
187
|
+
found_attribute = [attribute for attribute in resources_attributes if attribute in rule["resources"]]
|
|
188
|
+
if not found_attribute:
|
|
189
|
+
return
|
|
190
|
+
if "create" in rule["verbs"]:
|
|
191
|
+
filtered_name = self.get_non_default_name(role_name)
|
|
192
|
+
if filtered_name:
|
|
193
|
+
self._role.warning(
|
|
194
|
+
f"{Fore.GREEN}{filtered_name}" + f"{Fore.RED} Has permission to create {found_attribute[0]}!"
|
|
195
|
+
)
|
|
196
|
+
self.add_result(filtered_name, f"Has permission to create {found_attribute[0]}!")
|
|
197
|
+
|
|
198
|
+
def create_pods(self, rule, role_name):
|
|
199
|
+
if "pods" in rule["resources"] and "create" in rule["verbs"]:
|
|
200
|
+
filtered_name = self.get_non_default_name(role_name)
|
|
201
|
+
if filtered_name:
|
|
202
|
+
self._role.warning(f"{Fore.GREEN}{filtered_name}" + f"{Fore.RED} Has permission to create pods!")
|
|
203
|
+
self.add_result(filtered_name, "Has permission to create pods!")
|
|
204
|
+
|
|
205
|
+
def pods_exec(self, rule, role_name):
|
|
206
|
+
if "pods/exec" in rule["resources"] and "create" in rule["verbs"]:
|
|
207
|
+
filtered_name = self.get_non_default_name(role_name)
|
|
208
|
+
if filtered_name:
|
|
209
|
+
self._role.warning(f"{Fore.GREEN}{filtered_name}" + f"{Fore.RED} Has permission to use pod exec!")
|
|
210
|
+
self.add_result(filtered_name, "Has permission to use pod exec!")
|
|
211
|
+
|
|
212
|
+
def pods_attach(self, rule, role_name):
|
|
213
|
+
if "pods/attach" in rule["resources"] and "create" in rule["verbs"]:
|
|
214
|
+
filtered_name = self.get_non_default_name(role_name)
|
|
215
|
+
if filtered_name:
|
|
216
|
+
self._role.warning(f"{Fore.GREEN}{filtered_name}" + f"{Fore.RED} Has permission to attach pods!")
|
|
217
|
+
self.add_result(filtered_name, "Has permission to attach pods!")
|
|
218
|
+
|
|
219
|
+
@staticmethod
|
|
220
|
+
def get_non_default_name(name):
|
|
221
|
+
if not (
|
|
222
|
+
(name[:7] == "system:")
|
|
223
|
+
or (name == "edit")
|
|
224
|
+
or (name == "admin")
|
|
225
|
+
or (name == "cluster-admin")
|
|
226
|
+
or (name == "aws-node")
|
|
227
|
+
or (name[:11] == "kubernetes-")
|
|
228
|
+
):
|
|
229
|
+
return name
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class roleBindingChecker(object):
|
|
233
|
+
def __init__(self, json_file, extensive_roles, bind_kind):
|
|
234
|
+
self._json_file = json_file
|
|
235
|
+
self._extensive_roles = extensive_roles
|
|
236
|
+
self._bind_kind = bind_kind
|
|
237
|
+
self._results = []
|
|
238
|
+
self.subject_risky_roles_mapping = []
|
|
239
|
+
self.bindsCheck()
|
|
240
|
+
|
|
241
|
+
def bindsCheck(self):
|
|
242
|
+
_rolebinding_found = []
|
|
243
|
+
for entity in self._json_file["items"]:
|
|
244
|
+
_role_name = entity["metadata"]["name"]
|
|
245
|
+
_rol_ref = entity["roleRef"]["name"]
|
|
246
|
+
if not entity.get("subjects", None):
|
|
247
|
+
continue
|
|
248
|
+
if _rol_ref in self._extensive_roles:
|
|
249
|
+
_rolebinding_found.append(_rol_ref)
|
|
250
|
+
for sub in entity["subjects"]:
|
|
251
|
+
if not sub.get("name", None):
|
|
252
|
+
continue
|
|
253
|
+
self.add_role_binding_mapping(sub, entity)
|
|
254
|
+
self.print_rolebinding_results(sub, _role_name, self._bind_kind)
|
|
255
|
+
return _rolebinding_found
|
|
256
|
+
|
|
257
|
+
def print_rolebinding_results(self, sub, role_name, bind_kind):
|
|
258
|
+
if sub["kind"] == "ServiceAccount":
|
|
259
|
+
print(
|
|
260
|
+
f"{Fore.YELLOW}[!][{bind_kind}]{Fore.WHITE}\u2192 "
|
|
261
|
+
+ f'{Fore.GREEN}{role_name}{Fore.RED} is bound to {sub["name"]} ServiceAccount.'
|
|
262
|
+
)
|
|
263
|
+
else:
|
|
264
|
+
print(
|
|
265
|
+
f"{Fore.YELLOW}[!][{bind_kind}]{Fore.WHITE}\u2192 "
|
|
266
|
+
+ f'{Fore.GREEN}{role_name}{Fore.RED} is bound to the {sub["kind"]}: {sub["name"]}!'
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def add_role_binding_mapping(self, sub, binding):
|
|
270
|
+
element = next((item for item in self.subject_risky_roles_mapping if item["name"] == sub.get("name")), None)
|
|
271
|
+
|
|
272
|
+
if element is None:
|
|
273
|
+
element = {"kind": sub.get("kind"), "name": sub.get("name"), "riskyRoles": []}
|
|
274
|
+
self.subject_risky_roles_mapping.append(element)
|
|
275
|
+
|
|
276
|
+
element.get("riskyRoles").append(
|
|
277
|
+
{
|
|
278
|
+
"apiGroup": binding.get("roleRef", {}).get("apiGroup", {}),
|
|
279
|
+
"kind": binding.get("roleRef", {}).get("kind", {}),
|
|
280
|
+
"name": binding.get("roleRef", {}).get("name", {}),
|
|
281
|
+
"bindingKind": binding.get("kind", ""),
|
|
282
|
+
"bindingName": binding.get("metadata", {}).get("name"),
|
|
283
|
+
}
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class SubjectViewer:
|
|
288
|
+
def __init__(self, subject_risky_roles_mapping, checker, all_pods=None):
|
|
289
|
+
self.subject_risky_roles_mapping = subject_risky_roles_mapping
|
|
290
|
+
self.checker = checker
|
|
291
|
+
self.all_pods = all_pods
|
|
292
|
+
self.__prepare_json()
|
|
293
|
+
|
|
294
|
+
def print_risky_roles_for_subjects(self):
|
|
295
|
+
for subject in self.subject_risky_roles_mapping:
|
|
296
|
+
print("{color}{kind}: {name}".format(color=Fore.YELLOW, kind=subject.get("kind"), name=subject.get("name")))
|
|
297
|
+
|
|
298
|
+
for role in subject.get("riskyRoles"):
|
|
299
|
+
for permission in role.get("riskyRolePermissions"):
|
|
300
|
+
self.__print_risky_permission(role, permission.get("permission"))
|
|
301
|
+
|
|
302
|
+
if self.all_pods is not None and subject.get("kind") == "ServiceAccount":
|
|
303
|
+
self.__print_pods_using_service_account(subject.get("podUsingServiceAccount"))
|
|
304
|
+
|
|
305
|
+
def get_json(self):
|
|
306
|
+
return self.subject_risky_roles_mapping
|
|
307
|
+
|
|
308
|
+
def __prepare_json(self):
|
|
309
|
+
for subject in self.subject_risky_roles_mapping:
|
|
310
|
+
for role in subject.get("riskyRoles"):
|
|
311
|
+
role["riskyRolePermissions"] = []
|
|
312
|
+
risky_role_permissions = self.checker.results.get(role.get("name"))
|
|
313
|
+
|
|
314
|
+
if risky_role_permissions is None:
|
|
315
|
+
continue
|
|
316
|
+
|
|
317
|
+
for permission in risky_role_permissions:
|
|
318
|
+
role.get("riskyRolePermissions").append(
|
|
319
|
+
{
|
|
320
|
+
"bindingKind": role.get("bindingKind"),
|
|
321
|
+
"bindingName": role.get("bindingName"),
|
|
322
|
+
"kind": role.get("kind"),
|
|
323
|
+
"name": role.get("name"),
|
|
324
|
+
"permission": permission,
|
|
325
|
+
}
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
if self.all_pods is not None and subject.get("kind") == "ServiceAccount":
|
|
329
|
+
subject["podUsingServiceAccount"] = []
|
|
330
|
+
|
|
331
|
+
for pod in self.__get_pods_for_service_account(subject.get("name")):
|
|
332
|
+
pod_details = {"name": pod.get("metadata").get("name")}
|
|
333
|
+
|
|
334
|
+
for key, value in self.__get_pod_metadata(pod).items():
|
|
335
|
+
pod_details[key] = value
|
|
336
|
+
|
|
337
|
+
subject["podUsingServiceAccount"].append(pod_details)
|
|
338
|
+
|
|
339
|
+
@classmethod
|
|
340
|
+
def __print_risky_permission(cls, role, permission):
|
|
341
|
+
binding_kind = role.get("bindingKind")
|
|
342
|
+
binding_name = role.get("bindingName")
|
|
343
|
+
|
|
344
|
+
role_name = role.get("name")
|
|
345
|
+
role_kind = role.get("kind")
|
|
346
|
+
|
|
347
|
+
print(
|
|
348
|
+
f" {Fore.RED}{permission}{Fore.WHITE} \u2192 "
|
|
349
|
+
+ f"{Fore.WHITE}[{binding_kind}] {binding_name}{Fore.WHITE} \u2192 "
|
|
350
|
+
+ f"{Fore.GREEN}[{role_kind}] {role_name} {Fore.RED}"
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
@classmethod
|
|
354
|
+
def __print_pods_using_service_account(cls, pods):
|
|
355
|
+
if len(pods) == 0:
|
|
356
|
+
return
|
|
357
|
+
|
|
358
|
+
for pod in pods:
|
|
359
|
+
text = ""
|
|
360
|
+
|
|
361
|
+
for key, value in pod.items():
|
|
362
|
+
color = Fore.GREEN if key == "name" else Fore.WHITE
|
|
363
|
+
text = f" {Fore.WHITE}Used in: " if text == "" else text + " / "
|
|
364
|
+
text += f'{color}[{key if key != "name" else "pod"}] {value}'
|
|
365
|
+
|
|
366
|
+
print(text)
|
|
367
|
+
|
|
368
|
+
@classmethod
|
|
369
|
+
def __get_pods_for_service_account(cls, service_account_name):
|
|
370
|
+
pods_json_file = open_file(f"{args.pods}")
|
|
371
|
+
pods_to_check = pods_json_file.get("items", [])
|
|
372
|
+
return [x for x in pods_to_check if x.get("spec", {}).get("serviceAccountName", "") == service_account_name]
|
|
373
|
+
|
|
374
|
+
@classmethod
|
|
375
|
+
def __get_pod_metadata(cls, pod):
|
|
376
|
+
metadata = {"namespace": pod.get("metadata").get("namespace")}
|
|
377
|
+
|
|
378
|
+
owner_references = pod.get("metadata").get("ownerReferences")
|
|
379
|
+
|
|
380
|
+
if pod.get("metadata").get("labels").get("app", "") != "":
|
|
381
|
+
metadata["app"] = pod.get("metadata").get("labels").get("app", "")
|
|
382
|
+
|
|
383
|
+
if owner_references is not None and len(owner_references) > 0:
|
|
384
|
+
metadata[owner_references[0].get("kind")] = owner_references[0].get("name")
|
|
385
|
+
|
|
386
|
+
if pod.get("metadata").get("labels").get("heritage", "") == "Helm":
|
|
387
|
+
metadata["helmChart"] = pod.get("metadata").get("labels").get("chart")
|
|
388
|
+
|
|
389
|
+
return metadata
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
if __name__ == "__main__":
|
|
393
|
+
args = get_argument_parser()
|
|
394
|
+
if args.clusterRole:
|
|
395
|
+
print("\n[*] Started enumerating risky ClusterRoles:")
|
|
396
|
+
role_kind = "ClusterRole"
|
|
397
|
+
clusterRole_json_file = open_file(args.clusterRole)
|
|
398
|
+
extensiveClusterRolesChecker = ExtensiveRolesChecker(clusterRole_json_file, role_kind)
|
|
399
|
+
extensive_ClusterRoles = [result for result in extensiveClusterRolesChecker.results]
|
|
400
|
+
|
|
401
|
+
if args.role:
|
|
402
|
+
print(f"{Fore.WHITE}[*] Started enumerating risky Roles:")
|
|
403
|
+
role_kind = "Role"
|
|
404
|
+
Role_json_file = open_file(args.role)
|
|
405
|
+
extensiveRolesChecker = ExtensiveRolesChecker(Role_json_file, role_kind)
|
|
406
|
+
extensive_roles = [result for result in extensiveRolesChecker.results if result not in extensive_ClusterRoles]
|
|
407
|
+
extensive_roles = extensive_roles + extensive_ClusterRoles
|
|
408
|
+
|
|
409
|
+
if args.clusterrolebindings:
|
|
410
|
+
print(f"{Fore.WHITE}[*] Started enumerating risky ClusterRoleBinding:")
|
|
411
|
+
bind_kind = "ClusterRoleBinding"
|
|
412
|
+
clusterRoleBinding_json_file = open_file(args.clusterrolebindings)
|
|
413
|
+
extensive_clusterRoleBindings = roleBindingChecker(clusterRoleBinding_json_file, extensive_roles, bind_kind)
|
|
414
|
+
|
|
415
|
+
if args.rolebindings:
|
|
416
|
+
print(f"{Fore.WHITE}[*] Started enumerating risky RoleBindings:")
|
|
417
|
+
bind_kind = "RoleBinding"
|
|
418
|
+
RoleBinding_json_file = open_file(args.rolebindings)
|
|
419
|
+
extensive_RoleBindings = roleBindingChecker(RoleBinding_json_file, extensive_roles, bind_kind)
|
|
420
|
+
|
|
421
|
+
pods = open_file(args.pods) if args.pods else None
|
|
422
|
+
|
|
423
|
+
if (args.role and args.rolebindings) or args.clusterRole and args.clusterrolebindings:
|
|
424
|
+
print(f"{Fore.WHITE}[*] Started enumerating risky subjects:")
|
|
425
|
+
|
|
426
|
+
# Implemented out_path for Kubenumerate to control where the output goes, and not to dump it to cwd
|
|
427
|
+
out_path = ""
|
|
428
|
+
if args.outputjson is not None:
|
|
429
|
+
out_path = args.outputjson
|
|
430
|
+
|
|
431
|
+
if args.role and args.rolebindings:
|
|
432
|
+
subject_viewer = SubjectViewer(extensive_RoleBindings.subject_risky_roles_mapping, extensiveRolesChecker, pods)
|
|
433
|
+
subject_viewer.print_risky_roles_for_subjects()
|
|
434
|
+
|
|
435
|
+
if args.outputjson:
|
|
436
|
+
text_file = open(f"{out_path}role_audit.json", "w")
|
|
437
|
+
text_file.write(json.dumps(subject_viewer.get_json()))
|
|
438
|
+
text_file.close()
|
|
439
|
+
|
|
440
|
+
if args.clusterRole and args.clusterrolebindings:
|
|
441
|
+
subject_viewer_cluster_level = SubjectViewer(
|
|
442
|
+
extensive_clusterRoleBindings.subject_risky_roles_mapping, extensiveClusterRolesChecker, pods
|
|
443
|
+
)
|
|
444
|
+
subject_viewer_cluster_level.print_risky_roles_for_subjects()
|
|
445
|
+
|
|
446
|
+
if args.outputjson:
|
|
447
|
+
text_file = open(f"{out_path}cluster_role_audit.json", "w")
|
|
448
|
+
text_file.write(json.dumps(subject_viewer_cluster_level.get_json()))
|
|
449
|
+
text_file.close()
|
formatter.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
# try for now only with the first sheet
|
|
4
|
+
df = pd.read_excel("kubenumerate_results_v1_0.xlsx", "Capabilities - Added")
|
|
5
|
+
# https://stackoverflow.com/questions/26521266/using-pandas-to-pd-read-excel-for-multiple-worksheets-of-the-same-workbook
|
|
6
|
+
# xls = pd.ExcelFile("kubenumerate_results_v1_0.xlsx")
|
|
7
|
+
# xls.sheet_names # list of sheets
|
|
8
|
+
|
|
9
|
+
with pd.ExcelWriter("enhanced.xlsx", engine="xlsxwriter", mode="w") as writer:
|
|
10
|
+
# df.to_excel(writer, index=False, sheet_name="sheet one")
|
|
11
|
+
workbook = writer.book
|
|
12
|
+
worksheet = workbook.add_worksheet("Capabilities - Added")
|
|
13
|
+
worksheet.set_zoom(90)
|
|
14
|
+
|
|
15
|
+
worksheet.set_column(0, len(df.columns) - 1, 20)
|
|
16
|
+
header_format = workbook.add_format({
|
|
17
|
+
'font_name': 'Calibri', # Default right now, but including in case changes in the future
|
|
18
|
+
'bg_color': '#A93545',
|
|
19
|
+
'bold': True,
|
|
20
|
+
'font_color': 'white',
|
|
21
|
+
'align': 'left',
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
title = "Capabilities - Added"
|
|
25
|
+
# Merge cells
|
|
26
|
+
title_format = workbook.add_format({
|
|
27
|
+
'font_name': 'Calibri',
|
|
28
|
+
'bg_color': '#A93545',
|
|
29
|
+
'font_color': 'white',
|
|
30
|
+
'font_size': 20,
|
|
31
|
+
})
|
|
32
|
+
#
|
|
33
|
+
subtitle = "Capabilities (specifically, Linux capabilities), are used for permission management in Linux. Some capabilities are enabled by default."
|
|
34
|
+
# subtitle = "AppArmor is enabled by adding container.apparmor.security.beta.kubernetes.io/[container name] as a pod-level annotation and setting its value to either runtime/default or a profile (localhost/[profile name])."
|
|
35
|
+
# note down how many cells title and subheader require
|
|
36
|
+
worksheet.merge_range('A1:AC1', title, title_format)
|
|
37
|
+
worksheet.merge_range('A2:AC2', subtitle)
|
|
38
|
+
worksheet.set_row(2, 15) # row height to 15
|
|
39
|
+
df = df.rename(columns={
|
|
40
|
+
"ResourceNamespace": "Resource Namespace",
|
|
41
|
+
"ResourceKind": "Resource Kind",
|
|
42
|
+
"ResourceName": "Resource Name",
|
|
43
|
+
"Container": "Affected Container",
|
|
44
|
+
"Metadata": "Metadata",
|
|
45
|
+
"msg": "Recommendation",
|
|
46
|
+
})
|
|
47
|
+
for col_num, value in enumerate(df.columns.values):
|
|
48
|
+
worksheet.write(2, col_num, value, header_format)
|
|
49
|
+
worksheet.freeze_panes(3, 0)
|
|
50
|
+
bg_format1 = workbook.add_format({'bg_color': '#E2E2E2'})
|
|
51
|
+
bg_format2 = workbook.add_format({'bg_color': 'white'}) # white cell background color
|
|
52
|
+
|
|
53
|
+
skip_three = 3
|
|
54
|
+
for row in range(df.shape[0] + 3):
|
|
55
|
+
if skip_three > 0:
|
|
56
|
+
skip_three -= 1
|
|
57
|
+
continue
|
|
58
|
+
worksheet.set_row(row, cell_format=(bg_format1 if row % 2 == 0 else bg_format2))
|
|
59
|
+
|
|
60
|
+
df.to_excel(writer, index=False, sheet_name="Capabilities - Added", startrow=3, header=False)
|