Files
aquasecurity-trivy/extractor/docker/docker_test.go
Teppei Fukuda a1e818ac5e refactor(cache): replace image and layer with artifact and blob (fanal#108)
* refactor(cache): replace image and layer with artifact and blob

* fix(cache): replace S3
2020-05-21 10:51:29 +03:00

541 lines
14 KiB
Go

package docker
import (
"context"
"fmt"
"net/http/httptest"
"os"
"sort"
"testing"
"github.com/aquasecurity/testdocker/engine"
"github.com/aquasecurity/testdocker/registry"
godeptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/fanal/extractor"
"github.com/aquasecurity/fanal/types"
)
func TestApplyLayers(t *testing.T) {
testCases := []struct {
name string
inputLayers []types.BlobInfo
expectedArtifactDetail types.ArtifactDetail
}{
{
name: "happy path",
inputLayers: []types.BlobInfo{
{
SchemaVersion: 1,
Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
OS: &types.OS{
Family: "alpine",
Name: "3.10",
},
PackageInfos: []types.PackageInfo{
{
FilePath: "lib/apk/db/installed",
Packages: []types.Package{
{
Name: "openssl",
Version: "1.2.3",
Release: "4.5.6",
},
},
},
},
Applications: []types.Application{
{
Type: "gem",
FilePath: "app/Gemfile.lock",
Libraries: []types.LibraryInfo{
{
Library: godeptypes.Library{
Name: "gemlibrary1",
Version: "1.2.3",
},
},
},
},
{
Type: "composer",
FilePath: "app/composer.lock",
Libraries: []types.LibraryInfo{
{
Library: godeptypes.Library{
Name: "phplibrary1",
Version: "6.6.6",
},
},
},
},
},
},
{
SchemaVersion: 1,
Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7",
DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819",
PackageInfos: []types.PackageInfo{
{
FilePath: "lib/apk/db/installed",
Packages: []types.Package{
{
Name: "openssl",
Version: "1.2.3",
Release: "4.5.6",
},
{ // added
Name: "musl",
Version: "1.2.4",
Release: "4.5.7",
},
},
},
},
WhiteoutFiles: []string{"app/composer.lock"},
},
{
SchemaVersion: 1,
Digest: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4",
DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
PackageInfos: []types.PackageInfo{
{
FilePath: "lib/apk/db/installed",
Packages: []types.Package{
{
Name: "openssl",
Version: "1.2.3",
Release: "4.5.6",
},
{
Name: "musl",
Version: "1.2.4",
Release: "4.5.8", // updated
},
},
},
},
},
},
expectedArtifactDetail: types.ArtifactDetail{
OS: &types.OS{
Family: "alpine",
Name: "3.10",
},
Packages: []types.Package{
{
Name: "musl",
Version: "1.2.4",
Release: "4.5.8",
Layer: types.Layer{
Digest: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4",
DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
},
},
{
Name: "openssl",
Version: "1.2.3",
Release: "4.5.6",
Layer: types.Layer{
Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
},
},
},
Applications: []types.Application{
{
Type: "gem",
FilePath: "app/Gemfile.lock",
Libraries: []types.LibraryInfo{
{
Library: godeptypes.Library{
Name: "gemlibrary1",
Version: "1.2.3",
},
Layer: types.Layer{
Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
},
},
},
},
},
},
},
{
name: "happy path with removed and updated lockfile",
inputLayers: []types.BlobInfo{
{
SchemaVersion: 1,
Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
OS: &types.OS{
Family: "alpine",
Name: "3.10",
},
Applications: []types.Application{
{
Type: "gem",
FilePath: "app/Gemfile.lock",
Libraries: []types.LibraryInfo{
{
Library: godeptypes.Library{
Name: "rails",
Version: "5.0.0",
},
},
{
Library: godeptypes.Library{
Name: "rack",
Version: "4.0.0",
},
},
},
},
{
Type: "composer",
FilePath: "app/composer.lock",
Libraries: []types.LibraryInfo{
{
Library: godeptypes.Library{
Name: "phplibrary1",
Version: "6.6.6",
},
},
},
},
},
},
{
SchemaVersion: 1,
Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7",
DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819",
Applications: []types.Application{
{
Type: "gem",
FilePath: "app/Gemfile.lock",
Libraries: []types.LibraryInfo{
{
Library: godeptypes.Library{
Name: "rails",
Version: "6.0.0",
},
},
{
Library: godeptypes.Library{
Name: "rack",
Version: "4.0.0",
},
},
},
},
{
Type: "composer",
FilePath: "app/composer2.lock",
Libraries: []types.LibraryInfo{
{
Library: godeptypes.Library{
Name: "phplibrary1",
Version: "6.6.6",
},
},
},
},
},
WhiteoutFiles: []string{"app/composer.lock"},
},
},
expectedArtifactDetail: types.ArtifactDetail{
OS: &types.OS{
Family: "alpine",
Name: "3.10",
},
Applications: []types.Application{
{
Type: "gem",
FilePath: "app/Gemfile.lock",
Libraries: []types.LibraryInfo{
{
Library: godeptypes.Library{
Name: "rack",
Version: "4.0.0",
},
Layer: types.Layer{
Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
},
},
{
Library: godeptypes.Library{
Name: "rails",
Version: "6.0.0",
},
Layer: types.Layer{
Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7",
DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819",
},
},
},
},
{
Type: "composer",
FilePath: "app/composer2.lock",
Libraries: []types.LibraryInfo{
{
Library: godeptypes.Library{
Name: "phplibrary1",
Version: "6.6.6",
},
Layer: types.Layer{
Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7",
DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819",
},
},
},
},
},
},
},
{
name: "happy path with status.d",
inputLayers: []types.BlobInfo{
{
SchemaVersion: 1,
Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7",
DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819",
OS: &types.OS{
Family: "debian",
Name: "8",
},
PackageInfos: []types.PackageInfo{
{
FilePath: "var/lib/dpkg/status.d/openssl",
Packages: []types.Package{
{
Name: "openssl",
Version: "1.2.3",
Release: "4.5.6",
},
},
},
},
Applications: []types.Application{
{
Type: "composer",
FilePath: "app/composer.lock",
Libraries: []types.LibraryInfo{
{
Library: godeptypes.Library{
Name: "phplibrary1",
Version: "6.6.6",
},
},
},
},
},
},
{
SchemaVersion: 1,
Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
PackageInfos: []types.PackageInfo{
{
FilePath: "var/lib/dpkg/status.d/libc",
Packages: []types.Package{
{
Name: "libc",
Version: "1.2.4",
Release: "4.5.7",
},
},
},
},
OpaqueDirs: []string{"app"},
},
},
expectedArtifactDetail: types.ArtifactDetail{
OS: &types.OS{
Family: "debian",
Name: "8",
},
Packages: []types.Package{
{
Name: "libc",
Version: "1.2.4",
Release: "4.5.7",
Layer: types.Layer{
Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
},
},
{
Name: "openssl",
Version: "1.2.3",
Release: "4.5.6",
Layer: types.Layer{
Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7",
DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819",
},
},
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
gotArtifactDetail := ApplyLayers(tc.inputLayers)
sort.Slice(gotArtifactDetail.Packages, func(i, j int) bool {
return gotArtifactDetail.Packages[i].Name < gotArtifactDetail.Packages[j].Name
})
sort.Slice(gotArtifactDetail.Applications, func(i, j int) bool {
return gotArtifactDetail.Applications[i].FilePath < gotArtifactDetail.Applications[j].FilePath
})
for _, app := range gotArtifactDetail.Applications {
sort.Slice(app.Libraries, func(i, j int) bool {
return app.Libraries[i].Library.Name < app.Libraries[j].Library.Name
})
}
assert.Equal(t, tc.expectedArtifactDetail, gotArtifactDetail, tc.name)
})
}
}
func TestExtractor_ExtractLayerFiles(t *testing.T) {
te, tr := setupDockerEnvironment()
defer te.Close()
defer tr.Close()
serverAddr := tr.Listener.Addr().String()
type fields struct {
option types.DockerOption
}
type args struct {
ctx context.Context
diffID string
filenames []string
}
tests := []struct {
name string
imageName string
fields fields
args args
expectedDigest string
expectedFileMap extractor.FileMap
expectedOpqDirs []string
expectedWhFiles []string
wantErr string
}{
{
name: "happy path with Docker Registry",
imageName: fmt.Sprintf("%s/library/image1", serverAddr),
args: args{
ctx: nil,
diffID: "sha256:d9ff549177a94a413c425ffe14ae1cc0aa254bc9c7df781add08e7d2fba25d27",
filenames: []string{"etc/hostname"},
},
expectedDigest: "sha256:fe18b2be62164eb835d8c8c65d75682782d67d6fb1b4406a8943b4c538c5bbf5",
expectedFileMap: extractor.FileMap{
"etc/hostname": []byte("localhost\n"),
},
},
{
name: "happy path with Docker Engine",
imageName: "image1:latest",
args: args{
ctx: nil,
diffID: "sha256:d9ff549177a94a413c425ffe14ae1cc0aa254bc9c7df781add08e7d2fba25d27",
filenames: []string{"etc/hostname"},
},
expectedDigest: "", // Docker Engine doesn't have the ID of the compressed layer
expectedFileMap: extractor.FileMap{
"etc/hostname": []byte("localhost\n"),
},
},
{
name: "opq file path",
imageName: fmt.Sprintf("%s/library/image1", serverAddr),
args: args{
ctx: nil,
diffID: "sha256:a8b87ccf2f2f94b9e23308560800afa3f272aa6db5cc7d9b0119b6843889cff2",
filenames: []string{"etc/test/"},
},
expectedDigest: "sha256:c12d5ff49cfae67c6b0289ec7fb55a7e00aff1bafbc4b3da581325032c254a57",
expectedFileMap: extractor.FileMap{
"etc/test/bar": []byte("bar\n"),
},
expectedOpqDirs: []string{"etc/test/"},
expectedWhFiles: []string{"var/foo"},
},
{
name: "sad path with unknown layer",
imageName: fmt.Sprintf("%s/library/image1", serverAddr),
args: args{
ctx: nil,
diffID: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", // unknown
filenames: []string{"var/foo"},
},
wantErr: "unknown diffID",
},
{
name: "sad path with invalid layer ID",
imageName: fmt.Sprintf("%s/library/image1", serverAddr),
args: args{
ctx: nil,
diffID: "sha256:unknown",
filenames: []string{"var/foo"},
},
wantErr: "invalid layer ID",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d, cleanup, err := NewDockerExtractor(context.Background(), tt.imageName, types.DockerOption{})
require.NoError(t, err)
defer cleanup()
actualDigest, actualFileMap, actualOpqDirs, actualWhFiles, err := d.ExtractLayerFiles(tt.args.diffID, tt.args.filenames)
if tt.wantErr != "" {
require.NotNil(t, err, tt.name)
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
return
} else {
require.NoError(t, err, tt.name)
}
assert.Equal(t, tt.expectedDigest, actualDigest)
assert.Equal(t, tt.expectedFileMap, actualFileMap)
assert.Equal(t, tt.expectedOpqDirs, actualOpqDirs)
assert.Equal(t, tt.expectedWhFiles, actualWhFiles)
})
}
}
func setupDockerEnvironment() (*httptest.Server, *httptest.Server) {
// Docker Engine
imagePaths := map[string]string{
"index.docker.io/library/image1:latest": "../testdata/image1.tar",
}
opt := engine.Option{
APIVersion: "1.38",
ImagePaths: imagePaths,
}
te := engine.NewDockerEngine(opt)
os.Setenv("DOCKER_HOST", fmt.Sprintf("tcp://%s", te.Listener.Addr().String()))
// Docker Registry
imagePaths = map[string]string{
"v2/library/image1:latest": "../testdata/image1.tar",
}
tr := registry.NewDockerRegistry(registry.Option{
Images: imagePaths,
})
return te, tr
}