Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NoTech Technology for directories with no tech #230

Merged
merged 10 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,6 @@ func testXrayAuditPip(t *testing.T, format, requirementsFile string) string {
args := []string{"audit", "--pip", "--licenses", "--format=" + format}
if requirementsFile != "" {
args = append(args, "--requirements-file="+requirementsFile)

}
return securityTests.PlatformCli.RunCliCmdWithOutput(t, args...)
}
Expand Down
8 changes: 6 additions & 2 deletions commands/audit/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,13 +352,17 @@ func detectScanTargets(cmdResults *results.SecurityCommandResults, params *Audit
// We don't need to scan for both and get duplicate results.
continue
}
// No technology was detected, add scan without descriptors. (so no sca scan will be preformed and set at target level)
if len(workingDirs) == 0 {
// Requested technology (from params) descriptors/indicators were not found, scan only requested directory for this technology.
// Requested technology (from params) descriptors/indicators were not found or recursive scan with NoTech value, add scan without descriptors.
cmdResults.NewScanResults(results.ScanTarget{Target: requestedDirectory, Technology: tech})
}
for workingDir, descriptors := range workingDirs {
// Add scan for each detected working directory.
cmdResults.NewScanResults(results.ScanTarget{Target: workingDir, Technology: tech}).SetDescriptors(descriptors...)
targetResults := cmdResults.NewScanResults(results.ScanTarget{Target: workingDir, Technology: tech})
if tech != techutils.NoTech {
targetResults.SetDescriptors(descriptors...)
}
}
}
}
Expand Down
74 changes: 47 additions & 27 deletions commands/audit/audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,23 @@ func TestDetectScansToPreform(t *testing.T) {
return param
},
expected: []*results.TargetResults{
{
// We requested specific technologies, Nuget is not in the list but we want to run JAS on it
ScanTarget: results.ScanTarget{
Target: filepath.Join(dir, "Nuget"),
},
JasResults: &results.JasScansResults{},
},
{
ScanTarget: results.ScanTarget{
Technology: techutils.Go,
Target: filepath.Join(dir, "dir", "go"),
},
JasResults: &results.JasScansResults{},
ScaResults: &results.ScaScanResults{
Descriptors: []string{filepath.Join(dir, "dir", "go", "go.mod")},
},
},
{
ScanTarget: results.ScanTarget{
Technology: techutils.Maven,
Expand All @@ -59,9 +76,9 @@ func TestDetectScansToPreform(t *testing.T) {
JasResults: &results.JasScansResults{},
ScaResults: &results.ScaScanResults{
Descriptors: []string{
filepath.Join(dir, "dir", "maven", "pom.xml"),
filepath.Join(dir, "dir", "maven", "maven-sub", "pom.xml"),
filepath.Join(dir, "dir", "maven", "maven-sub2", "pom.xml"),
filepath.Join(dir, "dir", "maven", "pom.xml"),
},
},
},
Expand All @@ -76,14 +93,11 @@ func TestDetectScansToPreform(t *testing.T) {
},
},
{
// We requested specific technologies, yarn is not in the list but we want to run JAS on it
ScanTarget: results.ScanTarget{
Technology: techutils.Go,
Target: filepath.Join(dir, "dir", "go"),
Target: filepath.Join(dir, "yarn"),
},
JasResults: &results.JasScansResults{},
ScaResults: &results.ScaScanResults{
Descriptors: []string{filepath.Join(dir, "dir", "go", "go.mod")},
},
},
},
},
Expand All @@ -96,6 +110,26 @@ func TestDetectScansToPreform(t *testing.T) {
return param
},
expected: []*results.TargetResults{
{
ScanTarget: results.ScanTarget{
Technology: techutils.Nuget,
Target: filepath.Join(dir, "Nuget"),
},
JasResults: &results.JasScansResults{},
ScaResults: &results.ScaScanResults{
Descriptors: []string{filepath.Join(dir, "Nuget", "Nuget-sub", "project.csproj"), filepath.Join(dir, "Nuget", "project.sln")},
},
},
{
ScanTarget: results.ScanTarget{
Technology: techutils.Go,
Target: filepath.Join(dir, "dir", "go"),
},
JasResults: &results.JasScansResults{},
ScaResults: &results.ScaScanResults{
Descriptors: []string{filepath.Join(dir, "dir", "go", "go.mod")},
},
},
{
ScanTarget: results.ScanTarget{
Technology: techutils.Maven,
Expand All @@ -104,9 +138,9 @@ func TestDetectScansToPreform(t *testing.T) {
JasResults: &results.JasScansResults{},
ScaResults: &results.ScaScanResults{
Descriptors: []string{
filepath.Join(dir, "dir", "maven", "pom.xml"),
filepath.Join(dir, "dir", "maven", "maven-sub", "pom.xml"),
filepath.Join(dir, "dir", "maven", "maven-sub2", "pom.xml"),
filepath.Join(dir, "dir", "maven", "pom.xml"),
},
},
},
Expand All @@ -120,16 +154,6 @@ func TestDetectScansToPreform(t *testing.T) {
Descriptors: []string{filepath.Join(dir, "dir", "npm", "package.json")},
},
},
{
ScanTarget: results.ScanTarget{
Technology: techutils.Go,
Target: filepath.Join(dir, "dir", "go"),
},
JasResults: &results.JasScansResults{},
ScaResults: &results.ScaScanResults{
Descriptors: []string{filepath.Join(dir, "dir", "go", "go.mod")},
},
},
{
ScanTarget: results.ScanTarget{
Technology: techutils.Yarn,
Expand Down Expand Up @@ -160,16 +184,6 @@ func TestDetectScansToPreform(t *testing.T) {
Descriptors: []string{filepath.Join(dir, "yarn", "Pipenv", "Pipfile")},
},
},
{
ScanTarget: results.ScanTarget{
Technology: techutils.Nuget,
Target: filepath.Join(dir, "Nuget"),
},
JasResults: &results.JasScansResults{},
ScaResults: &results.ScaScanResults{
Descriptors: []string{filepath.Join(dir, "Nuget", "project.sln"), filepath.Join(dir, "Nuget", "Nuget-sub", "project.csproj")},
},
},
},
},
}
Expand All @@ -179,6 +193,12 @@ func TestDetectScansToPreform(t *testing.T) {
results := results.NewCommandResults(utils.SourceCode).SetEntitledForJas(true).SetSecretValidation(true)
detectScanTargets(results, test.params())
if assert.Len(t, results.Targets, len(test.expected)) {
sort.Slice(results.Targets, func(i, j int) bool {
return results.Targets[i].ScanTarget.Target < results.Targets[j].ScanTarget.Target
})
sort.Slice(test.expected, func(i, j int) bool {
return test.expected[i].ScanTarget.Target < test.expected[j].ScanTarget.Target
})
for i := range results.Targets {
if results.Targets[i].ScaResults != nil {
sort.Strings(results.Targets[i].ScaResults.Descriptors)
Expand Down
2 changes: 1 addition & 1 deletion commands/audit/scarunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func hasAtLeastOneTech(cmdResults *results.SecurityCommandResults) bool {
return false
}
for _, scan := range cmdResults.Targets {
if scan.Technology != "" {
if scan.Technology != techutils.NoTech {
return true
}
}
Expand Down
6 changes: 4 additions & 2 deletions utils/results/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,11 @@ func (st ScanTarget) String() (str string) {
if st.Name != "" {
str = st.Name
}
if st.Technology != "" {
str += fmt.Sprintf(" [%s]", st.Technology)
tech := st.Technology.String()
if tech == techutils.NoTech.String() {
tech = "unknown"
}
str += fmt.Sprintf(" [%s]", tech)
return
}

Expand Down
160 changes: 157 additions & 3 deletions utils/techutils/techutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const (
Docker Technology = "docker"
Oci Technology = "oci"
Conan Technology = "conan"
NoTech Technology = ""
)
const Pypi = "pypi"

Expand Down Expand Up @@ -322,10 +323,11 @@ func detectedTechnologiesListInPath(path string, recursive bool) (technologies [
}

// If recursive is true, the search will not be limited to files in the root path.
// If recursive is true the search may return Technology.NoTech value
// If requestedTechs is empty, all technologies will be checked.
// If excludePathPattern is not empty, files/directories that match the wildcard pattern will be excluded from the search.
func DetectTechnologiesDescriptors(path string, recursive bool, requestedTechs []string, requestedDescriptors map[Technology][]string, excludePathPattern string) (technologiesDetected map[Technology]map[string][]string, err error) {
filesList, err := fspatterns.ListFiles(path, recursive, false, true, true, excludePathPattern)
filesList, dirsList, err := listFilesAndDirs(path, recursive, true, true, excludePathPattern)
if err != nil {
return
}
Expand All @@ -340,12 +342,161 @@ func DetectTechnologiesDescriptors(path string, recursive bool, requestedTechs [
log.Debug(fmt.Sprintf("mapped %d working directories with indicators/descriptors:\n%s", len(workingDirectoryToIndicators), strJson))
}
technologiesDetected, err = mapWorkingDirectoriesToTechnologies(workingDirectoryToIndicators, excludedTechAtWorkingDir, ToTechnologies(requestedTechs), requestedDescriptors)
if len(technologiesDetected) > 0 {
log.Debug(fmt.Sprintf("Detected %d technologies at %s: %s.", len(technologiesDetected), path, maps.Keys(technologiesDetected)))
if err != nil {
return
}
if recursive {
// If recursive search, we need to also make sure to include directories that do not have any technology indicators.
technologiesDetected = addNoTechIfNeeded(technologiesDetected, path, dirsList)
}
techCount := len(technologiesDetected)
if _, exist := technologiesDetected[NoTech]; exist {
techCount--
}
if techCount > 0 {
log.Debug(fmt.Sprintf("Detected %d technologies at %s: %s.", techCount, path, maps.Keys(technologiesDetected)))
}
return
}

func listFilesAndDirs(rootPath string, isRecursive, excludeWithRelativePath, preserveSymlink bool, excludePathPattern string) (files, dirs []string, err error) {
filesOrDirsInPath, err := fspatterns.ListFiles(rootPath, isRecursive, true, excludeWithRelativePath, preserveSymlink, excludePathPattern)
if err != nil {
return
}
for _, path := range filesOrDirsInPath {
if isDir, e := fileutils.IsDirExists(path, preserveSymlink); e != nil {
err = errors.Join(err, fmt.Errorf("failed to check if %s is a directory: %w", path, e))
continue
} else if isDir {
dirs = append(dirs, path)
} else {
files = append(files, path)
}
}
return
}

func addNoTechIfNeeded(technologiesDetected map[Technology]map[string][]string, rootPath string, dirsList []string) (_ map[Technology]map[string][]string) {
noTechMap := map[string][]string{}
for _, dir := range getDirNoTechList(technologiesDetected, rootPath, dirsList) {
// Convert the directories
noTechMap[dir] = []string{}
}
if len(technologiesDetected) == 0 || len(noTechMap) > 0 {
// no technologies detected at all (add NoTech without any directories) or some directories were added to NoTech
technologiesDetected[NoTech] = noTechMap
}
return technologiesDetected
}

func getDirNoTechList(technologiesDetected map[Technology]map[string][]string, dir string, dirsList []string) (noTechList []string) {
for _, techDirs := range technologiesDetected {
if _, exist := techDirs[dir]; exist {
// The directory is already mapped to a technology, no need to add the dir or its sub directories to NoTech
return
}
}
children := getDirChildren(dir, dirsList)
childNoTechCount := 0
for _, child := range children {
childNoTechList := getDirNoTechList(technologiesDetected, child, dirsList)
if len(childNoTechList) > 0 {
childNoTechCount++
}
noTechList = append(noTechList, childNoTechList...)
}
if childNoTechCount == len(children) {
// If all children exists in childNoTechList, add only the parent directory to NoTech
noTechList = []string{dir}
}

// for _, techDirs := range technologiesDetected {
// if _, exist := techDirs[dir]; exist {
// // The directory is already mapped to a technology, no need to add the dir or its sub directories to NoTech
// break
// }
// for _, child := range children {
// childNoTechList := getDirNoTechList(technologiesDetected, child, dirsList)
// }

// if len(children) == 0 {
// // No children directories, add the directory to NoTech
// childNoTechList = append(childNoTechList, dir)
// break
// }
// for _, child := range children {
// childNoTechList = append(childNoTechList, getDirNoTechList(technologiesDetected, child, dirsList)...)
// }
// // If all children exists in childNoTechList, add only the parent directory to NoTech
// if len(children) == len(childNoTechList) {
// childNoTechList = []string{dir}
// }
// }
return
}

func getDirChildren(dir string, dirsList []string) (children []string) {
for _, dirPath := range dirsList {
if filepath.Dir(dirPath) == dir {
children = append(children, dirPath)
}
}
return
}

// func addNoTechIfNeeded(technologiesDetected map[Technology]map[string][]string, path, excludePathPattern string) (finalMap map[Technology]map[string][]string, err error) {
// finalMap = technologiesDetected
// noTechMap := map[string][]string{}
// // TODO: not only direct, need to see if multiple levels of directories are missing technology indicators
// // if all directories in are found no need for anything else,
// // if one missing need to add it to NoTech
// // if not one detected add only parent directory no need for each directory
// directories, err := getDirectDirectories(path, excludePathPattern)
// if err != nil {
// return
// }
// for _, dir := range directories {
// // Check if the directory is already mapped to a technology
// isMapped := false
// for _, techDirs := range finalMap {
// if _, exist := techDirs[dir]; exist {
// isMapped = true
// break
// }
// }
// if !isMapped {
// // Add the directory to NoTech (no indicators/descriptors were found)
// noTechMap[dir] = []string{}
// }
// }
// if len(technologiesDetected) == 0 || len(noTechMap) > 0 {
// // no technologies detected at all (add NoTech without any directories) or some directories were added to NoTech
// finalMap[NoTech] = noTechMap
// }
// return
// }

// func getDirectDirectories(path, excludePathPattern string) (directories []string, err error) {
// // Get all files and directories in the path, not recursive
// filesOrDirsInPath, err := fspatterns.ListFiles(path, false, true, true, true, excludePathPattern)
// if err != nil {
// return
// }
// // Filter to directories only
// for _, potentialDir := range filesOrDirsInPath {
// isDir, e := fileutils.IsDirExists(potentialDir, true)
// if e != nil {
// err = errors.Join(err, fmt.Errorf("failed to check if %s is a directory: %w", potentialDir, e))
// continue
// }
// if isDir {
// directories = append(directories, potentialDir)
// }
// }
// return
// }

// Map files to relevant working directories according to the technologies' indicators/descriptors and requested descriptors.
// files: The file paths to map.
// requestedDescriptors: Special requested descriptors (for example in Pip requirement.txt can have different path) for each technology.
Expand Down Expand Up @@ -545,6 +696,9 @@ func hasCompletePathPrefix(root, wd string) bool {
func DetectedTechnologiesToSlice(detected map[Technology]map[string][]string) []string {
keys := make([]string, 0, len(detected))
for tech := range detected {
if tech == NoTech {
continue
}
keys = append(keys, string(tech))
}
return keys
Expand Down
Loading
Loading