Skip to content

Commit a9257b9

Browse files
authored
Parse service and role bindings (#23)
* parse role bindings and services Signed-off-by: Amir Malka <amirm@armosec.io> * additional parsing of network policies Signed-off-by: Amir Malka <amirm@armosec.io> * fix Signed-off-by: Amir Malka <amirm@armosec.io> * fix Signed-off-by: Amir Malka <amirm@armosec.io> --------- Signed-off-by: Amir Malka <amirm@armosec.io>
1 parent 47c2591 commit a9257b9

24 files changed

+1161
-130
lines changed

armometadata/k8sutils.go

Lines changed: 142 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"github.com/cilium/cilium/pkg/labels"
1313
"github.com/olvrng/ujson"
1414
"github.com/spf13/viper"
15+
rbac "k8s.io/api/rbac/v1"
16+
"k8s.io/utils/ptr"
1517

1618
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1719
)
@@ -107,39 +109,58 @@ func LoadConfig(configPath string) (*ClusterConfig, error) {
107109
}
108110

109111
type Metadata struct {
110-
Annotations map[string]string
111-
Labels map[string]string
112-
OwnerReferences map[string]string
113-
CreationTimestamp string
114-
ResourceVersion string
115-
Kind string
116-
ApiVersion string
117-
PodSelectorMatchLabels map[string]string
118-
PodSpecLabels map[string]string
112+
Annotations map[string]string
113+
Labels map[string]string
114+
OwnerReferences map[string]string
115+
CreationTimestamp string
116+
ResourceVersion string
117+
Kind string
118+
ApiVersion string
119+
Namespace string
120+
121+
// workloads
122+
PodSpecLabels map[string]string
123+
// network policies
124+
NetworkPolicyPodSelectorMatchLabels map[string]string
125+
HasEgressRules *bool
126+
HasIngressRules *bool
127+
128+
// services
129+
ServicePodSelectorMatchLabels map[string]string
130+
// for role bindings
131+
Subjects []rbac.Subject
132+
RoleRef *rbac.RoleRef
119133
}
120134

121135
// ExtractMetadataFromBytes extracts metadata from the JSON bytes of a Kubernetes object
122136
func ExtractMetadataFromJsonBytes(input []byte) (Metadata, error) {
123137
// output values
124138
m := Metadata{
125-
Annotations: map[string]string{},
126-
Labels: map[string]string{},
127-
OwnerReferences: map[string]string{},
128-
PodSpecLabels: map[string]string{},
129-
PodSelectorMatchLabels: map[string]string{},
139+
Annotations: map[string]string{},
140+
Labels: map[string]string{},
141+
OwnerReferences: map[string]string{},
142+
PodSpecLabels: map[string]string{},
143+
NetworkPolicyPodSelectorMatchLabels: map[string]string{},
144+
ServicePodSelectorMatchLabels: map[string]string{},
130145
}
146+
147+
currentSubjectIndex := -1
148+
131149
// ujson parsing
132150
jsonPathElements := make([]string, 0)
133151
err := ujson.Walk(input, func(level int, key, value []byte) bool {
134152
if level > 0 {
135153
jsonPathElements = slices.Replace(jsonPathElements, level-1, len(jsonPathElements), unquote(key))
136154
}
137155
jsonPath := strings.Join(jsonPathElements, ".")
156+
138157
switch {
139158
case jsonPath == "kind":
140159
m.Kind = unquote(value)
141160
case jsonPath == "apiVersion":
142161
m.ApiVersion = unquote(value)
162+
case jsonPath == "metadata.namespace":
163+
m.Namespace = unquote(value)
143164
case jsonPath == "metadata.creationTimestamp":
144165
m.CreationTimestamp = unquote(value)
145166
case jsonPath == "metadata.resourceVersion":
@@ -154,20 +175,80 @@ func ExtractMetadataFromJsonBytes(input []byte) (Metadata, error) {
154175
m.PodSpecLabels[unquote(key)] = unquote(value)
155176
case strings.HasPrefix(jsonPath, "spec.jobTemplate.spec.template.metadata.labels."):
156177
m.PodSpecLabels[unquote(key)] = unquote(value)
157-
case m.ApiVersion == "cilium.io/v2" && strings.HasPrefix(jsonPath, "spec.endpointSelector.matchLabels."):
158-
addCiliumMatchLabels(m.PodSelectorMatchLabels, key, value)
159-
case m.ApiVersion == "networking.k8s.io/v1" && strings.HasPrefix(jsonPath, "spec.podSelector.matchLabels."):
160-
m.PodSelectorMatchLabels[unquote(key)] = unquote(value)
178+
case strings.HasPrefix(jsonPath, "subjects."):
179+
parseRoleBindingSubjects(&m, &currentSubjectIndex, key, value)
180+
case strings.HasPrefix(jsonPath, "roleRef."):
181+
parseRoleBindingRoleRef(&m, key, value)
182+
case m.Kind == "Service" && strings.HasPrefix(jsonPath, "spec.selector."):
183+
m.ServicePodSelectorMatchLabels[unquote(key)] = unquote(value)
184+
// cilium network policies
185+
case m.ApiVersion == "cilium.io/v2":
186+
if strings.HasPrefix(jsonPath, "spec.endpointSelector.matchLabels.") {
187+
addCiliumMatchLabels(m.NetworkPolicyPodSelectorMatchLabels, key, value)
188+
} else if jsonPath == "spec.egress." || jsonPath == "spec.egressDeny." {
189+
setHasEgress(&m)
190+
} else if jsonPath == "spec.ingress." || jsonPath == "spec.ingressDeny." {
191+
setHasIngress(&m)
192+
} else if jsonPath == "specs..ingress" || jsonPath == "specs..ingressDeny" {
193+
setHasIngress(&m)
194+
} else if jsonPath == "specs..egress" || jsonPath == "specs..egressDeny" {
195+
setHasEgress(&m)
196+
}
197+
// k8s network policies
198+
case m.ApiVersion == "networking.k8s.io/v1":
199+
if strings.HasPrefix(jsonPath, "spec.podSelector.matchLabels.") {
200+
m.NetworkPolicyPodSelectorMatchLabels[unquote(key)] = unquote(value)
201+
} else if strings.HasPrefix(jsonPath, "spec.policyTypes.") {
202+
val := unquote(value)
203+
if val == "Egress" {
204+
setHasEgress(&m)
205+
} else if val == "Ingress" {
206+
setHasIngress(&m)
207+
}
208+
} else if jsonPath == "spec.egress" {
209+
setHasEgress(&m)
210+
} else if jsonPath == "spec.ingress" {
211+
setHasIngress(&m)
212+
}
213+
// istio network policies
161214
case m.ApiVersion == "security.istio.io/v1" && strings.HasPrefix(jsonPath, "spec.selector.matchLabels."):
162-
m.PodSelectorMatchLabels[unquote(key)] = unquote(value)
163-
case m.ApiVersion == "projectcalico.org/v3" && jsonPath == "spec.selector":
164-
m.PodSelectorMatchLabels = ParseCalicoSelector(value)
215+
m.NetworkPolicyPodSelectorMatchLabels[unquote(key)] = unquote(value)
216+
// calico
217+
case m.ApiVersion == "projectcalico.org/v3":
218+
if jsonPath == "spec.selector" {
219+
m.NetworkPolicyPodSelectorMatchLabels = ParseCalicoSelector(value)
220+
} else if strings.HasPrefix(jsonPath, "spec.types.") {
221+
val := unquote(value)
222+
if val == "Egress" {
223+
setHasEgress(&m)
224+
}
225+
if val == "Ingress" {
226+
setHasIngress(&m)
227+
}
228+
} else if jsonPath == "spec.egress" {
229+
setHasEgress(&m)
230+
} else if jsonPath == "spec.ingress" {
231+
setHasIngress(&m)
232+
}
165233
}
166234
return true
167235
})
236+
168237
return m, err
169238
}
170239

240+
func setHasEgress(m *Metadata) {
241+
if m.HasEgressRules == nil {
242+
m.HasEgressRules = ptr.To(true)
243+
}
244+
}
245+
246+
func setHasIngress(m *Metadata) {
247+
if m.HasIngressRules == nil {
248+
m.HasIngressRules = ptr.To(true)
249+
}
250+
}
251+
171252
func ParseCalicoSelector(value []byte) map[string]string {
172253
selector := map[string]string{}
173254
for _, rule := range strings.Split(unquote(value), "&&") {
@@ -210,3 +291,43 @@ func unquote(value []byte) string {
210291
}
211292
return string(buf)
212293
}
294+
295+
func parseRoleBindingRoleRef(m *Metadata, key, value []byte) {
296+
if m.RoleRef == nil {
297+
m.RoleRef = &rbac.RoleRef{}
298+
}
299+
300+
k := unquote(key)
301+
switch k {
302+
case "apiGroup":
303+
m.RoleRef.APIGroup = unquote(value)
304+
case "kind":
305+
m.RoleRef.Kind = unquote(value)
306+
case "name":
307+
m.RoleRef.Name = unquote(value)
308+
}
309+
}
310+
311+
func parseRoleBindingSubjects(m *Metadata, currentSubjectIndex *int, key, value []byte) {
312+
v := unquote(value)
313+
if v == "{" {
314+
if m.Subjects == nil {
315+
m.Subjects = make([]rbac.Subject, 0)
316+
}
317+
*currentSubjectIndex += 1
318+
m.Subjects = append(m.Subjects, rbac.Subject{})
319+
return
320+
}
321+
322+
k := unquote(key)
323+
switch k {
324+
case "apiGroup":
325+
m.Subjects[*currentSubjectIndex].APIGroup = v
326+
case "kind":
327+
m.Subjects[*currentSubjectIndex].Kind = v
328+
case "name":
329+
m.Subjects[*currentSubjectIndex].Name = v
330+
case "namespace":
331+
m.Subjects[*currentSubjectIndex].Namespace = v
332+
}
333+
}

0 commit comments

Comments
 (0)