Skip to content
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ jobs:
run: |
sudo chmod +x ./internal/commands/.scripts/up.sh
./internal/commands/.scripts/up.sh
- name: Check if total coverage is greater then 78.2
- name: Check if total coverage is greater then 77.7
shell: bash
run: |
CODE_COV=$(go tool cover -func cover.out | grep total | awk '{print substr($3, 1, length($3)-1)}')
EXPECTED_CODE_COV=78.2
EXPECTED_CODE_COV=77.7
var=$(awk 'BEGIN{ print "'$CODE_COV'"<"'$EXPECTED_CODE_COV'" }')
if [ "$var" -eq 1 ];then
echo "Your code coverage is too low. Coverage precentage is: $CODE_COV"
Expand Down Expand Up @@ -109,7 +109,7 @@ jobs:
shell: bash
run: |
CODE_COV=$(go tool cover -func cover.out | grep total | awk '{print substr($3, 1, length($3)-1)}')
EXPECTED_CODE_COV=77.5
EXPECTED_CODE_COV=77.4
var=$(awk 'BEGIN{ print "'$CODE_COV'"<"'$EXPECTED_CODE_COV'" }')
if [ "$var" -eq 1 ];then
echo "Your code coverage is too low. Coverage precentage is: $CODE_COV"
Expand Down
2 changes: 2 additions & 0 deletions internal/commands/asca/asca-engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ func RunScanASCACommand(jwtWrapper wrappers.JWTWrapper) func(cmd *cobra.Command,
return func(cmd *cobra.Command, args []string) error {
ASCALatestVersion, _ := cmd.Flags().GetBool(commonParams.ASCALatestVersion)
fileSourceFlag, _ := cmd.Flags().GetString(commonParams.SourcesFlag)
ignoredFilePathFlag, _ := cmd.Flags().GetString(commonParams.IgnoredFilePathFlag)
agent, _ := cmd.Flags().GetString(commonParams.AgentFlag)
var port = viper.GetInt(commonParams.ASCAPortKey)
ASCAWrapper := grpcs.NewASCAGrpcWrapper(port)
ASCAParams := services.AscaScanParams{
FilePath: fileSourceFlag,
ASCAUpdateVersion: ASCALatestVersion,
IsDefaultAgent: agent == commonParams.DefaultAgent,
IgnoredFilePath: ignoredFilePathFlag,
}
wrapperParams := services.AscaWrappersParam{
JwtWrapper: jwtWrapper,
Expand Down
12 changes: 9 additions & 3 deletions internal/commands/containers-realtime-engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,26 @@ import (
"github.com/spf13/cobra"
)

func RunScanContainersRealtimeCommand(realtimeScannerWrapper wrappers.RealtimeScannerWrapper,
func RunScanContainersRealtimeCommand(
realtimeScannerWrapper wrappers.RealtimeScannerWrapper,
jwtWrapper wrappers.JWTWrapper,
featureFlagWrapper wrappers.FeatureFlagsWrapper) func(cmd *cobra.Command, args []string) error {
featureFlagWrapper wrappers.FeatureFlagsWrapper,
) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, _ []string) error {
fileSourceFlag, _ := cmd.Flags().GetString(commonParams.SourcesFlag)
if fileSourceFlag == "" {
return errorconstants.NewRealtimeEngineError("file path is required").Error()
}

ignoredFilePathFlag, _ := cmd.Flags().GetString(commonParams.IgnoredFilePathFlag)

containersRealtimeService := containersrealtime.NewContainersRealtimeService(jwtWrapper, featureFlagWrapper, realtimeScannerWrapper)

images, err := containersRealtimeService.RunContainersRealtimeScan(fileSourceFlag)
images, err := containersRealtimeService.RunContainersRealtimeScan(fileSourceFlag, ignoredFilePathFlag)
if err != nil {
return err
}

err = printer.Print(cmd.OutOrStdout(), images, printer.FormatJSON)
if err != nil {
return errorconstants.NewRealtimeEngineError("failed to return images").Error()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
{
"ImageName": "nginx",
"ImageTag": "latest"
}
]
9 changes: 9 additions & 0 deletions internal/commands/data/ignoredAsca.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[

{
"FileName": "python-vul-file.py",
"Line": 34,
"RuleID": 4006
}

]
6 changes: 6 additions & 0 deletions internal/commands/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ func scanASCASubCommand(jwtWrapper wrappers.JWTWrapper, featureFlagsWrapper wrap
Example: heredoc.Doc(
`
$ cx scan asca --file-source <path to a single file> --asca-latest-version
$ cx scan asca --file-source <path to a single file> --ignored-file-path <path to ignored in file>
`,
),
Annotations: map[string]string{
Expand All @@ -463,6 +464,8 @@ func scanASCASubCommand(jwtWrapper wrappers.JWTWrapper, featureFlagsWrapper wrap
"",
"The file source should be the path to a single file",
)

scanASCACmd.PersistentFlags().String(commonParams.IgnoredFilePathFlag, "", "Path to ignored secrets file")
return scanASCACmd
}

Expand Down Expand Up @@ -562,6 +565,7 @@ func scanContainersRealtimeSubCommand(realtimeScannerWrapper wrappers.RealtimeSc
Example: heredoc.Doc(
`
$ cx scan containers-realtime -s <path to containers file>
$ cx scan containers-realtime -s <path to file> --ignored-file-path <path to ignored containers file>
`,
),
Annotations: map[string]string{
Expand All @@ -580,6 +584,8 @@ func scanContainersRealtimeSubCommand(realtimeScannerWrapper wrappers.RealtimeSc
"",
"The file source should be the path to a single containers file (Dockerfile, docker-compose.yml, or Helm template)",
)
scanContainersRealtimeCmd.Flags().String(commonParams.IgnoredFilePathFlag, "", "Path to ignored containers file")

return scanContainersRealtimeCmd
}

Expand Down
76 changes: 73 additions & 3 deletions internal/services/asca.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package services

import (
"encoding/json"
"fmt"
"net"
"os"
Expand Down Expand Up @@ -29,6 +30,7 @@ type AscaScanParams struct {
FilePath string
ASCAUpdateVersion bool
IsDefaultAgent bool
IgnoredFilePath string
}

type AscaWrappersParam struct {
Expand All @@ -52,7 +54,30 @@ func CreateASCAScanRequest(ascaParams AscaScanParams, wrapperParams AscaWrappers
return emptyResults, nil
}

return executeScan(wrapperParams.ASCAWrapper, ascaParams.FilePath)
ignoredResults := validateIgnoredFilePath(ascaParams.IgnoredFilePath)
if ignoredResults != nil {
return ignoredResults, nil
}

return executeScan(wrapperParams.ASCAWrapper, ascaParams.FilePath, ascaParams.IgnoredFilePath)
}

func validateIgnoredFilePath(filePath string) *grpcs.ScanResult {
if filePath == "" {
return nil
}

if exists, _ := osinstaller.FileExists(filePath); !exists {
fileNotFoundMsg := fmt.Sprintf("Ignore file %s not found", filePath)
logger.PrintIfVerbose(fileNotFoundMsg)
return &grpcs.ScanResult{
Error: &grpcs.Error{
Description: fileNotFoundMsg,
},
}
}

return nil
}

func validateFilePath(filePath string) *grpcs.ScanResult {
Expand All @@ -76,14 +101,59 @@ func validateFilePath(filePath string) *grpcs.ScanResult {
return nil
}

func executeScan(ascaWrapper grpcs.AscaWrapper, filePath string) (*grpcs.ScanResult, error) {
func executeScan(ascaWrapper grpcs.AscaWrapper, filePath, ignoredFilePath string) (*grpcs.ScanResult, error) {
sourceCode, err := readSourceCode(filePath)
if err != nil {
return nil, err
}

_, fileName := filepath.Split(filePath)
return ascaWrapper.Scan(fileName, sourceCode)
scanResult, err := ascaWrapper.Scan(fileName, sourceCode)
if err != nil {
return nil, err
}

if ignoredFilePath != "" {
ignoredFindings, err := loadIgnoredAscaFindings(ignoredFilePath)
if err == nil {
ignoreMap := buildAscaIgnoreMap(ignoredFindings)
scanResult.ScanDetails = filterIgnoredAscaFindings(scanResult.ScanDetails, ignoreMap)
}
}

return scanResult, nil
}
func loadIgnoredAscaFindings(path string) ([]grpcs.AscaIgnoreFinding, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var ignored []grpcs.AscaIgnoreFinding
err = json.Unmarshal(data, &ignored)
if err != nil {
return nil, err
}
return ignored, nil
}
func filterIgnoredAscaFindings(results []grpcs.ScanDetail, ignoreMap map[string]bool) []grpcs.ScanDetail {
filtered := make([]grpcs.ScanDetail, 0, len(results))
for i := range results {
r := &results[i]
key := fmt.Sprintf("%s_%d_%d", r.FileName, r.Line, r.RuleID)
if !ignoreMap[key] {
filtered = append(filtered, *r)
}
}
return filtered
}

func buildAscaIgnoreMap(ignored []grpcs.AscaIgnoreFinding) map[string]bool {
m := make(map[string]bool)
for _, f := range ignored {
key := fmt.Sprintf("%s_%d_%d", f.FileName, f.Line, f.RuleID)
m[key] = true
}
return m
}

func manageASCAInstallation(ascaParams AscaScanParams, ascaWrappers AscaWrappersParam) error {
Expand Down
28 changes: 28 additions & 0 deletions internal/services/asca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,31 @@ func TestCreateASCAScanRequest_EngineRunningAndDefaultAgentAndNoLicense_Success(
assert.Nil(t, wrapperParams.ASCAWrapper.HealthCheck())
_ = wrapperParams.ASCAWrapper.ShutDown()
}

func TestCreateASCAScanRequest_WithSingleIgnoredFinding_FiltersResult(t *testing.T) {
ASCAParams := AscaScanParams{
FilePath: "data/python-vul-file.py",
ASCAUpdateVersion: false,
IsDefaultAgent: true,
IgnoredFilePath: "data/ignoredAsca.json",
}
wrapperParams := AscaWrappersParam{
JwtWrapper: &mock.JWTMockWrapper{},
ASCAWrapper: mock.NewASCAMockWrapper(1234),
}

sr, err := CreateASCAScanRequest(ASCAParams, wrapperParams)
if err != nil {
t.Fatalf("Failed to create ASCA scan request: %v", err)
}
if sr == nil {
t.Fatalf("Scan result is nil")
}

for _, finding := range sr.ScanDetails {
assert.False(t,
finding.FileName == "python-vul-file.py" && finding.Line == 34 && finding.RuleID == 4006,
"Expected ignored finding to be filtered out, but it was present: %+v", finding,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ type ContainerImage struct {
Vulnerabilities []Vulnerability `json:"Vulnerabilities"`
}

type IgnoredContainersFinding struct {
ImageName string `json:"ImageName"`
ImageTag string `json:"ImageTag"`
}

// ContainerImageResults holds the results of a containers realtime scan.
type ContainerImageResults struct {
Images []ContainerImage `json:"Images"`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package containersrealtime

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -37,28 +39,56 @@ func NewContainersRealtimeService(
}
}

func loadIgnoredContainerFindings(path string) ([]IgnoredContainersFinding, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var ignored []IgnoredContainersFinding
err = json.Unmarshal(data, &ignored)
if err != nil {
return nil, err
}
return ignored, nil
}

func buildContainerIgnoreMap(ignored []IgnoredContainersFinding) map[string]bool {
m := make(map[string]bool)
for _, f := range ignored {
key := fmt.Sprintf("%s_%s", f.ImageName, f.ImageTag)
m[key] = true
}
return m
}

func filterIgnoredContainers(results []ContainerImage, ignoreMap map[string]bool) []ContainerImage {
filtered := make([]ContainerImage, 0, len(results))
for _, r := range results {
key := fmt.Sprintf("%s_%s", r.ImageName, r.ImageTag)
if !ignoreMap[key] {
filtered = append(filtered, r)
}
}
return filtered
}

// RunContainersRealtimeScan performs a containers real-time scan on the given file.
func (c *ContainersRealtimeService) RunContainersRealtimeScan(filePath string) (results *ContainerImageResults, err error) {
func (c *ContainersRealtimeService) RunContainersRealtimeScan(filePath, ignoredFilePath string) (*ContainerImageResults, error) {
if filePath == "" {
return nil, errorconstants.NewRealtimeEngineError("file path is required").Error()
}

if enabled, err := realtimeengine.IsFeatureFlagEnabled(c.FeatureFlagWrapper, wrappers.OssRealtimeEnabled); err != nil || !enabled {
logger.PrintfIfVerbose("Containers Realtime scan is not available (feature flag disabled or error: %v)", err)
return nil, errorconstants.NewRealtimeEngineError(errorconstants.RealtimeEngineNotAvailable).Error()
}

if err := realtimeengine.EnsureLicense(c.JwtWrapper); err != nil {
return nil, errorconstants.NewRealtimeEngineError("failed to ensure license").Error()
}

if err := realtimeengine.ValidateFilePath(filePath); err != nil {
return nil, errorconstants.NewRealtimeEngineError("invalid file path").Error()
}

images, err := parseContainersFile(filePath)
if err != nil {
logger.PrintfIfVerbose("Failed to parse containers file %s: %v", filePath, err)
return nil, errorconstants.NewRealtimeEngineError("failed to parse containers file").Error()
}

Expand All @@ -68,13 +98,21 @@ func (c *ContainersRealtimeService) RunContainersRealtimeScan(filePath string) (

images = splitLocationsToSeparateResults(images)

result, err := c.scanImages(images, filePath)
results, err := c.scanImages(images, filePath)
if err != nil {
logger.PrintfIfVerbose("Failed to scan images via realtime service: %v", err)
return nil, errorconstants.NewRealtimeEngineError("Realtime scanner engine failed").Error()
}

return result, nil
if ignoredFilePath != "" {
ignored, err := loadIgnoredContainerFindings(ignoredFilePath)
if err != nil {
return nil, errorconstants.NewRealtimeEngineError("failed to load ignored containers").Error()
}
ignoreMap := buildContainerIgnoreMap(ignored)
results.Images = filterIgnoredContainers(results.Images, ignoreMap)
}

return results, nil
}

func splitLocationsToSeparateResults(images []types.ImageModel) []types.ImageModel {
Expand Down
Loading
Loading