mirror of
https://github.com/kubescape/kubescape.git
synced 2026-03-02 17:50:26 +00:00
259 lines
9.2 KiB
Go
259 lines
9.2 KiB
Go
package resourcehandler
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/armosec/kubescape/cautils"
|
|
"github.com/armosec/kubescape/cautils/logger"
|
|
"github.com/armosec/kubescape/cautils/logger/helpers"
|
|
"github.com/armosec/kubescape/hostsensorutils"
|
|
"github.com/armosec/opa-utils/objectsenvelopes"
|
|
"github.com/armosec/opa-utils/reporthandling"
|
|
|
|
"github.com/armosec/k8s-interface/cloudsupport"
|
|
"github.com/armosec/k8s-interface/k8sinterface"
|
|
"github.com/armosec/k8s-interface/workloadinterface"
|
|
|
|
"github.com/armosec/armoapi-go/armotypes"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
k8slabels "k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/version"
|
|
"k8s.io/client-go/dynamic"
|
|
)
|
|
|
|
type K8sResourceHandler struct {
|
|
k8s *k8sinterface.KubernetesApi
|
|
hostSensorHandler hostsensorutils.IHostSensor
|
|
fieldSelector IFieldSelector
|
|
rbacObjectsAPI *cautils.RBACObjects
|
|
registryAdaptors *RegistryAdaptors
|
|
}
|
|
|
|
func NewK8sResourceHandler(k8s *k8sinterface.KubernetesApi, fieldSelector IFieldSelector, hostSensorHandler hostsensorutils.IHostSensor, rbacObjects *cautils.RBACObjects, registryAdaptors *RegistryAdaptors) *K8sResourceHandler {
|
|
return &K8sResourceHandler{
|
|
k8s: k8s,
|
|
fieldSelector: fieldSelector,
|
|
hostSensorHandler: hostSensorHandler,
|
|
rbacObjectsAPI: rbacObjects,
|
|
registryAdaptors: registryAdaptors,
|
|
}
|
|
}
|
|
|
|
func (k8sHandler *K8sResourceHandler) GetResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, error) {
|
|
allResources := map[string]workloadinterface.IMetadata{}
|
|
|
|
// get k8s resources
|
|
logger.L().Info("Accessing Kubernetes objects")
|
|
|
|
cautils.StartSpinner()
|
|
|
|
// build resources map
|
|
// map resources based on framework required resources: map["/group/version/kind"][]<k8s workloads ids>
|
|
k8sResourcesMap := setResourceMap(frameworks)
|
|
|
|
// get namespace and labels from designator (ignore cluster labels)
|
|
_, namespace, labels := armotypes.DigestPortalDesignator(designator)
|
|
|
|
// pull k8s recourses
|
|
if err := k8sHandler.pullResources(k8sResourcesMap, allResources, namespace, labels); err != nil {
|
|
return k8sResourcesMap, allResources, err
|
|
}
|
|
|
|
if err := k8sHandler.registryAdaptors.collectImagesVulnerabilities(k8sResourcesMap, allResources); err != nil {
|
|
logger.L().Warning("failed to collect image vulnerabilities", helpers.Error(err))
|
|
}
|
|
|
|
if err := k8sHandler.collectHostResources(allResources, k8sResourcesMap); err != nil {
|
|
logger.L().Warning("failed to collect host sensor resources", helpers.Error(err))
|
|
}
|
|
|
|
if err := k8sHandler.collectRbacResources(allResources); err != nil {
|
|
logger.L().Warning("failed to collect rbac resources", helpers.Error(err))
|
|
}
|
|
if err := getCloudProviderDescription(allResources, k8sResourcesMap); err != nil {
|
|
logger.L().Warning("failed to collect cloud data", helpers.Error(err))
|
|
}
|
|
|
|
cautils.StopSpinner()
|
|
logger.L().Success("Accessed to Kubernetes objects")
|
|
|
|
return k8sResourcesMap, allResources, nil
|
|
}
|
|
|
|
func (k8sHandler *K8sResourceHandler) GetClusterAPIServerInfo() *version.Info {
|
|
clusterAPIServerInfo, err := k8sHandler.k8s.DiscoveryClient.ServerVersion()
|
|
if err != nil {
|
|
logger.L().Error("failed to discover API server information", helpers.Error(err))
|
|
return nil
|
|
}
|
|
return clusterAPIServerInfo
|
|
}
|
|
|
|
func (k8sHandler *K8sResourceHandler) pullResources(k8sResources *cautils.K8SResources, allResources map[string]workloadinterface.IMetadata, namespace string, labels map[string]string) error {
|
|
logger.L().Debug("Accessing Kubernetes objects")
|
|
|
|
var errs error
|
|
for groupResource := range *k8sResources {
|
|
apiGroup, apiVersion, resource := k8sinterface.StringToResourceGroup(groupResource)
|
|
gvr := schema.GroupVersionResource{Group: apiGroup, Version: apiVersion, Resource: resource}
|
|
result, err := k8sHandler.pullSingleResource(&gvr, namespace, labels)
|
|
if err != nil {
|
|
if !strings.Contains(err.Error(), "the server could not find the requested resource") {
|
|
// handle error
|
|
if errs == nil {
|
|
errs = err
|
|
} else {
|
|
errs = fmt.Errorf("%s; %s", errs, err.Error())
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
// store result as []map[string]interface{}
|
|
metaObjs := ConvertMapListToMeta(k8sinterface.ConvertUnstructuredSliceToMap(result))
|
|
for i := range metaObjs {
|
|
allResources[metaObjs[i].GetID()] = metaObjs[i]
|
|
}
|
|
(*k8sResources)[groupResource] = workloadinterface.ListMetaIDs(metaObjs)
|
|
}
|
|
return errs
|
|
}
|
|
|
|
func (k8sHandler *K8sResourceHandler) pullSingleResource(resource *schema.GroupVersionResource, namespace string, labels map[string]string) ([]unstructured.Unstructured, error) {
|
|
resourceList := []unstructured.Unstructured{}
|
|
// set labels
|
|
listOptions := metav1.ListOptions{}
|
|
fieldSelectors := k8sHandler.fieldSelector.GetNamespacesSelectors(resource)
|
|
for i := range fieldSelectors {
|
|
|
|
listOptions.FieldSelector = fieldSelectors[i]
|
|
|
|
if len(labels) > 0 {
|
|
set := k8slabels.Set(labels)
|
|
listOptions.LabelSelector = set.AsSelector().String()
|
|
}
|
|
|
|
// set dynamic object
|
|
var clientResource dynamic.ResourceInterface
|
|
if namespace != "" && k8sinterface.IsNamespaceScope(resource) {
|
|
clientResource = k8sHandler.k8s.DynamicClient.Resource(*resource).Namespace(namespace)
|
|
} else {
|
|
clientResource = k8sHandler.k8s.DynamicClient.Resource(*resource)
|
|
}
|
|
|
|
// list resources
|
|
result, err := clientResource.List(context.Background(), listOptions)
|
|
if err != nil || result == nil {
|
|
return nil, fmt.Errorf("failed to get resource: %v, namespace: %s, labelSelector: %v, reason: %v", resource, namespace, listOptions.LabelSelector, err)
|
|
}
|
|
|
|
resourceList = append(resourceList, result.Items...)
|
|
|
|
}
|
|
|
|
return resourceList, nil
|
|
|
|
}
|
|
func ConvertMapListToMeta(resourceMap []map[string]interface{}) []workloadinterface.IMetadata {
|
|
workloads := []workloadinterface.IMetadata{}
|
|
for i := range resourceMap {
|
|
if w := objectsenvelopes.NewObject(resourceMap[i]); w != nil {
|
|
workloads = append(workloads, w)
|
|
}
|
|
}
|
|
return workloads
|
|
}
|
|
|
|
// func (k8sHandler *K8sResourceHandler) collectHostResourcesAPI(allResources map[string]workloadinterface.IMetadata, resourcesMap *cautils.K8SResources) error {
|
|
|
|
// HostSensorAPI := map[string]string{
|
|
// "bla/v1": "",
|
|
// }
|
|
// for apiVersion := range allResources {
|
|
// if HostSensorAPI == apiVersion {
|
|
// k8sHandler.collectHostResources()
|
|
// }
|
|
// }
|
|
// return nil
|
|
// }
|
|
func (k8sHandler *K8sResourceHandler) collectHostResources(allResources map[string]workloadinterface.IMetadata, resourcesMap *cautils.K8SResources) error {
|
|
logger.L().Debug("Collecting host sensor resources")
|
|
|
|
hostResources, err := k8sHandler.hostSensorHandler.CollectResources()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for rscIdx := range hostResources {
|
|
group, version := getGroupNVersion(hostResources[rscIdx].GetApiVersion())
|
|
groupResource := k8sinterface.JoinResourceTriplets(group, version, hostResources[rscIdx].GetKind())
|
|
allResources[hostResources[rscIdx].GetID()] = &hostResources[rscIdx]
|
|
|
|
grpResourceList, ok := (*resourcesMap)[groupResource]
|
|
if !ok {
|
|
grpResourceList = make([]string, 0)
|
|
}
|
|
(*resourcesMap)[groupResource] = append(grpResourceList, hostResources[rscIdx].GetID())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (k8sHandler *K8sResourceHandler) collectRbacResources(allResources map[string]workloadinterface.IMetadata) error {
|
|
logger.L().Debug("Collecting rbac resources")
|
|
|
|
if k8sHandler.rbacObjectsAPI == nil {
|
|
return nil
|
|
}
|
|
allRbacResources, err := k8sHandler.rbacObjectsAPI.ListAllResources()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for k, v := range allRbacResources {
|
|
allResources[k] = v
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getCloudProviderDescription(allResources map[string]workloadinterface.IMetadata, k8sResourcesMap *cautils.K8SResources) error {
|
|
logger.L().Debug("Collecting cloud data")
|
|
|
|
cloudProvider := initCloudProvider()
|
|
cluster := cloudProvider.getKubeCluster()
|
|
clusterName := cloudProvider.getKubeClusterName()
|
|
provider := getCloudProvider()
|
|
region, err := cloudProvider.getRegion(cluster, provider)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
project, err := cloudProvider.getProject(cluster, provider)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if provider != "" {
|
|
logger.L().Debug("cloud", helpers.String("cluster", cluster), helpers.String("clusterName", clusterName), helpers.String("provider", provider), helpers.String("region", region), helpers.String("project", project))
|
|
|
|
wl, err := cloudsupport.GetDescriptiveInfoFromCloudProvider(clusterName, provider, region, project)
|
|
if err != nil {
|
|
// Return error with useful info on how to configure credentials for getting cloud provider info
|
|
switch provider {
|
|
case "gke":
|
|
return fmt.Errorf("could not get descriptive information about gke cluster: %s using sdk client. See https://developers.google.com/accounts/docs/application-default-credentials for more information", cluster)
|
|
case "eks":
|
|
return fmt.Errorf("could not get descriptive information about eks cluster: %s using sdk client. Check out how to configure credentials in https://docs.aws.amazon.com/sdk-for-go/api/", cluster)
|
|
case "aks":
|
|
return fmt.Errorf("could not get descriptive information about aks cluster: %s. %v", cluster, err.Error())
|
|
}
|
|
return err
|
|
}
|
|
allResources[wl.GetID()] = wl
|
|
(*k8sResourcesMap)[fmt.Sprintf("%s/%s", wl.GetApiVersion(), wl.GetKind())] = []string{wl.GetID()}
|
|
}
|
|
return nil
|
|
|
|
}
|