Skip to content

Commit 94a3ed3

Browse files
Checkmarx Automationcx-anurag-dalke
authored andcommitted
Add tar file detection and local resolution enforcement in scan commands
- Introduce `isTarFileReference` function to identify tar file references in container images. - Implement `enforceLocalResolutionForTarFiles` function to automatically enable local resolution when tar files are detected in the `--container-images` flag. - Enhance test coverage with new test cases for tar file detection and local resolution enforcement. - Ensure integration with the scan create command to validate behavior with tar files.
1 parent b6c4a20 commit 94a3ed3

2 files changed

Lines changed: 274 additions & 0 deletions

File tree

internal/commands/scan.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2255,6 +2255,108 @@ func definePathForZipFileOrDirectory(cmd *cobra.Command) (zipFile, sourceDir str
22552255
return zipFile, sourceDir, err
22562256
}
22572257

2258+
// enforceLocalResolutionForTarFiles checks if any container image is a tar file
2259+
// and enforces local resolution by setting the --containers-local-resolution flag.
2260+
// Container-security scan-type related function.
2261+
func enforceLocalResolutionForTarFiles(cmd *cobra.Command) error {
2262+
containerImagesFlag, _ := cmd.Flags().GetString(commonParams.ContainerImagesFlag)
2263+
2264+
// If no container images specified, nothing to check
2265+
if containerImagesFlag == "" {
2266+
return nil
2267+
}
2268+
2269+
// Check if --containers-local-resolution is already set
2270+
containerResolveLocally, _ := cmd.Flags().GetBool(commonParams.ContainerResolveLocallyFlag)
2271+
2272+
// If already set to true, we're good
2273+
if containerResolveLocally {
2274+
return nil
2275+
}
2276+
2277+
// Parse container images list
2278+
containerImagesList := strings.Split(strings.TrimSpace(containerImagesFlag), ",")
2279+
hasTarFile := false
2280+
2281+
for _, containerImageName := range containerImagesList {
2282+
// Normalize input: trim spaces and quotes
2283+
containerImageName = strings.TrimSpace(containerImageName)
2284+
containerImageName = strings.Trim(containerImageName, "'\"")
2285+
2286+
// Skip empty entries
2287+
if containerImageName == "" {
2288+
continue
2289+
}
2290+
2291+
// Check if this is a tar file by checking if it contains a tar file reference
2292+
if isTarFileReference(containerImageName) {
2293+
hasTarFile = true
2294+
break
2295+
}
2296+
}
2297+
2298+
// If at least one tar file is found, enforce local resolution
2299+
if hasTarFile {
2300+
logger.PrintIfVerbose("Detected tar file(s) in --container-images flag")
2301+
fmt.Println("Warning: Tar file(s) detected in --container-images. Automatically enabling --containers-local-resolution flag.")
2302+
2303+
// Set the flag to true
2304+
err := cmd.Flags().Set(commonParams.ContainerResolveLocallyFlag, "true")
2305+
if err != nil {
2306+
return errors.Wrapf(err, "Failed to set --containers-local-resolution flag")
2307+
}
2308+
}
2309+
2310+
return nil
2311+
}
2312+
2313+
// isTarFileReference checks if a container image reference points to a tar file.
2314+
// Container-security scan-type related function.
2315+
func isTarFileReference(imageRef string) bool {
2316+
// Known prefixes that might precede the actual file path
2317+
knownPrefixes := []string{
2318+
"docker-archive:",
2319+
"oci-archive:",
2320+
"file:",
2321+
"oci-dir:",
2322+
}
2323+
2324+
// First, trim quotes from the entire input
2325+
actualRef := strings.Trim(imageRef, "'\"")
2326+
2327+
// Strip known prefixes to get the actual reference
2328+
for _, prefix := range knownPrefixes {
2329+
if strings.HasPrefix(actualRef, prefix) {
2330+
actualRef = strings.TrimPrefix(actualRef, prefix)
2331+
actualRef = strings.Trim(actualRef, "'\"")
2332+
break
2333+
}
2334+
}
2335+
2336+
// Check if the reference ends with .tar (case-insensitive)
2337+
lowerRef := strings.ToLower(actualRef)
2338+
2339+
// If it ends with .tar, it's a tar file (no tag suffix allowed)
2340+
if strings.HasSuffix(lowerRef, ".tar") {
2341+
return true
2342+
}
2343+
2344+
// If it contains a colon but doesn't end with .tar, check if it's a file.tar:tag format (invalid)
2345+
// A tar file cannot have a tag suffix like file.tar:tag
2346+
if strings.Contains(actualRef, ":") {
2347+
parts := strings.Split(actualRef, ":")
2348+
if len(parts) >= 2 {
2349+
firstPart := strings.ToLower(parts[0])
2350+
// If the part before the colon is a tar file, this is invalid (tar files don't have tags)
2351+
if strings.HasSuffix(firstPart, ".tar") {
2352+
return false
2353+
}
2354+
}
2355+
}
2356+
2357+
return false
2358+
}
2359+
22582360
func runCreateScanCommand(
22592361
scansWrapper wrappers.ScansWrapper,
22602362
exportWrapper wrappers.ExportWrapper,
@@ -2281,6 +2383,11 @@ func runCreateScanCommand(
22812383
if err != nil {
22822384
return err
22832385
}
2386+
// Check if tar files are used in --container-images and enforce local resolution
2387+
err = enforceLocalResolutionForTarFiles(cmd)
2388+
if err != nil {
2389+
return err
2390+
}
22842391
ignorePolicy, _ := cmd.Flags().GetBool(commonParams.IgnorePolicyFlag)
22852392

22862393
// Check if the user has permission to override policy management if --ignore-policy is set

internal/commands/scan_test.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4050,3 +4050,170 @@ func Test_CreateScanWithExistingProjectAssign_to_Application_FF_DirectAssociatio
40504050
}
40514051
assert.Equal(t, strings.Contains(stdoutString, "Successfully updated the application"), true, "Expected output: %s", "Successfully updated the application")
40524052
}
4053+
4054+
// TestIsTarFileReference tests the tar file detection logic.
4055+
// Container-security scan-type related test function.
4056+
func TestIsTarFileReference(t *testing.T) {
4057+
testCases := []struct {
4058+
name string
4059+
imageRef string
4060+
expected bool
4061+
}{
4062+
// Tar files (various formats)
4063+
{"Simple tar", "alpine.tar", true},
4064+
{"Tar with path", "/path/to/image.tar", true},
4065+
{"Tar case insensitive", "image.TAR", true},
4066+
{"Tar with quotes", "'alpine.tar'", true},
4067+
{"Tar multiple dots", "alpine.3.18.0.tar", true},
4068+
4069+
// Prefixed tar files
4070+
{"docker-archive tar", "docker-archive:alpine.tar", true},
4071+
{"oci-archive tar", "oci-archive:image.tar", true},
4072+
{"file prefix tar", "file:myimage.tar", true},
4073+
{"oci-dir tar", "oci-dir:image.tar", true},
4074+
4075+
// Non-tar images
4076+
{"Image with tag", "nginx:latest", false},
4077+
{"Image with registry", "registry.io/namespace/image:v1.0", false},
4078+
{"Compressed tar.gz", "image.tar.gz", false},
4079+
4080+
// Prefixed non-tar images
4081+
{"docker-archive image", "docker-archive:nginx:latest", false},
4082+
{"docker daemon image", "docker:nginx:latest", false},
4083+
{"registry image", "registry:ubuntu:20.04", false},
4084+
{"oci-dir with directory:tag", "oci-dir:/path/to/dir:latest", false},
4085+
4086+
// Invalid: tar file cannot have tag
4087+
{"Invalid tar with tag", "oci-dir:image.tar:latest", false},
4088+
}
4089+
4090+
for _, tc := range testCases {
4091+
t.Run(tc.name, func(t *testing.T) {
4092+
if result := isTarFileReference(tc.imageRef); result != tc.expected {
4093+
t.Errorf("Expected %v for '%s', got %v", tc.expected, tc.imageRef, result)
4094+
}
4095+
})
4096+
}
4097+
}
4098+
4099+
// TestEnforceLocalResolutionForTarFiles tests the automatic enforcement of local resolution when tar files are detected.
4100+
// Container-security scan-type related test function.
4101+
func TestEnforceLocalResolutionForTarFiles(t *testing.T) {
4102+
testCases := []struct {
4103+
name string
4104+
containerImages string
4105+
initialLocalResolution bool
4106+
expectedLocalResolution bool
4107+
expectWarning bool
4108+
}{
4109+
// No action needed
4110+
{"Empty images", "", false, false, false},
4111+
{"Already enabled", "alpine.tar", true, true, false},
4112+
{"Only image:tag", "nginx:latest,alpine:3.18", false, false, false},
4113+
{"Non-tar prefixes", "docker:nginx:latest,registry:ubuntu:22.04", false, false, false},
4114+
{"Invalid tar:tag format", "oci-dir:file.tar:latest", false, false, false},
4115+
4116+
// Should enable local resolution
4117+
{"Single tar", "alpine.tar", false, true, true},
4118+
{"Mixed tar+image", "nginx:latest,alpine.tar", false, true, true},
4119+
{"Tar with spaces/quotes", " 'alpine.tar' ,nginx:latest", false, true, true},
4120+
{"Prefixed tar", "docker-archive:alpine.tar", false, true, true},
4121+
{"oci-dir tar", "oci-dir:image.tar", false, true, true},
4122+
{"Tar at end", "nginx:latest,ubuntu.tar", false, true, true},
4123+
}
4124+
4125+
for _, tc := range testCases {
4126+
tc := tc
4127+
t.Run(tc.name, func(t *testing.T) {
4128+
// Create a mock command
4129+
cmd := &cobra.Command{}
4130+
cmd.Flags().String(commonParams.ContainerImagesFlag, "", "")
4131+
cmd.Flags().Bool(commonParams.ContainerResolveLocallyFlag, false, "")
4132+
4133+
// Set the initial flag values
4134+
_ = cmd.Flags().Set(commonParams.ContainerImagesFlag, tc.containerImages)
4135+
_ = cmd.Flags().Set(commonParams.ContainerResolveLocallyFlag, fmt.Sprintf("%v", tc.initialLocalResolution))
4136+
4137+
// Capture output to check for warning message
4138+
oldStdout := os.Stdout
4139+
r, w, _ := os.Pipe()
4140+
os.Stdout = w
4141+
4142+
// Run the function
4143+
err := enforceLocalResolutionForTarFiles(cmd)
4144+
4145+
// Restore stdout
4146+
w.Close()
4147+
os.Stdout = oldStdout
4148+
var buf bytes.Buffer
4149+
_, _ = io.Copy(&buf, r)
4150+
output := buf.String()
4151+
4152+
// Validate results
4153+
if err != nil {
4154+
t.Errorf("Unexpected error: %v", err)
4155+
}
4156+
4157+
actualLocalResolution, _ := cmd.Flags().GetBool(commonParams.ContainerResolveLocallyFlag)
4158+
if actualLocalResolution != tc.expectedLocalResolution {
4159+
t.Errorf("Expected local resolution=%v, got=%v", tc.expectedLocalResolution, actualLocalResolution)
4160+
}
4161+
4162+
hasWarning := strings.Contains(output, "Warning:") && strings.Contains(output, "Tar file")
4163+
if tc.expectWarning && !hasWarning {
4164+
t.Errorf("Expected warning but got: %s", output)
4165+
} else if !tc.expectWarning && hasWarning {
4166+
t.Errorf("Unexpected warning: %s", output)
4167+
}
4168+
})
4169+
}
4170+
}
4171+
4172+
// TestEnforceLocalResolutionForTarFiles_Integration tests the integration with scan create command.
4173+
// Container-security scan-type related test function.
4174+
func TestEnforceLocalResolutionForTarFiles_Integration(t *testing.T) {
4175+
tempDir := t.TempDir()
4176+
tarFile := filepath.Join(tempDir, "test.tar")
4177+
if file, err := os.Create(tarFile); err != nil {
4178+
t.Fatalf("Failed to create test tar: %v", err)
4179+
} else {
4180+
file.Close()
4181+
}
4182+
4183+
testCases := []struct {
4184+
name string
4185+
images string
4186+
addFlag bool
4187+
expectWarn bool
4188+
}{
4189+
{"Tar without flag", tarFile, false, true},
4190+
{"Tar with flag", tarFile, true, false},
4191+
{"Image without flag", "nginx:latest", false, false},
4192+
{"Mixed without flag", tarFile + ",nginx:latest", false, true},
4193+
}
4194+
4195+
for _, tc := range testCases {
4196+
t.Run(tc.name, func(t *testing.T) {
4197+
args := []string{"scan", "create", "--project-name", "MOCK", "-s", ".",
4198+
"-b", "test-branch", "--scan-types", "containers", "--container-images", tc.images}
4199+
if tc.addFlag {
4200+
args = append(args, "--containers-local-resolution")
4201+
}
4202+
4203+
oldStdout := os.Stdout
4204+
r, w, _ := os.Pipe()
4205+
os.Stdout = w
4206+
execCmdNilAssertion(t, args...)
4207+
w.Close()
4208+
os.Stdout = oldStdout
4209+
4210+
var buf bytes.Buffer
4211+
_, _ = io.Copy(&buf, r)
4212+
hasWarning := strings.Contains(buf.String(), "Warning:") && strings.Contains(buf.String(), "Tar file")
4213+
4214+
if tc.expectWarn != hasWarning {
4215+
t.Errorf("Expected warning=%v, got=%v", tc.expectWarn, hasWarning)
4216+
}
4217+
})
4218+
}
4219+
}

0 commit comments

Comments
 (0)