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  }