Skip to content

Commit 5e7d607

Browse files
Alexandre Massonclaude
andcommitted
fix(trivy): initialize resource_name for cluster-scoped k8s resources
In the kubernetes-report branch of the Trivy parser, the Resources loop never initializes resource_name before the conditional concatenations, unlike the twin Vulnerabilities and Misconfigurations loops which reset service_name = "" on every iteration. Two consequences when a resource has no Namespace (cluster-scoped resources such as ClusterRole or ClusterRoleBinding, as emitted by `trivy k8s --report all --format json`): - if the first resource in the report is cluster-scoped, the parser raises UnboundLocalError and /api/v2/import-scan/ returns HTTP 500; - if a cluster-scoped resource follows a namespaced one, it silently inherits the previous iteration's value and gets a corrupted service name. Fix by initializing resource_name = "" at the top of each iteration, matching the two loops above. Adds a scan sample with a cluster-scoped resource first (plus a namespaced one and a trailing cluster-scoped one to cover the carry-over case) and a unit test asserting the parsed service names. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 680895a commit 5e7d607

3 files changed

Lines changed: 153 additions & 0 deletions

File tree

dojo/tools/trivy/parser.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ def get_findings(self, scan_file, test):
218218
namespace = resource.get("Namespace")
219219
kind = resource.get("Kind")
220220
name = resource.get("Name")
221+
resource_name = ""
221222
if namespace:
222223
resource_name = f"{namespace} / "
223224
if kind:
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
{
2+
"ClusterName": "kind-voc-lab",
3+
"Resources": [
4+
{
5+
"Kind": "ClusterRole",
6+
"Name": "system:controller:bootstrap-signer",
7+
"Results": [
8+
{
9+
"Target": "ClusterRole/system:controller:bootstrap-signer",
10+
"Class": "config",
11+
"Type": "rbac",
12+
"MisconfSummary": {
13+
"Successes": 0,
14+
"Failures": 1,
15+
"Exceptions": 0
16+
},
17+
"Misconfigurations": [
18+
{
19+
"Type": "Kubernetes Security Check",
20+
"ID": "KSV113",
21+
"Title": "Manage namespace secrets",
22+
"Description": "Check whether role permits managing namespace secrets",
23+
"Message": "ClusterRole 'system:controller:bootstrap-signer' shouldn't have access to manage secrets in namespace 'kube-system'",
24+
"Namespace": "builtin.kubernetes.KSV113",
25+
"Query": "data.builtin.kubernetes.KSV113.deny",
26+
"Resolution": "Manage namespace secrets are not allowed. Remove resource 'secrets' from role",
27+
"Severity": "MEDIUM",
28+
"PrimaryURL": "https://avd.aquasec.com/misconfig/ksv113",
29+
"References": [
30+
"https://kubernetes.io/docs/concepts/security/rbac-good-practices/",
31+
"https://avd.aquasec.com/misconfig/ksv113"
32+
],
33+
"Status": "FAIL",
34+
"Layer": {},
35+
"CauseMetadata": {
36+
"Provider": "Kubernetes",
37+
"Service": "general",
38+
"StartLine": 1,
39+
"EndLine": 1
40+
}
41+
}
42+
]
43+
}
44+
]
45+
},
46+
{
47+
"Namespace": "default",
48+
"Kind": "Deployment",
49+
"Name": "redis-follower",
50+
"Results": [
51+
{
52+
"Target": "Deployment/redis-follower",
53+
"Class": "config",
54+
"Type": "kubernetes",
55+
"MisconfSummary": {
56+
"Successes": 23,
57+
"Failures": 1,
58+
"Exceptions": 0
59+
},
60+
"Misconfigurations": [
61+
{
62+
"Type": "Kubernetes Security Check",
63+
"ID": "KSV001",
64+
"Title": "Process can elevate its own privileges",
65+
"Description": "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.",
66+
"Message": "Container 'follower' of Deployment 'redis-follower' should set 'securityContext.allowPrivilegeEscalation' to false",
67+
"Namespace": "builtin.kubernetes.KSV001",
68+
"Query": "data.builtin.kubernetes.KSV001.deny",
69+
"Resolution": "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.",
70+
"Severity": "MEDIUM",
71+
"PrimaryURL": "https://avd.aquasec.com/misconfig/ksv001",
72+
"References": [
73+
"https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted",
74+
"https://avd.aquasec.com/misconfig/ksv001"
75+
],
76+
"Status": "FAIL",
77+
"Layer": {},
78+
"CauseMetadata": {
79+
"Provider": "Kubernetes",
80+
"Service": "general",
81+
"StartLine": 132,
82+
"EndLine": 143
83+
}
84+
}
85+
]
86+
}
87+
]
88+
},
89+
{
90+
"Kind": "ClusterRoleBinding",
91+
"Name": "cluster-admin",
92+
"Results": [
93+
{
94+
"Target": "ClusterRoleBinding/cluster-admin",
95+
"Class": "config",
96+
"Type": "rbac",
97+
"MisconfSummary": {
98+
"Successes": 0,
99+
"Failures": 1,
100+
"Exceptions": 0
101+
},
102+
"Misconfigurations": [
103+
{
104+
"Type": "Kubernetes Security Check",
105+
"ID": "KSV111",
106+
"Title": "User with admin access",
107+
"Description": "ClusterRoleBindings with cluster-admin role is being used",
108+
"Message": "ClusterRoleBinding 'cluster-admin' should not bind to role 'cluster-admin'",
109+
"Namespace": "builtin.kubernetes.KSV111",
110+
"Query": "data.builtin.kubernetes.KSV111.deny",
111+
"Resolution": "Create a role which gives less access to the user",
112+
"Severity": "LOW",
113+
"PrimaryURL": "https://avd.aquasec.com/misconfig/ksv111",
114+
"References": [
115+
"https://kubernetes.io/docs/concepts/security/rbac-good-practices/",
116+
"https://avd.aquasec.com/misconfig/ksv111"
117+
],
118+
"Status": "FAIL",
119+
"Layer": {},
120+
"CauseMetadata": {
121+
"Provider": "Kubernetes",
122+
"Service": "general",
123+
"StartLine": 1,
124+
"EndLine": 1
125+
}
126+
}
127+
]
128+
}
129+
]
130+
}
131+
]
132+
}

unittests/tools/test_trivy_parser.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,26 @@ def test_kubernetes(self):
180180
self.assertIsNone(finding.component_version)
181181
self.assertEqual("default / Deployment / redis-follower", finding.service)
182182

183+
def test_kubernetes_cluster_scoped_resources(self):
184+
"""Resources whose first entry is cluster-scoped (no Namespace) must parse without UnboundLocalError"""
185+
with sample_path("kubernetes_cluster_scoped_resources.json").open(encoding="utf-8") as test_file:
186+
parser = TrivyParser()
187+
findings = parser.get_findings(test_file, Test())
188+
self.assertEqual(len(findings), 3)
189+
finding = findings[0]
190+
self.assertEqual("KSV113 - Manage namespace secrets", finding.title)
191+
self.assertEqual("Medium", finding.severity)
192+
self.assertEqual("ClusterRole / system:controller:bootstrap-signer", finding.service)
193+
finding = findings[1]
194+
self.assertEqual("KSV001 - Process can elevate its own privileges", finding.title)
195+
self.assertEqual("default / Deployment / redis-follower", finding.service)
196+
# a cluster-scoped resource following a namespaced one must not
197+
# inherit the previous resource's namespace/kind/name
198+
finding = findings[2]
199+
self.assertEqual("KSV111 - User with admin access", finding.title)
200+
self.assertEqual("Low", finding.severity)
201+
self.assertEqual("ClusterRoleBinding / cluster-admin", finding.service)
202+
183203
def test_license_scheme(self):
184204
with sample_path("license_scheme.json").open(encoding="utf-8") as test_file:
185205
parser = TrivyParser()

0 commit comments

Comments
 (0)