go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/_motor/discovery/k8s/resolver_cluster.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package k8s
     5  
     6  import (
     7  	"context"
     8  	"strings"
     9  
    10  	"github.com/rs/zerolog/log"
    11  	"go.mondoo.com/cnquery"
    12  	"go.mondoo.com/cnquery/motor/asset"
    13  	"go.mondoo.com/cnquery/motor/discovery/common"
    14  	"go.mondoo.com/cnquery/motor/platform/detector"
    15  	"go.mondoo.com/cnquery/motor/providers"
    16  	"go.mondoo.com/cnquery/motor/providers/k8s"
    17  	"go.mondoo.com/cnquery/motor/providers/k8s/resources"
    18  	"go.mondoo.com/cnquery/motor/providers/local"
    19  	"go.mondoo.com/cnquery/motor/vault"
    20  	"go.mondoo.com/cnquery/resources/packs/os/kubectl"
    21  )
    22  
    23  var _ common.ContextInitializer = (*ClusterResolver)(nil)
    24  
    25  type ClusterResolver struct{}
    26  
    27  func (r *ClusterResolver) Name() string {
    28  	return "Kubernetes Cluster Resolver"
    29  }
    30  
    31  func (r *ClusterResolver) AvailableDiscoveryTargets() []string {
    32  	return []string{
    33  		common.DiscoveryAuto,
    34  		common.DiscoveryAll,
    35  		DiscoveryClusters,
    36  		DiscoveryPods,
    37  		DiscoveryJobs,
    38  		DiscoveryCronJobs,
    39  		DiscoveryStatefulSets,
    40  		DiscoveryDeployments,
    41  		DiscoveryReplicaSets,
    42  		DiscoveryDaemonSets,
    43  		DiscoveryContainerImages,
    44  		DiscoveryAdmissionReviews,
    45  		DiscoveryIngresses,
    46  		DiscoveryNamespaces,
    47  	}
    48  }
    49  
    50  func (r *ClusterResolver) Resolve(ctx context.Context, root *asset.Asset, tc *providers.Config, credsResolver vault.Resolver, sfn common.QuerySecretFn, userIdDetectors ...providers.PlatformIdDetector) ([]*asset.Asset, error) {
    51  	features := cnquery.GetFeatures(ctx)
    52  	resolved := []*asset.Asset{}
    53  	nsFilter := NamespaceFilterOpts{}
    54  	excludeNamespaces := tc.Options["namespaces-exclude"]
    55  	if len(excludeNamespaces) > 0 {
    56  		nsFilter.exclude = strings.Split(excludeNamespaces, ",")
    57  	}
    58  
    59  	var k8sctlConfig *kubectl.KubectlConfig
    60  	localProvider, err := local.New()
    61  	if err == nil {
    62  		k8sctlConfig, err = kubectl.LoadKubeConfig(localProvider)
    63  		if err != nil {
    64  			return nil, err
    65  		}
    66  	}
    67  
    68  	p, err := k8s.New(ctx, tc)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	resourcesFilter, err := resourceFilters(tc)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	if tc.IncludesDiscoveryTarget(common.DiscoveryAuto) {
    79  		log.Info().Msg("discovery option auto is used. This will detect the assets: cluster, cronjobs, daemonsets, deployments, ingresses, jobs, pods, replicasets, statefulsets")
    80  	}
    81  
    82  	clusterIdentifier, err := p.Identifier()
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	// detect platform info for the asset
    88  	detector := detector.New(p)
    89  	pf, err := detector.Platform()
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	// Only discover cluster and nodes if there are no resource filters.
    95  	var clusterAsset *asset.Asset
    96  	ownershipDir := k8s.NewEmptyPlatformIdOwnershipDirectory(clusterIdentifier)
    97  	if tc.IncludesOneOfDiscoveryTarget(common.DiscoveryAll, common.DiscoveryAuto, DiscoveryClusters) &&
    98  		len(resourcesFilter) == 0 {
    99  		var clusterName string
   100  		// the name is still a bit unreliable
   101  		// see https://github.com/kubernetes/kubernetes/issues/44954
   102  		if len(tc.Options["context"]) > 0 {
   103  			clusterName = tc.Options["context"]
   104  			log.Info().Str("cluster-name", clusterName).Msg("use cluster name from --context")
   105  		} else {
   106  			clusterName = ""
   107  
   108  			if tc.Options[k8s.OPTION_MANIFEST] != "" || tc.Options[k8s.OPTION_ADMISSION] != "" || tc.Options[k8s.OPTION_IMMEMORY_CONTENT] != "" {
   109  				clusterName, _ = p.Name()
   110  			} else {
   111  				// try to parse context from kubectl config
   112  				if clusterName == "" && k8sctlConfig != nil && len(k8sctlConfig.CurrentContext) > 0 {
   113  					clusterName = k8sctlConfig.CurrentClusterName()
   114  					log.Info().Str("cluster-name", clusterName).Msg("use cluster name from kube config")
   115  				}
   116  
   117  				// fallback to first node name if we could not gather the name from kubeconfig
   118  				if clusterName == "" {
   119  					name, err := p.Name()
   120  					if err == nil {
   121  						clusterName = name
   122  						log.Info().Str("cluster-name", clusterName).Msg("use cluster name from node name")
   123  					}
   124  				}
   125  
   126  				clusterName = "K8s Cluster " + clusterName
   127  			}
   128  		}
   129  
   130  		clusterAsset = &asset.Asset{
   131  			PlatformIds: []string{clusterIdentifier},
   132  			Name:        clusterName,
   133  			Platform:    pf,
   134  			Connections: []*providers.Config{tc}, // pass-in the current config
   135  			State:       asset.State_STATE_RUNNING,
   136  		}
   137  		resolved = append(resolved, clusterAsset)
   138  
   139  		if features.IsActive(cnquery.K8sNodeDiscovery) {
   140  			// nodes are only added as related assets because we have no policies to scan them
   141  			nodes, nodeRelationshipInfos, err := ListNodes(p, tc, clusterIdentifier)
   142  			if err == nil && len(nodes) > 0 {
   143  				ri := nodeRelationshipInfos[0]
   144  				if ri.cloudAccountAsset != nil {
   145  					clusterAsset.RelatedAssets = append(clusterAsset.RelatedAssets, ri.cloudAccountAsset)
   146  				}
   147  				clusterAsset.RelatedAssets = append(clusterAsset.RelatedAssets, nodes...)
   148  			}
   149  		}
   150  	}
   151  
   152  	additionalAssets, err := addSeparateAssets(tc, p, nsFilter, resourcesFilter, clusterIdentifier, ownershipDir, features)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	if clusterAsset != nil {
   158  		isRelatedFn := func(a *asset.Asset) bool {
   159  			return a.Platform.GetKind() == providers.Kind_KIND_K8S_OBJECT
   160  		}
   161  
   162  		for _, aa := range additionalAssets {
   163  			if isRelatedFn(aa) {
   164  				clusterAsset.RelatedAssets = append(clusterAsset.RelatedAssets, aa)
   165  			}
   166  		}
   167  	}
   168  	resolved = append(resolved, additionalAssets...)
   169  
   170  	return resolved, nil
   171  }
   172  
   173  func (r *ClusterResolver) InitCtx(ctx context.Context) context.Context {
   174  	return resources.SetDiscoveryCache(ctx, resources.NewDiscoveryCache())
   175  }