package cmd import ( "fmt" "io" "io/ioutil" "net/http" "os" "strings" "testing" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/fake" piperDocker "github.com/SAP/jenkins-library/pkg/docker" piperhttp "github.com/SAP/jenkins-library/pkg/http" "github.com/SAP/jenkins-library/pkg/malwarescan" "github.com/SAP/jenkins-library/pkg/mock" "github.com/stretchr/testify/assert" ) var malwareScanConfig = malwareExecuteScanOptions{ Host: "https://example.org/malwarescanner", Username: "me", Password: "********", ScanFile: "target/myFile", Timeout: "60", } type malwareScanUtilsMockBundle struct { *mock.FilesMock returnScanResult *malwarescan.ScanResult returnSHA256 string } func (utils *malwareScanUtilsMockBundle) SHA256(filePath string) (string, error) { if utils.returnSHA256 == "" { return utils.returnScanResult.SHA256, nil } return utils.returnSHA256, nil } func (utils *malwareScanUtilsMockBundle) OpenFile(path string, flag int, perm os.FileMode) (io.ReadCloser, error) { return utils.FilesMock.OpenFile(path, flag, perm) } func (utils *malwareScanUtilsMockBundle) FileWrite(path string, content []byte, perm os.FileMode) error { return utils.FilesMock.FileWrite(path, content, perm) } func (utils *malwareScanUtilsMockBundle) Info() (*malwarescan.Info, error) { return &malwarescan.Info{EngineVersion: "Mock Malware Scanner", SignatureTimestamp: "n/a"}, nil } func (utils *malwareScanUtilsMockBundle) Scan(candidate io.Reader) (*malwarescan.ScanResult, error) { return utils.returnScanResult, nil } func (utils *malwareScanUtilsMockBundle) newDockerClient(options piperDocker.ClientOptions) piperDocker.Download { return &dockerClientMock{imageName: options.ImageName, registryURL: options.RegistryURL, localPath: options.LocalPath} } func TestMalwareScanWithNoBuildtool(t *testing.T) { files := &mock.FilesMock{} files.AddFile("target/myFile", []byte(`HELLO`)) utils := malwareScanUtilsMockBundle{ FilesMock: files, } t.Run("No malware, no encrypted content in file", func(t *testing.T) { utils.returnScanResult = &malwarescan.ScanResult{ MalwareDetected: false, EncryptedContentDetected: false, ScanSize: 298782, MimeType: "application/octet-stream", SHA256: "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85", } error := runMalwareScan(&malwareScanConfig, nil, &utils) assert.NoError(t, error) }) t.Run("Malware detected in file", func(t *testing.T) { utils.returnScanResult = &malwarescan.ScanResult{ MalwareDetected: true, EncryptedContentDetected: false, ScanSize: 298782, MimeType: "application/octet-stream", SHA256: "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85", Finding: "Win.Test.EICAR_HDB-1", } error := runMalwareScan(&malwareScanConfig, nil, &utils) assert.EqualError(t, error, "Malware scan failed for file 'target/myFile'. Malware detected: true, encrypted content detected: false, finding: Win.Test.EICAR_HDB-1") }) t.Run("Encrypted content detected in file", func(t *testing.T) { utils.returnScanResult = &malwarescan.ScanResult{ MalwareDetected: false, EncryptedContentDetected: true, ScanSize: 298782, MimeType: "application/octet-stream", SHA256: "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85", } error := runMalwareScan(&malwareScanConfig, nil, &utils) assert.EqualError(t, error, "Malware scan failed for file 'target/myFile'. Malware detected: false, encrypted content detected: true, finding: ") }) t.Run("Malware and encrypted content detected in file", func(t *testing.T) { utils.returnScanResult = &malwarescan.ScanResult{ MalwareDetected: true, EncryptedContentDetected: true, ScanSize: 298782, MimeType: "application/octet-stream", SHA256: "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85", Finding: "Win.Test.EICAR_HDB-1", } error := runMalwareScan(&malwareScanConfig, nil, &utils) assert.EqualError(t, error, "Malware scan failed for file 'target/myFile'. Malware detected: true, encrypted content detected: true, finding: Win.Test.EICAR_HDB-1") }) t.Run("No file and no buildtool specified", func(t *testing.T) { malwareScanConfig := malwareExecuteScanOptions{ Host: "https://example.org/malwarescanner", Username: "me", Password: "********", Timeout: "60", } error := runMalwareScan(&malwareScanConfig, nil, nil) assert.EqualError(t, error, "Please specify a file to be scanned") }) t.Run("File to be scanned, can't be found", func(t *testing.T) { utils.returnScanResult = nil malwareScanConfig := malwareExecuteScanOptions{ Host: "https://example.org/malwarescanner", Username: "me", Password: "********", Timeout: "60", ScanFile: "target/fileWhichDoesntExist", } error := runMalwareScan(&malwareScanConfig, nil, &utils) assert.EqualError(t, error, "the file 'target/fileWhichDoesntExist' does not exist: file does not exist") }) } func TestMalwareScanWithDockerAsBuildtoolTests(t *testing.T) { files := &mock.FilesMock{} files.AddFile("dockerimagename_latest.tar", []byte(``)) utils := malwareScanUtilsMockBundle{ FilesMock: files, } t.Run("No malware detected in docker image", func(t *testing.T) { utils.returnScanResult = &malwarescan.ScanResult{ MalwareDetected: false, EncryptedContentDetected: false, ScanSize: 298782, MimeType: "application/octet-stream", SHA256: "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85", } malwareScanConfig := malwareExecuteScanOptions{ Host: "https://example.org/malwarescanner", Username: "me", Password: "********", Timeout: "60", BuildTool: "docker", ScanImage: "dockerimagename:latest", } error := runMalwareScan(&malwareScanConfig, nil, &utils) assert.NoError(t, error) }) t.Run("No file and no buildtool specified", func(t *testing.T) { malwareScanConfig := malwareExecuteScanOptions{ Host: "https://example.org/malwarescanner", Username: "me", Password: "********", Timeout: "60", } error := runMalwareScan(&malwareScanConfig, nil, &utils) assert.EqualError(t, error, "Please specify a file to be scanned") }) } func TestMalwareScanWithOtherBuildtoolTests(t *testing.T) { files := &mock.FilesMock{} files.AddFile("dockerimagename_latest.tar", []byte(``)) utils := malwareScanUtilsMockBundle{ FilesMock: files, } t.Run("No malware detected in docker image", func(t *testing.T) { utils.returnScanResult = &malwarescan.ScanResult{ MalwareDetected: false, EncryptedContentDetected: false, ScanSize: 298782, MimeType: "application/octet-stream", SHA256: "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85", } malwareScanConfig := malwareExecuteScanOptions{ Host: "https://example.org/malwarescanner", Username: "me", Password: "********", Timeout: "60", BuildTool: "golang", ScanImage: "dockerimagename:latest", } error := runMalwareScan(&malwareScanConfig, nil, &utils) assert.NoError(t, error) }) } type httpMock struct { Method string // is set during test execution URL string // is set before test execution ResponseBody string // is set before test execution Options piperhttp.ClientOptions // is set during test StatusCode int // is set during test Body readCloserMock // is set during test } func (c *httpMock) SetOptions(options piperhttp.ClientOptions) { c.Options = options } func (c *httpMock) SendRequest(method string, url string, r io.Reader, header http.Header, cookies []*http.Cookie) (*http.Response, error) { if strings.HasSuffix(url, "/info") { return &http.Response{StatusCode: 200, Body: &readCloserMock{Content: "{\"engineVersion\": \"Malware Service Mock\", \"signatureTimestamp\": \"2022-01-12T09:26:28.000Z\"}"}}, nil } c.Method = method c.URL = url _, err := ioutil.ReadAll(r) if err != nil { return nil, err } c.Body = readCloserMock{Content: c.ResponseBody} res := http.Response{StatusCode: c.StatusCode, Body: &c.Body} return &res, nil } type readCloserMock struct { Content string Closed bool } func (rc readCloserMock) Read(b []byte) (n int, err error) { if len(b) < len(rc.Content) { // in real life we would fill the buffer according to buffer size ... return 0, fmt.Errorf("Buffer size (%d) not sufficient, need: %d", len(b), len(rc.Content)) } copy(b, rc.Content) return len(rc.Content), io.EOF } func (rc *readCloserMock) Close() error { rc.Closed = true return nil } type dockerClientMock struct { imageName string registryURL string localPath string includeLayers bool } //DownloadImage download the image to the specified path func (c *dockerClientMock) DownloadImage(imageSource, filePath string) (v1.Image, error) { return &fake.FakeImage{}, nil // fmt.Errorf("%s", filePath) } //DownloadImage download the image to the specified path func (c *dockerClientMock) DownloadImageContent(imageSource, filePath string) (v1.Image, error) { return &fake.FakeImage{}, nil // fmt.Errorf("%s", filePath) } // GetRemoteImageInfo return remote image information func (c *dockerClientMock) GetRemoteImageInfo(imageSoure string) (v1.Image, error) { return &fake.FakeImage{}, nil }