github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/image/daemon/image_test.go (about) 1 package daemon 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "runtime" 8 "testing" 9 "time" 10 11 dimage "github.com/docker/docker/api/types/image" 12 "github.com/google/go-containerregistry/pkg/name" 13 v1 "github.com/google/go-containerregistry/pkg/v1" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 17 "github.com/aquasecurity/testdocker/engine" 18 ) 19 20 var imagePaths = map[string]string{ 21 "alpine:3.10": "../../test/testdata/alpine-310.tar.gz", 22 "alpine:3.11": "../../test/testdata/alpine-311.tar.gz", 23 "gcr.io/distroless/base": "../../test/testdata/distroless.tar.gz", 24 } 25 26 // for Docker 27 var opt = engine.Option{ 28 APIVersion: "1.38", 29 ImagePaths: imagePaths, 30 } 31 32 func TestMain(m *testing.M) { 33 te := engine.NewDockerEngine(opt) 34 defer te.Close() 35 36 os.Setenv("DOCKER_HOST", fmt.Sprintf("tcp://%s", te.Listener.Addr().String())) 37 38 os.Exit(m.Run()) 39 } 40 41 func Test_image_ConfigName(t *testing.T) { 42 tests := []struct { 43 name string 44 imageName string 45 want v1.Hash 46 wantErr bool 47 }{ 48 { 49 name: "happy path", 50 imageName: "alpine:3.11", 51 want: v1.Hash{ 52 Algorithm: "sha256", 53 Hex: "a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", 54 }, 55 wantErr: false, 56 }, 57 } 58 for _, tt := range tests { 59 t.Run(tt.name, func(t *testing.T) { 60 ref, err := name.ParseReference(tt.imageName) 61 require.NoError(t, err) 62 63 img, cleanup, err := DockerImage(ref, "") 64 require.NoError(t, err) 65 defer cleanup() 66 67 conf, err := img.ConfigName() 68 assert.Equal(t, tt.want, conf) 69 assert.Equal(t, tt.wantErr, err != nil) 70 }) 71 } 72 } 73 74 func Test_image_ConfigNameWithCustomDockerHost(t *testing.T) { 75 76 ref, err := name.ParseReference("alpine:3.11") 77 require.NoError(t, err) 78 79 eo := engine.Option{ 80 APIVersion: opt.APIVersion, 81 ImagePaths: opt.ImagePaths, 82 } 83 84 var dockerHostParam string 85 86 if runtime.GOOS != "windows" { 87 runtimeDir, err := os.MkdirTemp("", "daemon") 88 require.NoError(t, err) 89 90 dir := filepath.Join(runtimeDir, "image") 91 err = os.MkdirAll(dir, os.ModePerm) 92 require.NoError(t, err) 93 94 customDockerHost := filepath.Join(dir, "image-test-unix-socket.sock") 95 eo.UnixDomainSocket = customDockerHost 96 dockerHostParam = "unix://" + customDockerHost 97 } 98 99 te := engine.NewDockerEngine(eo) 100 defer te.Close() 101 102 if runtime.GOOS == "windows" { 103 dockerHostParam = te.Listener.Addr().Network() + "://" + te.Listener.Addr().String() 104 } 105 106 img, cleanup, err := DockerImage(ref, dockerHostParam) 107 require.NoError(t, err) 108 defer cleanup() 109 110 conf, err := img.ConfigName() 111 assert.Equal(t, v1.Hash{ 112 Algorithm: "sha256", 113 Hex: "a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", 114 }, conf) 115 assert.Nil(t, err) 116 } 117 118 func Test_image_ConfigFile(t *testing.T) { 119 tests := []struct { 120 name string 121 imageName string 122 want *v1.ConfigFile 123 wantErr bool 124 }{ 125 { 126 name: "one diff_id", 127 imageName: "alpine:3.11", 128 want: &v1.ConfigFile{ 129 Architecture: "amd64", 130 Container: "fb71ddde5f6411a82eb056a9190f0cc1c80d7f77a8509ee90a2054428edb0024", 131 OS: "linux", 132 Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 196162891, time.UTC)}, 133 DockerVersion: "18.09.7", 134 History: []v1.History{ 135 { 136 Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 0, time.UTC)}, 137 CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", 138 Comment: "", 139 EmptyLayer: true, 140 }, 141 { 142 Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 0, time.UTC)}, 143 CreatedBy: "/bin/sh -c #(nop) ADD file:0c4555f363c2672e350001f1293e689875a3760afe7b3f9146886afe67121cba in / ", 144 EmptyLayer: false, 145 }, 146 }, 147 RootFS: v1.RootFS{ 148 Type: "layers", 149 DiffIDs: []v1.Hash{ 150 { 151 Algorithm: "sha256", 152 Hex: "beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", 153 }, 154 }, 155 }, 156 Config: v1.Config{ 157 Cmd: []string{"/bin/sh"}, 158 Image: "sha256:74df73bb19fbfc7fb5ab9a8234b3d98ee2fb92df5b824496679802685205ab8c", 159 Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, 160 ArgsEscaped: true, 161 }, 162 OSVersion: "", 163 }, 164 wantErr: false, 165 }, 166 { 167 name: "multiple diff_ids", 168 imageName: "gcr.io/distroless/base", 169 want: &v1.ConfigFile{ 170 Architecture: "amd64", 171 OS: "linux", 172 Author: "Bazel", 173 Created: v1.Time{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)}, 174 History: []v1.History{ 175 { 176 Created: v1.Time{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)}, 177 CreatedBy: "bazel build ...", 178 EmptyLayer: false, 179 }, 180 { 181 Created: v1.Time{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)}, 182 CreatedBy: "bazel build ...", 183 EmptyLayer: false, 184 }, 185 }, 186 RootFS: v1.RootFS{ 187 Type: "layers", 188 DiffIDs: []v1.Hash{ 189 {Algorithm: "sha256", Hex: "42a3027eaac150d2b8f516100921f4bd83b3dbc20bfe64124f686c072b49c602"}, 190 {Algorithm: "sha256", Hex: "f47163e8de57e3e3ccfe89d5dfbd9c252d9eca53dc7906b8db60eddcb876c592"}, 191 }, 192 }, 193 Config: v1.Config{Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt"}}, 194 OSVersion: "", 195 }, 196 wantErr: false, 197 }, 198 } 199 for _, tt := range tests { 200 t.Run(tt.name, func(t *testing.T) { 201 ref, err := name.ParseReference(tt.imageName) 202 require.NoError(t, err) 203 204 img, cleanup, err := DockerImage(ref, "") 205 require.NoError(t, err) 206 defer cleanup() 207 208 conf, err := img.ConfigFile() 209 require.Equal(t, tt.wantErr, err != nil, err) 210 assert.Equal(t, tt.want, conf) 211 }) 212 } 213 } 214 215 func Test_image_LayerByDiffID(t *testing.T) { 216 type args struct { 217 h v1.Hash 218 } 219 tests := []struct { 220 name string 221 imageName string 222 args args 223 wantErr bool 224 }{ 225 { 226 name: "happy path", 227 imageName: "alpine:3.10", 228 args: args{h: v1.Hash{ 229 Algorithm: "sha256", 230 Hex: "531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", 231 }}, 232 wantErr: false, 233 }, 234 { 235 name: "ImageSave returns 404", 236 imageName: "alpine:3.11", 237 args: args{h: v1.Hash{ 238 Algorithm: "sha256", 239 Hex: "531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", 240 }}, 241 wantErr: true, 242 }, 243 } 244 for _, tt := range tests { 245 t.Run(tt.name, func(t *testing.T) { 246 ref, err := name.ParseReference(tt.imageName) 247 require.NoError(t, err) 248 249 img, cleanup, err := DockerImage(ref, "") 250 require.NoError(t, err) 251 defer cleanup() 252 253 _, err = img.LayerByDiffID(tt.args.h) 254 assert.Equal(t, tt.wantErr, err != nil, err) 255 }) 256 } 257 } 258 259 func Test_image_RawConfigFile(t *testing.T) { 260 tests := []struct { 261 name string 262 imageName string 263 goldenFile string 264 wantErr bool 265 }{ 266 { 267 name: "happy path", 268 imageName: "alpine:3.10", 269 goldenFile: "testdata/golden/config-alpine310.json", 270 wantErr: false, 271 }, 272 } 273 for _, tt := range tests { 274 t.Run(tt.name, func(t *testing.T) { 275 ref, err := name.ParseReference(tt.imageName) 276 require.NoError(t, err) 277 278 img, cleanup, err := DockerImage(ref, "") 279 require.NoError(t, err) 280 defer cleanup() 281 282 got, err := img.RawConfigFile() 283 assert.Equal(t, tt.wantErr, err != nil, err) 284 285 if err != nil { 286 return 287 } 288 289 want, err := os.ReadFile(tt.goldenFile) 290 require.NoError(t, err) 291 292 require.JSONEq(t, string(want), string(got)) 293 }) 294 } 295 } 296 297 func Test_image_emptyLayer(t *testing.T) { 298 tests := []struct { 299 name string 300 history dimage.HistoryResponseItem 301 want bool 302 }{ 303 { 304 name: "size != 0", 305 history: dimage.HistoryResponseItem{ 306 Size: 10, 307 }, 308 want: false, 309 }, 310 { 311 name: "ENV", 312 history: dimage.HistoryResponseItem{ 313 CreatedBy: "/bin/sh -c #(nop) ENV TESTENV=TEST", 314 }, 315 want: true, 316 }, 317 { 318 name: "ENV created with buildkit", 319 history: dimage.HistoryResponseItem{ 320 CreatedBy: "ENV BUILDKIT_ENV=TEST", 321 Comment: "buildkit.dockerfile.v0", 322 }, 323 want: true, 324 }, 325 { 326 name: "ENV", 327 history: dimage.HistoryResponseItem{ 328 CreatedBy: "/bin/sh -c #(nop) ENV TESTENV=TEST", 329 }, 330 want: true, 331 }, 332 { 333 name: "CMD", 334 history: dimage.HistoryResponseItem{ 335 CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", 336 }, 337 want: true, 338 }, 339 { 340 name: "WORKDIR == '/'", 341 history: dimage.HistoryResponseItem{ 342 CreatedBy: "/bin/sh -c #(nop) WORKDIR /", 343 }, 344 want: true, 345 }, 346 { 347 name: "WORKDIR != '/'", 348 history: dimage.HistoryResponseItem{ 349 CreatedBy: "/bin/sh -c #(nop) WORKDIR /app", 350 }, 351 want: true, 352 }, 353 { 354 name: "WORKDIR =='/' buildkit", 355 history: dimage.HistoryResponseItem{ 356 CreatedBy: "/bin/sh -c #(nop) WORKDIR /", 357 Comment: "buildkit.dockerfile.v0", 358 }, 359 want: true, 360 }, 361 { 362 name: "WORKDIR == '/app' buildkit", 363 history: dimage.HistoryResponseItem{ 364 CreatedBy: "/bin/sh -c #(nop) WORKDIR /app", 365 Comment: "buildkit.dockerfile.v0", 366 }, 367 want: false, 368 }, 369 { 370 name: "without command", 371 history: dimage.HistoryResponseItem{ 372 CreatedBy: "/bin/sh -c mkdir test", 373 }, 374 want: false, 375 }, 376 } 377 378 for _, tt := range tests { 379 t.Run(tt.name, func(t *testing.T) { 380 empty := emptyLayer(tt.history) 381 assert.Equal(t, tt.want, empty) 382 }) 383 } 384 }