mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-14 18:09:55 +00:00
346 lines
11 KiB
Go
346 lines
11 KiB
Go
package resourcehandler
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/kubescape/go-logger"
|
|
"github.com/kubescape/go-logger/helpers"
|
|
"github.com/kubescape/k8s-interface/k8sinterface"
|
|
"github.com/kubescape/k8s-interface/workloadinterface"
|
|
"github.com/kubescape/kubescape/v3/core/cautils"
|
|
"github.com/kubescape/opa-utils/reporthandling"
|
|
"k8s.io/apimachinery/pkg/version"
|
|
)
|
|
|
|
// FileResourceHandler handle resources from files and URLs
|
|
type FileResourceHandler struct{}
|
|
|
|
func NewFileResourceHandler() *FileResourceHandler {
|
|
k8sinterface.InitializeMapResourcesMock() // initialize the resource map
|
|
return &FileResourceHandler{}
|
|
}
|
|
|
|
func (fileHandler *FileResourceHandler) GetResources(ctx context.Context, sessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) (cautils.K8SResources, map[string]workloadinterface.IMetadata, cautils.ExternalResources, map[string]bool, error) {
|
|
allResources := map[string]workloadinterface.IMetadata{}
|
|
externalResources := cautils.ExternalResources{}
|
|
|
|
if len(scanInfo.InputPatterns) == 0 {
|
|
return nil, nil, nil, nil, fmt.Errorf("missing input")
|
|
}
|
|
|
|
logger.L().Start("Accessing local objects...")
|
|
|
|
// load resources from all input paths
|
|
mappedResources := map[string][]workloadinterface.IMetadata{}
|
|
for path := range scanInfo.InputPatterns {
|
|
var workloadIDToSource map[string]reporthandling.Source
|
|
var workloads []workloadinterface.IMetadata
|
|
var err error
|
|
|
|
if scanInfo.ChartPath != "" && scanInfo.FilePath != "" {
|
|
workloadIDToSource, workloads, _ = getWorkloadFromHelmChart(ctx, scanInfo.InputPatterns[path], scanInfo.ChartPath, scanInfo.FilePath)
|
|
} else {
|
|
workloadIDToSource, workloads, err = getResourcesFromPath(ctx, scanInfo.InputPatterns[path])
|
|
if err != nil {
|
|
return nil, allResources, nil, nil, err
|
|
}
|
|
}
|
|
if len(workloads) == 0 {
|
|
continue
|
|
}
|
|
|
|
for k, v := range workloadIDToSource {
|
|
sessionObj.ResourceSource[k] = v
|
|
sessionObj.TemplateMapping[k] = cautils.MappingNodes{}
|
|
}
|
|
|
|
// map all resources: map["/apiVersion/version/kind"][]<k8s workloads>
|
|
addWorkloadsToResourcesMap(mappedResources, workloads)
|
|
}
|
|
|
|
// locate input k8s object in the mapped resources - if not found or not a valid resource, return error
|
|
var err error
|
|
if sessionObj.SingleResourceScan, err = findScanObjectResource(mappedResources, scanInfo.ScanObject); err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
if sessionObj.SingleResourceScan != nil && k8sinterface.WorkloadHasParent(sessionObj.SingleResourceScan) {
|
|
return nil, nil, nil, nil, fmt.Errorf("resource %s has a parent and cannot be scanned", sessionObj.SingleResourceScan.GetID())
|
|
}
|
|
|
|
scanningScope := cautils.GetScanningScope(sessionObj.Metadata.ContextMetadata)
|
|
|
|
// build a resources map, based on the policies
|
|
// map resources based on framework required resources: map["/group/version/kind"][]<k8s workloads ids>
|
|
resourceToQuery, excludedRulesMap := getQueryableResourceMapFromPolicies(sessionObj.Policies, sessionObj.SingleResourceScan, scanningScope)
|
|
k8sResources := resourceToQuery.ToK8sResourceMap()
|
|
|
|
// save only relevant resources
|
|
for i := range mappedResources {
|
|
if _, ok := k8sResources[i]; ok {
|
|
var ids []string
|
|
for j := range mappedResources[i] {
|
|
ids = append(ids, mappedResources[i][j].GetID())
|
|
allResources[mappedResources[i][j].GetID()] = mappedResources[i][j]
|
|
}
|
|
k8sResources[i] = append(k8sResources[i], ids...)
|
|
}
|
|
}
|
|
|
|
logger.L().StopSuccess("Done accessing local objects")
|
|
// save input resource in resource maps
|
|
addSingleResourceToResourceMaps(k8sResources, allResources, sessionObj.SingleResourceScan)
|
|
|
|
return k8sResources, allResources, externalResources, excludedRulesMap, nil
|
|
}
|
|
|
|
func (fileHandler *FileResourceHandler) GetCloudProvider() string {
|
|
return ""
|
|
}
|
|
func getWorkloadFromHelmChart(ctx context.Context, path, helmPath, workloadPath string) (map[string]reporthandling.Source, []workloadinterface.IMetadata, error) {
|
|
clonedRepo := cautils.GetClonedPath(path)
|
|
|
|
if clonedRepo != "" {
|
|
// if the repo was cloned, add the workload path to the cloned repo
|
|
workloadPath = filepath.Join(clonedRepo, workloadPath)
|
|
} else {
|
|
// if the repo was not cloned
|
|
clonedRepo = path
|
|
}
|
|
|
|
// Get repo root
|
|
repoRoot, gitRepo := extractGitRepo(clonedRepo)
|
|
|
|
helmSourceToWorkloads, helmSourceToChart := cautils.LoadResourcesFromHelmCharts(ctx, helmPath)
|
|
|
|
wlSource, ok := helmSourceToWorkloads[workloadPath]
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("workload %s not found in chart %s", workloadPath, helmPath)
|
|
}
|
|
|
|
if len(wlSource) != 1 {
|
|
return nil, nil, fmt.Errorf("workload %s found multiple times in chart %s", workloadPath, helmPath)
|
|
}
|
|
|
|
helmChart, ok := helmSourceToChart[workloadPath]
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("helmChart not found for workload %s", workloadPath)
|
|
}
|
|
|
|
workloadSource := getWorkloadSourceHelmChart(repoRoot, helmPath, gitRepo, helmChart)
|
|
|
|
workloadIDToSource := make(map[string]reporthandling.Source, 1)
|
|
workloadIDToSource[wlSource[0].GetID()] = workloadSource
|
|
|
|
var workloads []workloadinterface.IMetadata
|
|
workloads = append(workloads, wlSource...)
|
|
|
|
return workloadIDToSource, workloads, nil
|
|
|
|
}
|
|
|
|
func getWorkloadSourceHelmChart(repoRoot string, source string, gitRepo *cautils.LocalGitRepository, helmChart cautils.Chart) reporthandling.Source {
|
|
relSource, err := filepath.Rel(repoRoot, source)
|
|
if err == nil {
|
|
source = relSource
|
|
}
|
|
|
|
var lastCommit reporthandling.LastCommit
|
|
if gitRepo != nil {
|
|
commitInfo, _ := gitRepo.GetFileLastCommit(source)
|
|
if commitInfo != nil {
|
|
lastCommit = reporthandling.LastCommit{
|
|
Hash: commitInfo.SHA,
|
|
Date: commitInfo.Author.Date,
|
|
CommitterName: commitInfo.Author.Name,
|
|
CommitterEmail: commitInfo.Author.Email,
|
|
Message: commitInfo.Message,
|
|
}
|
|
}
|
|
}
|
|
|
|
return reporthandling.Source{
|
|
Path: repoRoot,
|
|
HelmPath: helmChart.Path,
|
|
RelativePath: source,
|
|
FileType: reporthandling.SourceTypeHelmChart,
|
|
HelmChartName: helmChart.Name,
|
|
LastCommit: lastCommit,
|
|
}
|
|
}
|
|
|
|
func getResourcesFromPath(ctx context.Context, path string) (map[string]reporthandling.Source, []workloadinterface.IMetadata, error) {
|
|
workloadIDToSource := make(map[string]reporthandling.Source)
|
|
var workloads []workloadinterface.IMetadata
|
|
|
|
clonedRepo := cautils.GetClonedPath(path)
|
|
if clonedRepo != "" {
|
|
// if the repo was cloned, add the workload path to the cloned repo
|
|
path = clonedRepo
|
|
}
|
|
|
|
// Get repo root
|
|
repoRoot, gitRepo := extractGitRepo(path)
|
|
|
|
// when scanning a single file, we consider the repository root to be
|
|
// the directory of the scanned file
|
|
if cautils.IsYaml(repoRoot) {
|
|
repoRoot = filepath.Dir(repoRoot)
|
|
}
|
|
|
|
// load resource from local file system
|
|
sourceToWorkloads := cautils.LoadResourcesFromFiles(ctx, path, repoRoot)
|
|
|
|
// update workloads and workloadIDToSource
|
|
var warnIssued bool
|
|
for source, ws := range sourceToWorkloads {
|
|
workloads = append(workloads, ws...)
|
|
|
|
relSource, err := filepath.Rel(repoRoot, source)
|
|
|
|
if err == nil {
|
|
source = relSource
|
|
}
|
|
|
|
var filetype string
|
|
if cautils.IsYaml(source) {
|
|
filetype = reporthandling.SourceTypeYaml
|
|
} else if cautils.IsJson(source) {
|
|
filetype = reporthandling.SourceTypeJson
|
|
} else {
|
|
continue
|
|
}
|
|
|
|
var lastCommit reporthandling.LastCommit
|
|
if gitRepo != nil {
|
|
commitInfo, err := gitRepo.GetFileLastCommit(source)
|
|
if err != nil && !warnIssued {
|
|
logger.L().Debug("Git scan skipped", helpers.Error(err))
|
|
warnIssued = true // croak only once
|
|
}
|
|
|
|
if commitInfo != nil {
|
|
lastCommit = reporthandling.LastCommit{
|
|
Hash: commitInfo.SHA,
|
|
Date: commitInfo.Author.Date,
|
|
CommitterName: commitInfo.Author.Name,
|
|
CommitterEmail: commitInfo.Author.Email,
|
|
Message: commitInfo.Message,
|
|
}
|
|
}
|
|
}
|
|
|
|
var workloadSource reporthandling.Source
|
|
if clonedRepo != "" {
|
|
workloadSource = reporthandling.Source{
|
|
Path: "",
|
|
RelativePath: relSource,
|
|
FileType: filetype,
|
|
LastCommit: lastCommit,
|
|
}
|
|
} else {
|
|
workloadSource = reporthandling.Source{
|
|
Path: repoRoot,
|
|
RelativePath: relSource,
|
|
FileType: filetype,
|
|
LastCommit: lastCommit,
|
|
}
|
|
}
|
|
|
|
for i := range ws {
|
|
workloadIDToSource[ws[i].GetID()] = workloadSource
|
|
}
|
|
}
|
|
|
|
if len(workloads) == 0 {
|
|
logger.L().Debug("files found in local storage", helpers.Int("files", len(sourceToWorkloads)), helpers.Int("workloads", len(workloads)))
|
|
}
|
|
|
|
// load resources from helm charts
|
|
helmSourceToWorkloads, helmSourceToChart := cautils.LoadResourcesFromHelmCharts(ctx, path)
|
|
for source, ws := range helmSourceToWorkloads {
|
|
workloads = append(workloads, ws...)
|
|
helmChart := helmSourceToChart[source]
|
|
|
|
if clonedRepo != "" && gitRepo != nil {
|
|
url, err := gitRepo.GetRemoteUrl()
|
|
if err != nil {
|
|
logger.L().Warning("failed to get remote url", helpers.Error(err))
|
|
break
|
|
}
|
|
helmChart.Path = strings.TrimSuffix(url, ".git")
|
|
repoRoot = ""
|
|
source = strings.TrimPrefix(source, fmt.Sprintf("%s/", clonedRepo))
|
|
}
|
|
|
|
workloadSource := getWorkloadSourceHelmChart(repoRoot, source, gitRepo, helmChart)
|
|
|
|
for i := range ws {
|
|
workloadIDToSource[ws[i].GetID()] = workloadSource
|
|
}
|
|
}
|
|
|
|
if len(helmSourceToWorkloads) > 0 { // && len(helmSourceToNodes) > 0
|
|
logger.L().Debug("helm templates found in local storage", helpers.Int("helmTemplates", len(helmSourceToWorkloads)), helpers.Int("workloads", len(workloads)))
|
|
}
|
|
|
|
//patch, get value from env
|
|
// Load resources from Kustomize directory
|
|
kustomizeSourceToWorkloads, kustomizeDirectoryName := cautils.LoadResourcesFromKustomizeDirectory(ctx, path) //?
|
|
|
|
// update workloads and workloadIDToSource with workloads from Kustomize Directory
|
|
for source, ws := range kustomizeSourceToWorkloads {
|
|
workloads = append(workloads, ws...)
|
|
relSource, err := filepath.Rel(repoRoot, source)
|
|
|
|
if err == nil {
|
|
source = relSource
|
|
}
|
|
|
|
var lastCommit reporthandling.LastCommit
|
|
if gitRepo != nil {
|
|
commitInfo, _ := gitRepo.GetFileLastCommit(source)
|
|
if commitInfo != nil {
|
|
lastCommit = reporthandling.LastCommit{
|
|
Hash: commitInfo.SHA,
|
|
Date: commitInfo.Author.Date,
|
|
CommitterName: commitInfo.Author.Name,
|
|
CommitterEmail: commitInfo.Author.Email,
|
|
Message: commitInfo.Message,
|
|
}
|
|
}
|
|
}
|
|
|
|
workloadSource := reporthandling.Source{
|
|
RelativePath: source,
|
|
FileType: reporthandling.SourceTypeKustomizeDirectory,
|
|
KustomizeDirectoryName: kustomizeDirectoryName,
|
|
LastCommit: lastCommit,
|
|
}
|
|
|
|
for i := range ws {
|
|
workloadIDToSource[ws[i].GetID()] = workloadSource
|
|
}
|
|
}
|
|
|
|
return workloadIDToSource, workloads, nil
|
|
}
|
|
|
|
func extractGitRepo(path string) (string, *cautils.LocalGitRepository) {
|
|
repoRoot := ""
|
|
gitRepo, err := cautils.NewLocalGitRepository(path)
|
|
if err == nil && gitRepo != nil {
|
|
repoRoot, _ = gitRepo.GetRootDir()
|
|
} else {
|
|
repoRoot, _ = filepath.Abs(path)
|
|
}
|
|
return repoRoot, gitRepo
|
|
}
|
|
|
|
func (fileHandler *FileResourceHandler) GetClusterAPIServerInfo(_ context.Context) *version.Info {
|
|
return nil
|
|
}
|