Skip to content

Commit ca73a55

Browse files
author
Lukas Fischer
committed
#1894 Improve docker reference handling
Move docker reference normalization to the kubernetes side, it fits better there. Several places of the code need different parts of the docker reference of the running containers. This simplifies the access to those properties and uses them in a more consistent way. The scans now only use the actual image name for their name, so that there is space for information other than docker-io-. The hashmap of running containers now uses a normalized reference string instead of the ImageInfo objects, which makes it less error prone with the included reference information. Signed-off-by: Lukas Fischer <lukas.fischer@iteratec.com>
1 parent fe99575 commit ca73a55

File tree

6 files changed

+142
-59
lines changed

6 files changed

+142
-59
lines changed

auto-discovery/cloud-aws/cmd/service/main_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ var _ = Describe("Integration tests", func() {
3434
)
3535

3636
// Templates to check the actual state against
37-
juiceShopScanName1 := "docker-io-bkimminich-test-scan-at-163482fed1f8e7c8558cc476a512b13768a8d2f7a04b8aab407ab02987c42382"
37+
juiceShopScanName1 := "bkimminich-juice-sho-test-scan-at-163482fed1f8e7c8558cc476a512b13768a8d2f7a04b8aab407ab02987c42382"
3838
juiceShopScanName1 = juiceShopScanName1[:62]
39-
juiceShopScanName2 := "docker-io-bkimminich-test-scan-two-at-163482fed1f8e7c8558cc476a512b13768a8d2f7a04b8aab407ab02987c42382"
39+
juiceShopScanName2 := "bkimminich-juice-sho-test-scan-two-at-163482fed1f8e7c8558cc476a512b13768a8d2f7a04b8aab407ab02987c42382"
4040
juiceShopScanName2 = juiceShopScanName2[:62]
4141

4242
juiceShopScanGoTemplate := scanGoTemplate{
@@ -51,9 +51,9 @@ var _ = Describe("Integration tests", func() {
5151
nil,
5252
}
5353

54-
helloWorldScanName1 := "docker-io-library-he-test-scan-at-7e9b6e7ba2842c91cf49f3e214d04a7a496f8214356f41d81a6e6dcad11f11e3"
54+
helloWorldScanName1 := "library-hello-world-test-scan-at-7e9b6e7ba2842c91cf49f3e214d04a7a496f8214356f41d81a6e6dcad11f11e3"
5555
helloWorldScanName1 = helloWorldScanName1[:62]
56-
helloWorldScanName2 := "docker-io-library-he-test-scan-two-at-7e9b6e7ba2842c91cf49f3e214d04a7a496f8214356f41d81a6e6dcad11f11e3"
56+
helloWorldScanName2 := "library-hello-world-test-scan-two-at-7e9b6e7ba2842c91cf49f3e214d04a7a496f8214356f41d81a6e6dcad11f11e3"
5757
helloWorldScanName2 = helloWorldScanName2[:62]
5858

5959
helloWorldScanGoTemplate := scanGoTemplate{

auto-discovery/cloud-aws/pkg/aws/ecs_events.go

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99

1010
awssdk "github.com/aws/aws-sdk-go/aws"
1111
"github.com/aws/aws-sdk-go/service/ecs"
12-
dockerparser "github.com/novln/docker-parser"
1312
"github.com/secureCodeBox/secureCodeBox/auto-discovery/cloud-aws/pkg/kubernetes"
1413
)
1514

@@ -29,21 +28,7 @@ func handleEcsEvent(rawMessage string) ([]kubernetes.Request, error) {
2928

3029
requests := make([]kubernetes.Request, len(stateChange.Detail.Containers))
3130
for idx, container := range stateChange.Detail.Containers {
32-
name := *container.Image
33-
34-
// To prevent misdetection of containers using the same digest but different tags (i.e. none
35-
// and latest or 22.04 and jammy), remove the tag from the image if we have a digest so that
36-
// these images will occupy the same spot in the "set"
37-
// Technically we could also take a tag from the image reference if it includes one, but all
38-
// the libraries to work with image references don't allow accessing that properly
39-
if container.ImageDigest != nil && *container.ImageDigest != "" {
40-
reference, err := dockerparser.Parse(*container.Image)
41-
if err != nil {
42-
return nil, err
43-
}
44-
45-
name = reference.Repository()
46-
} else {
31+
if container.ImageDigest == nil {
4732
container.ImageDigest = awssdk.String("")
4833
}
4934

@@ -52,7 +37,7 @@ func handleEcsEvent(rawMessage string) ([]kubernetes.Request, error) {
5237
Container: kubernetes.ContainerInfo{
5338
Id: *container.ContainerArn,
5439
Image: kubernetes.ImageInfo{
55-
Name: name,
40+
Name: *container.Image,
5641
Digest: *container.ImageDigest,
5742
},
5843
},

auto-discovery/cloud-aws/pkg/aws/events_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ var _ = Describe("AWS Events unit tests", func() {
4242
Container: kubernetes.ContainerInfo{
4343
Id: "VeryUniqueId",
4444
Image: kubernetes.ImageInfo{
45-
Name: "docker.io/bkimminich/juice-shop",
45+
Name: "bkimminich/juice-shop:v15.0.0",
4646
Digest: "sha256:163482fed1f8e7c8558cc476a512b13768a8d2f7a04b8aab407ab02987c42382",
4747
},
4848
},
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// SPDX-FileCopyrightText: the secureCodeBox authors
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package kubernetes
6+
7+
import (
8+
"strings"
9+
10+
dockerparser "github.com/novln/docker-parser"
11+
)
12+
13+
type ImageInfo struct {
14+
Name string
15+
Digest string
16+
parsed *dockerparser.Reference
17+
}
18+
19+
func (image *ImageInfo) normalize() error {
20+
// To prevent misdetection of containers using the same digest but different tags (i.e. none
21+
// and latest or 22.04 and jammy), remove the tag from the image if we have a digest so that
22+
// these images will occupy the same spot in the "set"
23+
parsed, err := dockerparser.Parse(image.Name)
24+
if err != nil {
25+
return err
26+
}
27+
28+
// Store the result for later
29+
image.parsed = parsed
30+
31+
return nil
32+
}
33+
34+
// Get a short, representative name for the image
35+
func (image *ImageInfo) appName() string {
36+
// If the image is parsed or parsing works use library function
37+
if image.parsed != nil || image.normalize() == nil {
38+
return image.parsed.ShortName()
39+
}
40+
41+
// Parsing failed, try to salvage this somehow by returning what we have
42+
name := image.Name
43+
44+
// If name contains a port, domain or localhost remove that
45+
split := strings.Split(name, "/")
46+
if strings.Contains(split[0], ":") || strings.Contains(split[0], ".") || split[0] == "localhost" {
47+
name = strings.Join(split[1:], "")
48+
}
49+
50+
// Remove tag or digest
51+
if strings.Contains(name, ":") {
52+
return strings.Split(name, ":")[0]
53+
} else if strings.Contains(name, "@") {
54+
return strings.Split(name, "@")[0]
55+
} else {
56+
return name
57+
}
58+
}
59+
60+
// Get an identifier for the version of the app, either a tag if available or the digest
61+
func (image *ImageInfo) version() string {
62+
// If the image is parsed or parsing works use library function
63+
if image.parsed != nil || image.normalize() == nil {
64+
return image.parsed.Tag()
65+
}
66+
67+
// Parsing failed, try to salvage this somehow by returning what we have
68+
// Check if we have a tag by getting the last component of the reference and checking for :
69+
split := strings.Split(image.Name, "/")
70+
last := split[len(split)-1]
71+
if strings.Contains(last, ":") {
72+
split = strings.Split(last, ":")
73+
return split[len(split)-1]
74+
} else if strings.Contains(last, "@") {
75+
split = strings.Split(last, "@")
76+
return split[len(split)-1]
77+
} else {
78+
return "latest"
79+
}
80+
}
81+
82+
// Get the digest without prefix
83+
func (image *ImageInfo) hash() string {
84+
digest := image.Digest
85+
// Try to use a digest from the reference if there is one and the separate one is empty
86+
if image.Digest == "" {
87+
// If the image is parsed or parsing works use library function
88+
if image.parsed != nil || image.normalize() == nil {
89+
// The library stores digest and tag in the same way, wonderfully convenient
90+
maybeDigest := image.parsed.Tag()
91+
if strings.Contains(maybeDigest, ":") {
92+
digest = maybeDigest
93+
}
94+
} else {
95+
split := strings.Split(image.Name, "@")
96+
digest = split[len(split)-1]
97+
}
98+
}
99+
100+
split := strings.Split(digest, ":")
101+
return split[len(split)-1]
102+
}
103+
104+
// Get a complete and unique reference to this docker image
105+
func (image *ImageInfo) reference() string {
106+
// If the image is parsed or parsing works use library function
107+
if image.parsed != nil || image.normalize() == nil {
108+
if image.Digest == "" {
109+
return image.parsed.Remote()
110+
} else {
111+
return image.parsed.Repository() + "@" + image.Digest
112+
}
113+
}
114+
115+
// Parsing failed, try to salvage this somehow by returning what we have
116+
if image.Digest == "" {
117+
return image.Name
118+
} else {
119+
return image.Name + "@" + image.Digest
120+
}
121+
}

auto-discovery/cloud-aws/pkg/kubernetes/kubernetes.go

Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -39,28 +39,6 @@ type ContainerInfo struct {
3939
Image ImageInfo
4040
}
4141

42-
type ImageInfo struct {
43-
Name string
44-
Digest string
45-
}
46-
47-
func (image *ImageInfo) getImageName() string {
48-
return strings.Split(image.Name, ":")[0]
49-
}
50-
51-
func (image *ImageInfo) getImageHash() string {
52-
split := strings.Split(image.Digest, ":")
53-
return split[len(split)-1]
54-
}
55-
56-
func (image *ImageInfo) getReference() string {
57-
if image.Digest == "" {
58-
return image.Name
59-
} else {
60-
return image.Name + "@" + image.Digest
61-
}
62-
}
63-
6442
type AWSReconciler interface {
6543
Reconcile(ctx context.Context, req Request) error
6644
}
@@ -72,7 +50,7 @@ type AWSContainerScanReconciler struct {
7250

7351
// Track which images are actually running and which instances use them
7452
// Technically a HashMap to a HashSet but Go doesn't have sets
75-
RunningContainers map[ImageInfo]map[string]struct{}
53+
RunningContainers map[string]map[string]struct{}
7654
}
7755

7856
type ContainerAutoDiscoveryTemplateArgs struct {
@@ -83,7 +61,7 @@ type ContainerAutoDiscoveryTemplateArgs struct {
8361
}
8462

8563
func (r *AWSContainerScanReconciler) Reconcile(ctx context.Context, req Request) error {
86-
r.Log.V(1).Info("Received reconcile request", "State", req.State, "Image", req.Container.Image.getReference())
64+
r.Log.V(1).Info("Received reconcile request", "State", req.State, "Image", req.Container.Image.reference())
8765

8866
// Decide what to do based on the current state we are notified about and the information we
8967
// saved about this container
@@ -117,18 +95,18 @@ func NewAWSReconcilerWith(client client.Client, cfg *config.AutoDiscoveryConfig,
11795
Client: client,
11896
Config: cfg,
11997
Log: log,
120-
RunningContainers: make(map[ImageInfo]map[string]struct{}),
98+
RunningContainers: make(map[string]map[string]struct{}),
12199
}
122100
}
123101

124102
func (r *AWSContainerScanReconciler) handleCreateRequest(ctx context.Context, req Request) error {
125103
// Make sure there is at least an empty "set"
126-
if r.RunningContainers[req.Container.Image] == nil {
127-
r.RunningContainers[req.Container.Image] = make(map[string]struct{})
104+
if r.RunningContainers[req.Container.Image.reference()] == nil {
105+
r.RunningContainers[req.Container.Image.reference()] = make(map[string]struct{})
128106
}
129107

130108
// Add this container to the "set" for this image
131-
r.RunningContainers[req.Container.Image][req.Container.Id] = struct{}{}
109+
r.RunningContainers[req.Container.Image.reference()][req.Container.Id] = struct{}{}
132110

133111
// Create all configured scans
134112
var err error = nil
@@ -156,14 +134,14 @@ func (r *AWSContainerScanReconciler) handleCreateRequest(ctx context.Context, re
156134
}
157135

158136
func (r *AWSContainerScanReconciler) handleDeleteRequest(ctx context.Context, req Request) error {
159-
if r.RunningContainers[req.Container.Image] == nil {
137+
if r.RunningContainers[req.Container.Image.reference()] == nil {
160138
// We received a PENDING/STOPPED event but this container wasn't running before either
161139
r.Log.V(1).Info("Container was not running before, nothing to do")
162140
return nil
163141
}
164142

165-
delete(r.RunningContainers[req.Container.Image], req.Container.Id)
166-
if len(r.RunningContainers[req.Container.Image]) > 0 {
143+
delete(r.RunningContainers[req.Container.Image.reference()], req.Container.Id)
144+
if len(r.RunningContainers[req.Container.Image.reference()]) > 0 {
167145
// More containers using this image are running, keep the ScheduledScan
168146
r.Log.V(1).Info("There are still instances of this image running, keeping the ScheduledScan")
169147
return nil
@@ -226,7 +204,7 @@ func getScheduledScanForRequest(req Request, cfg *config.AutoDiscoveryConfig, sc
226204
Config: *cfg,
227205
ScanConfig: scanConfig,
228206
Target: req.Container,
229-
ImageID: req.Container.Image.getReference(),
207+
ImageID: req.Container.Image.reference(),
230208
}
231209
scanSpec := util.GenerateScanSpec(scanConfig, templateArgs)
232210

@@ -246,10 +224,9 @@ func getScanName(req Request, name string) string {
246224
// adapted from the kubernetes container autodiscovery
247225
// function builds string like: _appName_-_customScanName_-at-_imageID_HASH_ eg: nginx-myTrivyScan-at-0123456789
248226

249-
appName := req.Container.Image.getImageName()
250-
hash := req.Container.Image.getImageHash()
227+
appName := req.Container.Image.appName()
228+
hash := req.Container.Image.hash()
251229

252-
// TODO if image name contains a namespace the actual name will mostly get cut off
253230
// cutoff appname if it is longer than 20 chars
254231
maxAppLength := 20
255232
if len(appName) > maxAppLength {

auto-discovery/cloud-aws/pkg/kubernetes/kubernetes_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ import (
1010
)
1111

1212
var _ = Describe("Kubernetes unit tests", func() {
13-
scanName := "docker-io-bkimminich-aws-trivy-sbom-at-163482fed1f8e7c8558cc476a512b13768a8d2f7a04b8aab407ab02987c42382"
13+
scanName := "bkimminich-juice-sho-aws-trivy-sbom-at-163482fed1f8e7c8558cc476a512b13768a8d2f7a04b8aab407ab02987c42382"
1414
scanName = scanName[:62]
1515

1616
req := Request{
1717
State: "RUNNING",
1818
Container: ContainerInfo{
1919
Id: "VeryUniqueId",
2020
Image: ImageInfo{
21-
Name: "docker.io/bkimminich/juice-shop",
21+
Name: "docker.io/bkimminich/juice-shop:v15.0.0",
2222
Digest: "sha256:163482fed1f8e7c8558cc476a512b13768a8d2f7a04b8aab407ab02987c42382",
2323
},
2424
},

0 commit comments

Comments
 (0)