Skip to content

Commit 1892a53

Browse files
committed
⭐️ discover k8s applications
1 parent da56dae commit 1892a53

File tree

4 files changed

+300
-0
lines changed

4 files changed

+300
-0
lines changed
+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package resources
2+
3+
import (
4+
"slices"
5+
"sort"
6+
7+
"go.mondoo.com/cnquery/v11/llx"
8+
"go.mondoo.com/cnquery/v11/providers-sdk/v1/util/convert"
9+
"go.mondoo.com/cnquery/v11/types"
10+
)
11+
12+
const (
13+
// https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/
14+
K8sApplicationName = "app.kubernetes.io/name"
15+
K8sApplicationInstance = "app.kubernetes.io/instance"
16+
K8sApplicationVersion = "app.kubernetes.io/version"
17+
K8sApplicationComponent = "app.kubernetes.io/component"
18+
K8sApplicationPartOf = "app.kubernetes.io/part-of"
19+
K8sApplicationManagedBy = "app.kubernetes.io/managed-by"
20+
)
21+
22+
func (k *mqlK8s) apps() ([]interface{}, error) {
23+
apps := map[string]k8sapp{}
24+
25+
// fetch deployment resources
26+
deployments := k.GetDeployments()
27+
if deployments.Error != nil {
28+
return nil, deployments.Error
29+
}
30+
31+
for i := range deployments.Data {
32+
d := deployments.Data[i].(*mqlK8sDeployment)
33+
labels := d.GetLabels().Data
34+
extractApp(apps, labels)
35+
}
36+
37+
// fetch daemonset resources
38+
daemonsets := k.GetDaemonsets()
39+
if daemonsets.Error != nil {
40+
return nil, daemonsets.Error
41+
}
42+
43+
for i := range daemonsets.Data {
44+
d := daemonsets.Data[i].(*mqlK8sDaemonset)
45+
labels := d.GetLabels().Data
46+
extractApp(apps, labels)
47+
}
48+
49+
// return k8s app list
50+
appList := []interface{}{}
51+
for _, app := range apps {
52+
r, err := CreateResource(k.MqlRuntime, "k8s.app", map[string]*llx.RawData{
53+
"__id": llx.StringData("app"),
54+
"name": llx.StringData(app.name),
55+
"version": llx.StringData(app.version),
56+
"instance": llx.StringData(app.instance),
57+
"managedBy": llx.StringData(app.managedBy),
58+
"partOf": llx.StringData(app.partOf),
59+
"components": llx.ArrayData(convert.SliceAnyToInterface(app.components), types.String),
60+
})
61+
if err != nil {
62+
return nil, err
63+
}
64+
65+
appList = append(appList, r)
66+
}
67+
68+
return appList, nil
69+
}
70+
71+
type k8sapp struct {
72+
name string
73+
version string
74+
instance string
75+
components []string
76+
partOf string
77+
managedBy string
78+
}
79+
80+
func extractApp(apps map[string]k8sapp, labels map[string]interface{}) {
81+
name, nameOk := labels[K8sApplicationName]
82+
instance, instanceOK := labels[K8sApplicationInstance]
83+
version, versionOK := labels[K8sApplicationVersion]
84+
component, componentOK := labels[K8sApplicationComponent]
85+
partOf, partOfOK := labels[K8sApplicationPartOf]
86+
managedBy, managedByOK := labels[K8sApplicationManagedBy]
87+
88+
if !nameOk {
89+
// if the name is not set, we cannot create an app
90+
return
91+
}
92+
93+
app := k8sapp{
94+
name: name.(string),
95+
}
96+
if instanceOK {
97+
app.instance = instance.(string)
98+
}
99+
if versionOK {
100+
app.version = version.(string)
101+
}
102+
if componentOK {
103+
app.components = []string{component.(string)}
104+
}
105+
if partOfOK {
106+
app.partOf = partOf.(string)
107+
}
108+
if managedByOK {
109+
app.managedBy = managedBy.(string)
110+
}
111+
112+
key := app.name + app.instance
113+
if existing, ok := apps[key]; ok {
114+
// if the app already exists, we need to merge the components
115+
components := append(existing.components, app.components...)
116+
sort.Strings(components)
117+
components = slices.Compact(components)
118+
existing.components = components
119+
apps[app.name+app.instance] = existing
120+
} else {
121+
apps[app.name+app.instance] = app
122+
}
123+
}

providers/k8s/resources/k8s.lr

+19
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ k8s {
5858
customresources() []k8s.customresource
5959
// Kubernetes admission webhook configurations
6060
validatingWebhookConfigurations() []k8s.admission.validatingwebhookconfiguration
61+
// Kubernetes applications
62+
apps() []k8s.app
6163
}
6264

6365
// Kubernetes API resources
@@ -865,6 +867,7 @@ private k8s.userinfo @defaults("username") {
865867
uid string
866868
}
867869

870+
// Kubernetes Validating Webhook Configuration
868871
private k8s.admission.validatingwebhookconfiguration @defaults("name") {
869872
// Mondoo ID for the Kubernetes object
870873
id string
@@ -886,4 +889,20 @@ private k8s.admission.validatingwebhookconfiguration @defaults("name") {
886889
manifest() dict
887890
// Webhooks configuration
888891
webhooks() []dict
892+
}
893+
894+
// Kubernetes Application
895+
private k8s.app {
896+
// Application name
897+
name string
898+
// Application version
899+
version string
900+
// Application instance
901+
instance string
902+
// Managed By
903+
managedBy string
904+
// Name of the higher-level application
905+
partOf string
906+
// Components
907+
components []string
889908
}

providers/k8s/resources/k8s.lr.go

+143
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

providers/k8s/resources/k8s.lr.manifest.yaml

+15
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ resources:
55
k8s:
66
fields:
77
apiResources: {}
8+
apps:
9+
min_mondoo_version: 9.0.0
810
clusterrolebindings:
911
min_mondoo_version: 5.31.0
1012
clusterroles:
@@ -120,6 +122,19 @@ resources:
120122
platform:
121123
name:
122124
- kubernetes
125+
k8s.app:
126+
fields:
127+
components: {}
128+
instance: {}
129+
managedBy: {}
130+
name: {}
131+
partOf: {}
132+
version: {}
133+
is_private: true
134+
min_mondoo_version: 9.0.0
135+
platform:
136+
name:
137+
- kubernetes
123138
k8s.configmap:
124139
fields:
125140
annotations: {}

0 commit comments

Comments
 (0)