Skip to content

Commit a0d5fc2

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

File tree

4 files changed

+303
-0
lines changed

4 files changed

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

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)