go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/_motor/discovery/k8s/list_workloads.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package k8s 5 6 import ( 7 "fmt" 8 9 "github.com/gobwas/glob" 10 "github.com/pkg/errors" 11 "github.com/rs/zerolog/log" 12 "go.mondoo.com/cnquery/motor/asset" 13 "go.mondoo.com/cnquery/motor/providers" 14 "go.mondoo.com/cnquery/motor/providers/k8s" 15 v1 "k8s.io/api/core/v1" 16 k8sErrors "k8s.io/apimachinery/pkg/api/errors" 17 "k8s.io/apimachinery/pkg/api/meta" 18 "k8s.io/apimachinery/pkg/runtime" 19 ) 20 21 type NamespaceFilterOpts struct { 22 include []string 23 exclude []string 24 } 25 26 // ListCronJobs list all cronjobs in the cluster. 27 func ListCronJobs( 28 p k8s.KubernetesProvider, 29 connection *providers.Config, 30 clusterIdentifier string, 31 nsFilter NamespaceFilterOpts, 32 resFilter map[string][]K8sResourceIdentifier, 33 od *k8s.PlatformIdOwnershipDirectory, 34 ) ([]*asset.Asset, error) { 35 return ListNamespacedObj(p, connection, clusterIdentifier, nsFilter, resFilter, od, "cronjob", p.CronJob, p.CronJobs) 36 } 37 38 func ListDaemonSets( 39 p k8s.KubernetesProvider, 40 connection *providers.Config, 41 clusterIdentifier string, 42 nsFilter NamespaceFilterOpts, 43 resFilter map[string][]K8sResourceIdentifier, 44 od *k8s.PlatformIdOwnershipDirectory, 45 ) ([]*asset.Asset, error) { 46 return ListNamespacedObj(p, connection, clusterIdentifier, nsFilter, resFilter, od, "daemonset", p.DaemonSet, p.DaemonSets) 47 } 48 49 // ListDeployments lits all deployments in the cluster. 50 func ListDeployments( 51 p k8s.KubernetesProvider, 52 connection *providers.Config, 53 clusterIdentifier string, 54 nsFilter NamespaceFilterOpts, 55 resFilter map[string][]K8sResourceIdentifier, 56 od *k8s.PlatformIdOwnershipDirectory, 57 ) ([]*asset.Asset, error) { 58 return ListNamespacedObj(p, connection, clusterIdentifier, nsFilter, resFilter, od, "deployment", p.Deployment, p.Deployments) 59 } 60 61 // ListJobs list all jobs in the cluster. 62 func ListJobs( 63 p k8s.KubernetesProvider, 64 connection *providers.Config, 65 clusterIdentifier string, 66 nsFilter NamespaceFilterOpts, 67 resFilter map[string][]K8sResourceIdentifier, 68 od *k8s.PlatformIdOwnershipDirectory, 69 ) ([]*asset.Asset, error) { 70 return ListNamespacedObj(p, connection, clusterIdentifier, nsFilter, resFilter, od, "job", p.Job, p.Jobs) 71 } 72 73 // ListPods list all pods in the cluster. 74 func ListPods( 75 p k8s.KubernetesProvider, 76 connection *providers.Config, 77 clusterIdentifier string, 78 nsFilter NamespaceFilterOpts, 79 resFilter map[string][]K8sResourceIdentifier, 80 od *k8s.PlatformIdOwnershipDirectory, 81 ) ([]*asset.Asset, error) { 82 return ListNamespacedObj(p, connection, clusterIdentifier, nsFilter, resFilter, od, "pod", p.Pod, p.Pods) 83 } 84 85 // ListReplicaSets list all replicaSets in the cluster. 86 func ListReplicaSets( 87 p k8s.KubernetesProvider, 88 connection *providers.Config, 89 clusterIdentifier string, 90 nsFilter NamespaceFilterOpts, 91 resFilter map[string][]K8sResourceIdentifier, 92 od *k8s.PlatformIdOwnershipDirectory, 93 ) ([]*asset.Asset, error) { 94 return ListNamespacedObj(p, connection, clusterIdentifier, nsFilter, resFilter, od, "replicaset", p.ReplicaSet, p.ReplicaSets) 95 } 96 97 // ListStatefulSets list all statefulsets in the cluster. 98 func ListStatefulSets( 99 p k8s.KubernetesProvider, 100 connection *providers.Config, 101 clusterIdentifier string, 102 nsFilter NamespaceFilterOpts, 103 resFilter map[string][]K8sResourceIdentifier, 104 od *k8s.PlatformIdOwnershipDirectory, 105 ) ([]*asset.Asset, error) { 106 return ListNamespacedObj(p, connection, clusterIdentifier, nsFilter, resFilter, od, "statefulset", p.StatefulSet, p.StatefulSets) 107 } 108 109 func ListNamespacedObj[T runtime.Object]( 110 p k8s.KubernetesProvider, 111 connection *providers.Config, 112 clusterIdentifier string, 113 nsFilter NamespaceFilterOpts, 114 resFilter map[string][]K8sResourceIdentifier, 115 od *k8s.PlatformIdOwnershipDirectory, 116 workloadType string, 117 getter func(string, string) (T, error), 118 lister func(v1.Namespace) ([]T, error), 119 ) ([]*asset.Asset, error) { 120 workloads := []T{} 121 122 if len(resFilter) > 0 { 123 // If there is a resources filter we should only retrieve the workloads that are in the filter. 124 if len(resFilter[workloadType]) == 0 { 125 return []*asset.Asset{}, nil 126 } 127 128 for _, res := range resFilter[workloadType] { 129 ds, err := getter(res.Namespace, res.Name) 130 if err != nil { 131 return nil, errors.Wrapf(err, "failed to get %s %s/%s", workloadType, res.Namespace, res.Name) 132 } 133 134 workloads = append(workloads, ds) 135 } 136 } else { 137 namespaces, err := p.Namespaces() 138 if err != nil { 139 // If we don't have rights to list the cluster namespaces, attempt getting them 1 by 1 140 if k8sErrors.IsForbidden(err) && len(nsFilter.include) > 0 { 141 for _, ns := range nsFilter.include { 142 n, err := p.Namespace(ns) 143 if err != nil { 144 return nil, err 145 } 146 namespaces = append(namespaces, *n) 147 } 148 } else { 149 return nil, errors.Wrap(err, "could not list kubernetes namespaces") 150 } 151 } 152 153 for i := range namespaces { 154 namespace := namespaces[i] 155 skip, err := skipNamespace(namespace, nsFilter) 156 if err != nil { 157 log.Error().Err(err).Str("namespace", namespace.Name).Msg("error checking whether Namespace should be included or excluded") 158 return nil, err 159 } 160 if skip { 161 log.Debug().Str("namespace", namespace.Name).Msg("ignoring namespace") 162 continue 163 } 164 165 workloadsPerNamespace, err := lister(namespace) 166 if err != nil { 167 return nil, errors.Wrap(err, fmt.Sprintf("failed to list %ss", workloadType)) 168 } 169 workloads = append(workloads, workloadsPerNamespace...) 170 } 171 } 172 173 assetsIdx := map[string]*asset.Asset{} 174 for i := range workloads { 175 od.Add(workloads[i]) 176 177 asset, err := createAssetFromObject(workloads[i], p.Runtime(), connection, clusterIdentifier) 178 if err != nil { 179 return nil, errors.Wrap(err, fmt.Sprintf("failed to create asset from %s", workloadType)) 180 } 181 182 // An error can never happen because of the type constraint. 183 obj, _ := meta.Accessor(workloads[i]) 184 log.Debug().Str("name", obj.GetName()).Str("connection", asset.Connections[0].Host).Msgf("resolved %s", workloadType) 185 186 assetsIdx[asset.PlatformIds[0]] = asset 187 } 188 189 // Return a unique list of assets. Manifests can contain a namespaces that is an empty string. When we try to list k8s 190 // resources for the empty namespace, that actually means list all resources. Therefore we can have duplicate entries in the list. 191 // Here we just return only the unique assets to make sure the code works correctly with both manifests and k8s API. 192 assets := make([]*asset.Asset, 0, len(assetsIdx)) 193 for k := range assetsIdx { 194 assets = append(assets, assetsIdx[k]) 195 } 196 197 return assets, nil 198 } 199 200 func skipNamespace(namespace v1.Namespace, filter NamespaceFilterOpts) (bool, error) { 201 // anything explicitly specified in the list of includes means accept only from that list 202 if len(filter.include) > 0 { 203 for _, ns := range filter.include { 204 g, err := glob.Compile(ns) 205 if err != nil { 206 return false, err 207 } 208 if g.Match(namespace.Name) { 209 // stop looking, we found our match 210 return false, nil 211 } 212 } 213 214 // didn't find it, so it must be skipped 215 return true, nil 216 } 217 218 // if nothing explicitly meant to be included, then check whether 219 // it should be excluded 220 for _, ns := range filter.exclude { 221 g, err := glob.Compile(ns) 222 if err != nil { 223 return false, err 224 } 225 if g.Match(namespace.Name) { 226 return true, nil 227 } 228 } 229 230 return false, nil 231 }