Skip to content

Commit eb4c35d

Browse files
Support Multi Locations, Support Sha Instead Of Tag (AST-105014) (#1230)
* support multi locations, support Sha instead of tag * linter fix - 1 * add UT * add UT * linter fix - 2
1 parent df80dee commit eb4c35d

File tree

4 files changed

+149
-15
lines changed

4 files changed

+149
-15
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.24.4
44

55
require (
66
github.com/Checkmarx/containers-resolver v1.0.15
7-
github.com/Checkmarx/containers-types v1.0.6
7+
github.com/Checkmarx/containers-types v1.0.7
88
github.com/Checkmarx/gen-ai-prompts v0.0.0-20240807143411-708ceec12b63
99
github.com/Checkmarx/gen-ai-wrapper v1.0.2
1010
github.com/Checkmarx/manifest-parser v0.1.0
@@ -41,7 +41,7 @@ require (
4141
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
4242
github.com/BobuSumisu/aho-corasick v1.0.3 // indirect
4343
github.com/BurntSushi/toml v1.5.0 // indirect
44-
github.com/Checkmarx/containers-images-extractor v1.0.11
44+
github.com/Checkmarx/containers-images-extractor v1.0.14
4545
github.com/Checkmarx/containers-syft-packages-extractor v1.0.13 // indirect
4646
github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect
4747
github.com/DataDog/zstd v1.5.6 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,14 @@ github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
6363
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
6464
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
6565
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
66-
github.com/Checkmarx/containers-images-extractor v1.0.11 h1:vkXenD5d9oiTn5CjMXx4V+Lr2Ol0WvMLaw+tUgY+Ky4=
67-
github.com/Checkmarx/containers-images-extractor v1.0.11/go.mod h1:5R3RtBHmMu9bjuXZCKBacPZwxtitXzIdRqQTnO2BeII=
66+
github.com/Checkmarx/containers-images-extractor v1.0.14 h1:ehGaOupkSbowq7LhiOG+bSuif9cyRuW+LNYFNPF3JKY=
67+
github.com/Checkmarx/containers-images-extractor v1.0.14/go.mod h1:/oMzTVB9exQNec/xfnVOtu752hRd223SOQt54JvGWUA=
6868
github.com/Checkmarx/containers-resolver v1.0.15 h1:cm4d6vYWi6G9J9vnAw+dWcMsJwEFMo+anCHVaSp0nMQ=
6969
github.com/Checkmarx/containers-resolver v1.0.15/go.mod h1:9mdw8elUHj9NO9+ejjuuuCByfxvx9mG+JTJxDLi9ubM=
7070
github.com/Checkmarx/containers-syft-packages-extractor v1.0.13 h1:9ah0rruMGgRiug/bD/JJDSrDqEqS7sKGVdc5sqbkwk8=
7171
github.com/Checkmarx/containers-syft-packages-extractor v1.0.13/go.mod h1:EFeB4//lO4KMVj9+eMg6z5jnO9F1e1T4jUoIcx0/19M=
72-
github.com/Checkmarx/containers-types v1.0.6 h1:wshT95XKnFhn1zfZabg89+SoxwyfjHnkUSOm/OnWtGY=
73-
github.com/Checkmarx/containers-types v1.0.6/go.mod h1:KR0w8XCosq3+6jRCfQrH7i//Nj2u11qaUJM62CREFZA=
72+
github.com/Checkmarx/containers-types v1.0.7 h1:SZUB8S//yFc1WlgLbw33conN5eR9CLv+DTewxMGVp7M=
73+
github.com/Checkmarx/containers-types v1.0.7/go.mod h1:KR0w8XCosq3+6jRCfQrH7i//Nj2u11qaUJM62CREFZA=
7474
github.com/Checkmarx/gen-ai-prompts v0.0.0-20240807143411-708ceec12b63 h1:SCuTcE+CFvgjbIxUNL8rsdB2sAhfuNx85HvxImKta3g=
7575
github.com/Checkmarx/gen-ai-prompts v0.0.0-20240807143411-708ceec12b63/go.mod h1:MI6lfLerXU+5eTV/EPTDavgnV3owz3GPT4g/msZBWPo=
7676
github.com/Checkmarx/gen-ai-wrapper v1.0.2 h1:T6X40+4hYnwfDsvkjWs9VIcE6s1O+8DUu0+sDdCY3GI=

internal/services/realtimeengine/containersrealtime/containers-realtime.go

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ func (c *ContainersRealtimeService) RunContainersRealtimeScan(filePath string) (
6464
return &ContainerImageResults{Images: []ContainerImage{}}, nil
6565
}
6666

67+
images = splitLocationsToSeparateResults(images)
68+
6769
result, err := c.scanImages(images, filePath)
6870
if err != nil {
6971
logger.PrintfIfVerbose("Failed to scan images via realtime service: %v", err)
@@ -73,6 +75,22 @@ func (c *ContainersRealtimeService) RunContainersRealtimeScan(filePath string) (
7375
return result, nil
7476
}
7577

78+
func splitLocationsToSeparateResults(images []types.ImageModel) []types.ImageModel {
79+
for i := 0; i < len(images); {
80+
if len(images[i].ImageLocations) > 1 {
81+
for _, loc := range images[i].ImageLocations {
82+
newImage := images[i]
83+
newImage.ImageLocations = []types.ImageLocation{loc}
84+
images = append(images, newImage)
85+
}
86+
images = append(images[:i], images[i+1:]...)
87+
} else {
88+
i++
89+
}
90+
}
91+
return images
92+
}
93+
7694
// parseContainersFile parses the containers file and returns a list of images.
7795
func parseContainersFile(filePath string) ([]types.ImageModel, error) {
7896
extractor := imagesExtractor.NewImagesExtractor()
@@ -106,7 +124,13 @@ func (c *ContainersRealtimeService) scanImages(images []types.ImageModel, filePa
106124
logger.PrintfIfVerbose("Scanning %d images for vulnerabilities", len(images))
107125

108126
var requestImages []wrappers.ContainerImageRequestItem
127+
var imagesWithSha []wrappers.ContainerImageResponseItem
109128
for _, img := range images {
129+
if img.IsSha {
130+
logger.PrintfIfVerbose("Skipping image with SHA: %s", img.Name)
131+
addShaImage(&imagesWithSha, img)
132+
continue
133+
}
110134
imageName, imageTag := splitToImageAndTag(img.Name)
111135

112136
logger.PrintfIfVerbose("Processing image: %s:%s", imageName, imageTag)
@@ -128,19 +152,54 @@ func (c *ContainersRealtimeService) scanImages(images []types.ImageModel, filePa
128152

129153
logger.PrintfIfVerbose("Received scan results for %d images", len(response.Images))
130154

131-
result := c.buildContainerImageResults(response.Images, images, filePath)
155+
result := c.buildContainerImageResults(response.Images, imagesWithSha, images, filePath)
132156
return &result, nil
133157
}
134158

159+
func addShaImage(images *[]wrappers.ContainerImageResponseItem, img types.ImageModel) {
160+
imageName, imageTag := splitToImageAndSha(img.Name)
161+
162+
*images = append(*images, wrappers.ContainerImageResponseItem{
163+
ImageName: imageName,
164+
ImageTag: imageTag,
165+
Status: "Unknown",
166+
Vulnerabilities: []wrappers.ContainerImageVulnerability{},
167+
})
168+
}
169+
170+
func splitToImageAndSha(image string) (imageName, imageTag string) {
171+
atIndex := strings.Index(image, "@")
172+
if atIndex == -1 {
173+
return splitToImageAndTag(image)
174+
}
175+
176+
nameAndTag := image[:atIndex]
177+
shaPart := image[atIndex+1:]
178+
179+
colonIndex := strings.LastIndex(nameAndTag, ":")
180+
if colonIndex != -1 {
181+
imageName = nameAndTag[:colonIndex]
182+
tag := nameAndTag[colonIndex+1:]
183+
imageTag = tag + "@" + shaPart
184+
} else {
185+
imageName = nameAndTag
186+
imageTag = shaPart
187+
}
188+
return
189+
}
190+
135191
// buildContainerImageResults builds ContainerImageResults from response and images
136-
func (c *ContainersRealtimeService) buildContainerImageResults(responseImages []wrappers.ContainerImageResponseItem, images []types.ImageModel, filePath string) ContainerImageResults {
192+
func (c *ContainersRealtimeService) buildContainerImageResults(responseImages, imagesWithSha []wrappers.ContainerImageResponseItem, images []types.ImageModel, filePath string) ContainerImageResults {
137193
var result ContainerImageResults
138-
for i, respImg := range responseImages {
139-
var locations []realtimeengine.Location
140-
if i < len(images) {
141-
locations = convertLocations(images[i].ImageLocations)
142-
}
143194

195+
result = mergeImagesToResults(responseImages, result, &images, filePath)
196+
result = mergeImagesToResults(imagesWithSha, result, &images, filePath)
197+
return result
198+
}
199+
200+
func mergeImagesToResults(listOfImages []wrappers.ContainerImageResponseItem, result ContainerImageResults, images *[]types.ImageModel, filePath string) ContainerImageResults {
201+
for _, respImg := range listOfImages {
202+
locations := getImageLocations(images, respImg.ImageName, respImg.ImageTag)
144203
containerImage := ContainerImage{
145204
ImageName: respImg.ImageName,
146205
ImageTag: respImg.ImageTag,
@@ -154,6 +213,17 @@ func (c *ContainersRealtimeService) buildContainerImageResults(responseImages []
154213
return result
155214
}
156215

216+
func getImageLocations(images *[]types.ImageModel, imageName, imageTag string) []realtimeengine.Location {
217+
for i, img := range *images {
218+
if img.Name == imageName+":"+imageTag || img.Name == imageName+"@"+imageTag {
219+
location := convertLocations(&img.ImageLocations)
220+
*images = append((*images)[:i], (*images)[i+1:]...)
221+
return location
222+
}
223+
}
224+
return []realtimeengine.Location{}
225+
}
226+
157227
// splitToImageAndTag splits the image string into name and tag components.
158228
func splitToImageAndTag(image string) (imageName, imageTag string) {
159229
// Split the image string by the last colon to separate name and tag
@@ -170,9 +240,9 @@ func splitToImageAndTag(image string) (imageName, imageTag string) {
170240
}
171241

172242
// convertLocations converts types locations to realtimeengine locations.
173-
func convertLocations(locations []types.ImageLocation) []realtimeengine.Location {
243+
func convertLocations(locations *[]types.ImageLocation) []realtimeengine.Location {
174244
var result []realtimeengine.Location
175-
for _, loc := range locations {
245+
for _, loc := range *locations {
176246
line := loc.Line
177247
startIndex := loc.StartIndex
178248
endIndex := loc.EndIndex

internal/services/realtimeengine/containersrealtime/containers-realtime_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import (
44
"errors"
55
"testing"
66

7+
"github.com/Checkmarx/containers-types/types"
78
"github.com/checkmarx/ast-cli/internal/wrappers"
89
"github.com/checkmarx/ast-cli/internal/wrappers/mock"
910
"github.com/stretchr/testify/assert"
1011
)
1112

13+
const testImageName = "nginx"
14+
1215
func TestRunContainersRealtimeScan_ValidLicenseAndFile_Success(t *testing.T) {
1316
mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.OssRealtimeEnabled, Status: true}
1417
service := NewContainersRealtimeService(
@@ -151,3 +154,64 @@ func TestRunContainersRealtimeScan_ImageVulnerabilityMapping(t *testing.T) {
151154
assert.Equal(t, 5, result.Images[0].Locations[0].StartIndex)
152155
assert.Equal(t, 17, result.Images[0].Locations[0].EndIndex)
153156
}
157+
158+
func TestSplitToImageAndSha_whenHasShaAndTag_shouldReturnImageAndShaAndTag(t *testing.T) {
159+
image := testImageName + ":latest@sha256:1234567890abcdef"
160+
expectedImage := testImageName
161+
expectedSha := "latest@sha256:1234567890abcdef"
162+
163+
imageName, sha := splitToImageAndSha(image)
164+
165+
assert.Equal(t, expectedImage, imageName)
166+
assert.Equal(t, expectedSha, sha)
167+
}
168+
169+
func TestSplitToImageAndSha_whenHasShaNoTag_shouldReturnImageAndShaNoTag(t *testing.T) {
170+
image := testImageName + "@sha256:1234567890abcdef"
171+
expectedImage := testImageName
172+
expectedSha := "sha256:1234567890abcdef"
173+
174+
imageName, sha := splitToImageAndSha(image)
175+
176+
assert.Equal(t, expectedImage, imageName)
177+
assert.Equal(t, expectedSha, sha)
178+
}
179+
180+
func TestSplitLocationsToSeparateResults_MultipleLocations(t *testing.T) {
181+
img := types.ImageModel{
182+
Name: testImageName,
183+
ImageLocations: []types.ImageLocation{
184+
{Line: 1, StartIndex: 0, EndIndex: 5},
185+
{Line: 2, StartIndex: 6, EndIndex: 11},
186+
},
187+
}
188+
result := splitLocationsToSeparateResults([]types.ImageModel{img})
189+
assert.Equal(t, 2, len(result))
190+
assert.Equal(t, 1, len(result[0].ImageLocations))
191+
assert.Equal(t, 1, result[0].ImageLocations[0].Line)
192+
assert.Equal(t, 1, len(result[1].ImageLocations))
193+
assert.Equal(t, 2, result[1].ImageLocations[0].Line)
194+
}
195+
196+
func TestSplitLocationsToSeparateResults_SingleLocation(t *testing.T) {
197+
img := types.ImageModel{
198+
Name: testImageName,
199+
ImageLocations: []types.ImageLocation{
200+
{Line: 3, StartIndex: 0, EndIndex: 5},
201+
},
202+
}
203+
result := splitLocationsToSeparateResults([]types.ImageModel{img})
204+
assert.Equal(t, 1, len(result))
205+
assert.Equal(t, 1, len(result[0].ImageLocations))
206+
assert.Equal(t, 3, result[0].ImageLocations[0].Line)
207+
}
208+
209+
func TestSplitLocationsToSeparateResults_NoLocations(t *testing.T) {
210+
img := types.ImageModel{
211+
Name: testImageName,
212+
ImageLocations: []types.ImageLocation{},
213+
}
214+
result := splitLocationsToSeparateResults([]types.ImageModel{img})
215+
assert.Equal(t, 1, len(result))
216+
assert.Equal(t, 0, len(result[0].ImageLocations))
217+
}

0 commit comments

Comments
 (0)